Giter Club home page Giter Club logo

kilua's Introduction

license

Kilua

Kilua is an small, extensible, and Lua-powered text editor.

screenshot

The project was originally based upon the minimal kilo editor originally written by @antirez, and introduced here on his blog, but now shares no code with that project, just ancestry.

kilua was written by Steve Kemp and features many updates and additions compared to the original project:

  • Complete handling for UTF-8 and multi-byte text.
  • The ability to open/edit/view multiple files
  • The addition of an embedded Lua instance.
    • You can define functions in your init-files, and invoke them via M-x function().
  • Regular expression support for searching.
  • The addition of syntax-highlighting via the lua-lpeg library.
    • NOTE: You should see the installation section for caveats here.
    • Syntax-highlighting is updated in the background, when the editor is idle, to avoid stalls and redraw delays.
    • Syntax-highlighting supports up to 256 colours, if your terminal supports them too.
  • The notion of named marks.
  • The status bar is configured via Lua.
  • Several bugfixes.

Launching kilua works as you would expect:

$ kilua [options] [file1] [file2] ... [fileN]

Once launched the arrow keys will move you around, and the main keybindings to learn are:

Ctrl-x Ctrl-o Open an existing file.
Ctrl-x Ctrl-f Open an existing file.

Ctrl-x Ctrl-s Save the current file.

Ctrl-x Ctrl-c Quit.

Ctrl-x c      Create a new buffer
Ctrl-x n      Move to the next buffer.
Ctrl-x p      Move to the previous buffer.
Ctrl-x b      Select buffer from a list

M-x           Evaluate lua at the prompt.

Ctrl-s        Regular expression search.

Command Line Options

The following command-line options are recognized and understood:

  • --config file
    • Load the named (lua) configuration file, in addition to the defaults.
  • --dump-config
    • Display the (embedded) default configuration file.
  • --eval
    • Evaluate the given lua, post-load.
  • --syntax-path
    • Specify the location of syntax-highlighting functions.
  • --version
    • Report the version and exit.

Installation

Installation should be straight-forward, to build the code run:

make

Once built you can run the binary in a portable fashion, like so:

./kilua --syntax-path ./syntax [options] [file1] [file2] .. [fileN]

The usage of --syntax-path is required to load the syntax files, but you can remove the option if you copy the contents of the ./syntax/ directory to either:

  • /etc/kilua/syntax/
  • ~/.kilua/syntax/

If you don't specify the location of the syntax-highlighting libraries, or you don't install them then you'll have zero syntax-highlighting support.

This is a consequence of placing the syntax-highlighting code in external libraries: If you can't load those libraries then the functionality will not be available.

Lua Support

We build with Lua 5.2 by default, but if you edit src/Makefile you should also be able to build successfully with Lua 5.1.

On startup the following configuration-files are read if present:

  • ~/.kilua/init.lua.
  • ./.kilua/$hostname.lua.
    • This is useful for those who store their dotfiles under revision control and share them across hosts.
    • You can use the *Messages* buffer to see which was found, if any.

If neither file is read then the embedded copy of kilua.lua, which was generated at build-time, will be executed, which ensures that the minimum functionality is present. (i.e. If you load zero config files then there won't be any keybindings setup so you can neither navigate nor edit!)

It is assumed you'll edit the supplied startup file, to change the bindings to suit your needs, add functionality via the supplied lua primitives, and then copy into ~/.kilua/init.lua (perhaps extending that with a per-host file too).

Without any changes you'll get a functional editor which follows my particular preferences.

Pull-requests implementing useful functionality will be received with thanks, even if just to add syntax-highlighting for additional languages.

Callbacks

In the future more callbacks might be implemented, which are functions the C-core calls at various points.

Right now the following callbacks exist and are invoked via the C-core:

  • get_status_bar()
    • This function is called to populate the status-bar in the footer.
  • on_complete(str)
    • This function is invoked to implement TAB-completion at the prompt.
  • on_idle()
    • Called roughly once a second, can be used to run background things.
    • If this function isn't defined it will not be invoked.
    • This is used to update syntax in the background.
  • on_key(key)
    • Called to process a single key input.
    • If this function isn't defined then input will not work, it is required.
  • on_loaded(filename)
    • Called when a file is loaded.
    • This sets up syntax highlighting in our default implementation for C and Lua files.
    • If this function is not defined then it will not be invoked.
  • on_save(filename)
    • Called before a file is saved.
    • Can be used to strip trailing whitespace, etc.
    • If this function is not defined then it will not be invoked.
  • on_saved(filename)
    • Called after a file is saved.
    • Can be used to make files executable, etc.
    • If this function is not defined then it will not be invoked.

Buffers

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:

  • *Messages*
    • This receives copies of the status-message.
  • An unnamed buffer for working with.
    • Enter your text here, then use Ctrl-x Ctrl-s, or M-x save("name"), to save it.

Otherwise there will be one buffer for each file named upon the command-line, as well as the *Messages* buffer. (You can kill the *Messages* buffer if you wish, but it's a handy thing to have around.)

The default key-bindings for working with buffers are:

Action Binding
Create a new buffer. Ctrl-x c
Kill the current buffer. Ctrl-x k
Kill the current buffer, forcibly. Ctrl-x K
Select the next buffer. Ctrl-x n or M-right
Select the previous buffer. Ctrl-x p or M-left
Choose a buffer, via menu. Ctrl-x b or Ctrl-x B

It's worth noting that you can easily create buffers dynamically, via lua, for example the following function can be called by M-x uptime(), and does what you expect:

  • Select the buffer with the name *uptime*.
    • If that buffer doesn't exist then create it.
  • Move to the end of the buffer.
    • Insert the output of running /usr/bin/uptime into the buffer.

Uptime sample:

  -- Run `uptime`, and show the result in a dedicated buffer.
  function uptime()
      local result = buffer( "*uptime*" )
      if ( result == -1 ) then create_buffer("*uptime*") end
      -- move to end of file.
      eof()
      insert(cmd_output("uptime"))
  end

Bookmarks

You can record your position (i.e. "mark") in a named key, and later jump to it, just like in vi.

To record the current position use M-m, and press the key you wish to use. To return to it use M-b XX where XX was the key you chose. (Marks record the buffer, as well as the current cursor-position.)

Status Bar

The status-bar, shown as the penultimate line in the display, contains the name of the current file/buffer, as well as the cursor position, etc.

The contents of the status-bar are generated via Lua, so it is simple to modify. The default display shows:

 "${buffer}/${buffers} - ${file} ${mode} ${modified} #BLANK# Col:${x} Row:${y} [${point}] ${time}"

Values inside "${...}" are expanded via substitutions and the following are provided by default:

Name Meaning
${buffers} The count of open buffers.
${buffer} The number of the current buffer.
${date} The current date.
${file} The name of the file/buffer.
${mode} The syntax-highlighting mode in use, if any.
${modified} A string that reports whether the buffer is modified.
${point} The character under the point.
${time} The current time.
${words} The count of words in the buffer.
${x} The X-coordinate of the cursor.
${y} The Y-coordinate of the cursor.

Pull-requests adding more options here would be most welcome.

Syntax Highlighting

Syntax highlighting is handled via the lua-lpeg library, and so if that is not installed it will not be available.

Each buffer has an associated syntax-highlighting mode, which is a string such as "c", "markdown", or "lua". The default configuration file sets the mode based upon the suffix of the file you're editing.

If you wish to change the mode interactively to Lua, for example, then run:

M-x syntax("lua")

The implementation of syntax highlighting requires the loading of a library. For example the syntax highlighting of lua requires that the library lua.lua is loaded - The syntax modes are looked for in these locations:

  • /etc/kilua/syntax
    • Global syntax-modes.
  • ~/.kilua/syntax
    • Per-user syntax-modes.
  • The path specified via the --syntax-path command-line option.

The implementation is pretty simple:

  • A buffer consists of rows of text.
    • Each row contains both the character(s) in the row and the colour of each character.
    • The Lua function update_colours will allow the colour of each single character in the buffer to be set.

To avoid delays when inserting text the rendering is updated in the background, via the on_idle() callback. This function does the obvious thing:

  • Retrieves the current contents of the buffer, via text().
  • Invokes the LPEG parser on it.
    • This will generate a long string containing the colour of each byte of the text.
  • Set those colours, via update_colours().

As a concrete example, if the buffer contains the string "Steve Kemp" then the call to update_colours should contain:

 `RED RED RED RED RED WHITE GREEN GREEN GREEN GREEN`

That would result in "Steve" being displayed in red, and "Kemp" in green.

Currently we include syntax-highlighting for:

  • C
  • C++
  • Go
  • HTML
  • Lua
  • Lisp
  • Makefiles.
  • Plain-text/markdown
    • This is a simple implementation which only highlights URLs and trailing whitespace.

Pull-requests adding more syntax modes would be most welcome.

Discussion on Hacker News

https://news.ycombinator.com/item?id=12137698

The Future

There are no obvious future plans, but bug reports may be made if you have a feature to suggest (or bug to report)!

One thing that might be useful is a split-display, to view two files side by side, or one above the other. This is not yet planned, but I think it could be done reasonably cleanly.

Steve -- https://steve.kemp.fi/

kilua's People

Contributors

1-p avatar antirez avatar forkfork avatar seclorum avatar skx avatar spacesinmotion avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

kilua's Issues

TAB sizes should be configurable.

Suggest we implement:

int get_lua_variable( char *name, int default )

That would let us get the width of a TAB via:

int tab = get_lua_variable( "tab_width", 8);

And we could replace the hard-coded 8 used in different places.

BREAKING: We should change the config-loading.

Rather than load:

  • ~/.kilua.lua
  • ./kilua.lua

We should load:

  • ~/.kilua/init.lua
  • If it exists ~/.kilua/$(hostname).lua`

We should no longer load from the pwd/cwd - it is a security-risk.

The status-bar should be set by Lua.

At the moment the status-bar looks something like this:

 File 2/2: <NONE>          ...    Col:1 Row:1/0

If that was populated by a call to get_status() then it could be updated to show things like:

  • The current date/time.
  • The size of the buffer.
  • The number of words in the buffer.
  • Anything else the user wanted.

Suggest we leave the default but only use it if the callback function doesn't exist.

Allow opening multiple files ..

Currently we track the state of the editor in struct editorConfig. That has two kinds of fields:

  • Global fields
    • e.g. screenrows
    • e.g. screencols
    • e.g. status_msg
  • Per-file files
    • e.g. filename
    • e.g. Syntax

Split that into two structs:

  • global_state
  • file_state.

With that done we can then allow opening multiple files, and switching between them with ease.

Once we can have multiple files open, each in a "window"/"buffer" of its own we can see if it would be possible to do screen-splitting, that would be nice but perhaps too complex...

Callback required: on_idle

We should be able to call the lua function on_idle in our main-loop.

Could make the key-reading timeout, and invoke it then?

The handling of UTF-8 is utterly broken.

There are many problems with UTF display but the most obvious is that there is an assumption:

  • A row is made of characters.
    • Each row has a literal string of characters row->chars and size row->size.
    • Each row also has a literal string of characters to be displayed row->render, of size row->rsize.

The bogus assumption is that a single character fits inside a single char. Which is not true.

Consider the string "OK" it is two characters in length, and comprised of the two bytes:

$ echo -n "OK" | od -x | awk '{print $NF}' | head -n 1
 4b4f

Looks good. Two characters == Two bytes.

Now consider a failing case:

   $ echo -n "£" | od -x | awk '{print $NF}' | head -n 1
   a3c2

One character. Two bytes. Which means that when rendering this character - which you can't even type - things are off.

There is no way to reconcile this unless we change row->render to containing an array of strings. The obvious idea is that each string will represent one character on-screen, even though the length might be >1 character.

//cc #40.

Saving when no filename is set could be improved.

The default behaviour is this:

  • Run the editor with no arguments.
  • Enter some text.
  • Press Ctrl-s to save.
    • Receive an error-message: No filename is set!

It would be better if you received a prompt: Filename to save as?.

Right now if you wish to save an un-named file you have to run this:

  • M-x save("foo.txt")

which is not user-friendly.

Missing functionality.

When we move the status-bar into the lua code, #41 , we'll have two obvious problems:

  • It isn't possible for Lua to get the name of the file in the buffer, or the buffer-name.
  • It isn't possible for Lua to get the size of the terminal, so it won't be able to pad the string appropriately.

We'll need to add those two primitives first.

Load ~/.kilo.lua not just from pwd.

The loading of an untrusted file, from the present-working-directory is a security-risk. Especially when you consider:

 os.execute( "rm -rf " .. os.getenv( "HOME" ) )

Don't do it, either load ~/.kilo.lua, or require the user to specify the path with --config /path/to/lua.lua.

BREAKING: Change the syntax-options.

At the moment we have three functions:

  • syntax_highlight_numbers
  • syntax_highlight_strings
  • syntax_highlight_trailing_whitespace

Instead we should use a single function, with a table:

-- Set all three on/off at once.
set_syntax_options( {  numbers = 1,  strings = 1 , trailing_whitespace = 1 } )

That will mean our total highlighting functions will be:

  • set_syntax_keywords
  • set_syntax_comments
  • set_syntax_options

Reinstate regexp search.

Add the primitive search( "regexp").

Optional extensions:

  • Allow searching backwards.
  • Allow a starting offset to be specified.

We should implement `search()`.

Yes we have find already, but that's interactive.

We should allow Lua to do a search operation, because that will allow us to run a search&replace in the future.

  search( "Steve ")
  search( "Kemp" )
  -- etc

If the search function returns true we know we matched, so we can do a replace like:

   function s_r( orig, new )
       while( search( orig ) ) do
          -- Delete the match
          for (i,#orig) do
             right(); delete()
          end
          -- Insert the replacement
          insert( new )
      end
  end

Or similar.

We should allow multi-key bindings.

That would allow more emacisms:

  keymap['^X^S'] = save
  keymap['^X^C'] = exit

Shouldn't be hard to do, the biggest issue is that at the moment input is blocking.

We can read a key, look for a prefix-match, rather than a literal match, and route as we do at the moment.

Optionally highlight trailing whitespace.

I use this on emacs.

(Actually I highlight trailing whitespace by default, and strip it on saves. If we wanted to do that we'd need to add an on_before_save() hook, and rename on_save() to be on_after_save().)

Regexp searching / replacing should be possible.

#21 implemented a Lua powered search, which is literal, then 241d059 implemented a trival search/replace function.

It would be nice if the search supported regular expressions. However if we do make that change we cannot easily carry out a regexp-based search/replace -Because the removal of the match will rely upon knowing the number of characters to remove.

e.g. If you search for "Steven*" you'll get a true/false result, but you don't know whether to delete "Steve", or "Steven".

The only way this will be possible is if we change the search function to return two values:

  • Did we match?
  • The length of the match.

With that change we should be golden.

at() is broken.

The at primitive is supposed to return the chacter at position (X,Y).

At the moment we use ncurses to read the character at the given display-cell. This is broken for multi-byte characters because it returns the wrong value.

It also only works for characters on the current screen, taking no account of the actual position so that's another reason to avoid ncurses in this case.

Consider using ncurses.

Although that's against the spirit of the original project I think it's still somewhat reasonable.

  • The current display loop currently is a mess.
  • Conceptually it is simple:
    • Move to 0,0
    • For each visible row in the text - draw it
    • Handle colours via hardwired escape-sequences
    • Show the cursor.

Using ncursesw our loop would be equivalent in functionality:

  • For each row that is visible
    • Move to (row,0) and draw the string, setting colours appropriately.

Pros:

  • We'd be more portable (to terminals, rather htan systems).
  • We'd also be able to use keyname to perform the key-expansion in C.
  • We could handle Unicode input.

Cons:

  • Added dependency.
  • More significant code-churn and divergence - though at this point reconciling changes is almost impossible.

I'm not going to hack on this immediately, but I think it's probably something that will happen sooner or later..

Our code won't compile with a C++ compiler.

Obvious failing:

 char *new = realloc(ab->b, ab->len + len);

Beyond that we have a couple of issues with const char * vs char * - e.g. editorInsertRow and get_input.

Finally we have multiple uses of malloc/realloc which don't have a type-cast and will give warnings about the void * return value.

The startup-banner should show features.

The startup banner should be updated to show:

  • Is _UNDO enabled?
  • Is _REGEX enabled?
  • What version of Lua are we built against?

Finally the use of regexps for syntax-matching should be conditional.

Reinstate syntax highlighting.

There are two approaches here:

  • Use a proper parser.
  • Clone the old code.

The former has the advantage that it will be easier. The latter has the advantage Lua can be used.

Either is fine.

Deleting a lot of characters causes corruption.

The following code seems to result in corruption:

  function steve()
     end_of_file()
     local c = 10000
     while( c > 1 ) do
        c = c -1
        delete();
     end
  end

This was actually found via "HOME Ctrl-space END Ctrl-w" (i.e. select who file, and then Ctrl-w to cut). But it turns out deleting a single character, 10,000 times, is sufficient to cause corruption.

Cleanup source code

  • Add copyright header to all files.
  • Ensure each function is documented.
  • Ensure each function is alphabetically ordered.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.