Lite XL

About

FAQ Features Screenshots Contributors

Documentation

Usage Build Default Keymap MacOS Keymap

Tutorials

Simple Plugin Syntax Highlighting API Overview System Fonts

Plugins

Downloads

Using system fonts

lite-xl does not provide a convenient way to use fonts on the system. There is literally _different APIs for the each platforms we support (Windows, Linux and Mac). This is where fontconfig comes to our rescue. fontconfig is installable on a lot of OSes.

lite-xl has a fontconfig plugin that we can use to find system fonts.

Installing fontconfig

Windows

mingw-w64-fontconfig provides a build that can be used directly on Windows. Download the file, extract it to somewhere and (optionally) add it to the PATH.

Linux

Check your distro-specific instructions.

# ubuntu / debian
apt install fontconfig
# arch
pacman -Su fontconfig
# fedora
dnf install fontconfig
...

MacOS

brew install fontconfig

Setting up

  1. Install the plugin
  2. Put this in your user module:
local fontconfig = require "plugins.fontconfig"
fontconfig.use {
     font = { name = "sans", size = 13 * SCALE },
     code_font = { name = "monospace", size = 13 * SCALE }
}

"sans" and "monospace" can be any fontconfig syntax. (check "Font Names")

Note that the font might not load immediately (because we need to wait for fc-match to return. If you want that, replace fontconfig.use with fontconfig.use_blocking. Doing this will force lite-xl to wait for fc-match, which can be much slower.

System API

This is where Lite XL's lua code interact with its underlying C engine. Some of the functions here will be omitted because they're not useful for plugins.

Clipboard

  • system.set_clipboard(text) sets the clipboard content.
  • system.get_clipboard() retrieves the content of the clipboard.

File / Directory manipulation

  • system.list_dir(dir) returns a list of filenames in a directory.
  • system.rmdir(dir) removes a directory. Use this instead of os.remove(). The directory must be empty.
  • system.chdir(dir) changes the current working directory (like cd).
  • system.mkdir(dir) creates a new directory. It does not recursively create directories.
  • system.absolute_path(path) resolves the path components (.. and .) to an absolute path.
  • system.get_file_info(path) returns info about a path.
    • modified: last modification time of the file in seconds since UNIX epoch.
    • size: file size in bytes.
    • type: Path type ("file" or "dir").

Timing

  • system.get_time() returns time in seconds (as floating point number) since Lite XL started. Use this instead of os.time() for higher precision timers.
  • system.sleep(time) sleeps for time in milliseconds. Do not use this. Write asynchronous code.

Window manipulation

  • system.set_window_opacity(o) sets the window opacity from 0 to 1.
  • system.set_window_title(title) sets the window title.
  • system.set_window_mode(mode) sets window mode:
    • "normal": also known as "restored" on Windows.
    • "maximized": Maximize the window.
    • "minimized": Minimize the window.
    • "fullscreen": Fullscreen
  • system.set_window_bordered(bordered) enables or disable window border (decoration).
  • system.set_window_hit_test(height, control_width, resize_border) sets window hit test (used for config.borderless to make custom drawn border interactable).
    • If no argument is supplied, reset the hit test values.
    • height: height of the title bar.
    • controls_width: Not too sure about this, but it should be the size of the title bar controls (Maximize, Minimize and Normal buttons on the right). It seems to be fixed at the right side of the title bar.
    • resize_border: Number of pixels reserved for resizing the window. (setting this to a large value means that you can resize the window way easier)
  • system.get_window_size() gets the window size.
  • system.set_window_size(w, h, x, y) sets the window size (and also position).
  • system.window_has_focus() checks whether the window is in focus.
  • system.show_fatal_error(title, msg) shows an system error message box. Use nagview whenever possible.

Misc

  • system.exec(command) runs a command. Use the Process API instead of this.
  • system.fuzzy_match(haystack, needle, file) generates a score depends on how close the needle matches the haystack.
    • file: match backwards (more accurate for filename matching).

Process API

Lite XL provides a process API to launch external applications. This API is meant to replace lua's io.popen and lite's pipe-to-a-file approach.

Advantages of this API includes:

  • Proper argument escaping (arguments are supplied via a table)
  • Nonblocking IO
  • Able to detach processes from Lite XL (in progress)
  • Does not create temporary files
  • Mostly cross-platform (does not require special code for each shell)

Using the Process API

Error handling

  • process.start() may throw errors if it cannot run the program.
  • process.read* and process.write functions may throw errors if
    • the process ended
    • the process closed the stream
    • you closed the stream
  • there might be other errors to look forward to too

Starting a process

To start a process, use process.start(args, options).

Here are some of the more useful arguments.

  • args: The executable and any arguments, eg: { "sh", "-c", "echo hello world" }
  • options: Options for process.start()
    • env: A key-value table containing the env. Note that if this is provided, environment variables will not be inherited.
    • stdin: Specify where to redirect stdin
    • stdout: Specify where to redirect stdout
    • stderr: Specify where to redirect stderr

for options.std{in,out,err}, valid values are:

  • process.REDIRECT_PIPE (Make it available to subprocess API for reading / writing)
  • process.REDIRECT_DISCARD (Discard the output. Use this to prevent buffering)
  • process.REDIRECT_STDOUT (stderr only, for redirecting stderr to stdout)

Reading from process

To read from stdout or stderr of a process, use process:read_stdout() and process:read_stderr() respectively.

You can specify a numeric argument to them, which will change the size of internal buffer used to read the output.

Alternatively, you could use process:read() with process.STREAM_STDERR and process.STREAM_STDOUT.

Example:

local proc = process.start { "sh", "-c", "echo hello world!" }

-- do not use `while proc:running()` if you care about output.
-- The process could die and leave data in the buffer
-- You should just read until `proc:read_stdout()` returns nil
while true do
  local rdbuf = proc:read_stdout()
  if not rdbuf then break end
  -- yay, output
end

Writing to process

You can use process:write(data) to write a string to stdin.

Checking completion

  • process:running() returns a boolean to indicate whether if the process is running.
  • process:wait(time) also does the same thing, but you specify how long it should wait (or 0 to return immediately).

Terminating process

  • process:terminate() sends SIGTERM (or Windows equivalent) to the process.
  • process:kill() sends SIGKILL (or Windows equivalent) to the progress. Use this only if process:terminate() cannot kill the process, as it can cause issues.

Misc

  • process:pid() returns the PID of the process. There are no guarantees for this PID to be correct if the process terminated early.
  • process:returncode() returns the exit code of the process, if any
  • process:close_stream() closes stdin, stdout or stderr stream of the process.

Regex API

This API provides PCRE regular expressions for those who needs more power in matching text. This API written in C and Lua.

Creating a regex

Use regex.compile(pattern, options) to compile a regex.

  • pattern: The regex pattern
  • options: regex modifiers as a string, eg "im"
    • "i": Case-insensitive search
    • "m": Multiline search
    • "s": Match all characters with dot (.), including newlines.

Matching

Low level functions

  • regex:cmatch(str, offset, options) low-level matching function
    • str: The string to match against
    • offset: Where to start matching
    • options: A bit field of options
    • regex.ANCHORED: Only match from the start of the string
    • regex.ENDANCHORED: Only match from the end of the string
    • regex.NOTBOL: String is not beginning of line
    • regex.NOTEOL: String is not the end of line
    • regex.NOTEMPTY: Do not match an empty string
    • regex.NOTEMPTY_ATSTART: Do not match empty string at the start

Note: regex:cmatch() returns wrong indexes (currently at version 2.0.2). The end index returned by regex:cmatch() is always off by 1 (-1 to get the actual end index).

High level functions

All the functions below can be in 2 forms: - regex:fn(...) where regex is the compiled regex instance - regex.fn(pattern, ...) where pattern is a pattern string to be compiled and used directly.

We will only document the first form.

  • regex:match(str, offset, options) high level matching function. This function accepts the same arguments as regex:cmatch()
  • regex:gsub(str, replacement) replaces matches in str with replacement. Capture groups are identified with \0 to \9, this might change in the future.

API Overview

This is a work in progress.

Here we provide some resources and explanation to some API and aspects of Lite XL. For API docs, it's available in the repo Thus, we'll not explain everything, only the more important and frequently used features.

Built-in APIs:

Simple Plugin

What is Simple?

Simple is a very basic plugin written with the intention of introducing developers who are new to Lite XL to the process of writing plugins for the editor.

What does the plugin do?

The plugin displays a message (that is taken as input from the user) at the top right corner of the editor window. It also allows the user to toggle the visibility of the message.

I can't write Lua!

If you come from other programming languages, take a look at Lua cheatsheet. If you're new to programming, you can read this.

Format of the tutorial

The code contains comments detailing what most (if not all) of the code in the file does.

The code :

-- mod-version:2 -- lite-xl 2.0

-- you MUST put mod-version:x on the first line of your plugin
-- mod-version usually maps to lite-xl releases (eg. mod-version: 2 == lite-xl 2.0)
-- lite-xl won't load the plugin if the mod-version mismatches

-----------------------------------------------------------------------
-- NAME       : Simple
-- DESCRIPTION: A simple guide on how to make your first Lite XL plugin
-- AUTHOR     : Ashwin Godbole (aelobdog)
-- GOALS      : To render some text inside the editor
-----------------------------------------------------------------------
-- Disclaimer :
-- I am not a lua developer, and my knowledge about writing plugins for
-- Lite XL is very limited. This file serves the purpose of helping the
-- reader get started with plugin development for Lite XL, and therefore
-- demonstrates only some very basic features. For more complex plugin
-- development, be sure to check out the source code of some other
-- plugins after going through this file.
-----------------------------------------------------------------------
-- Before we start writing any code for the plugin, we must import the
-- required modules from the "core" package.

-- the "core" module
local core = require "core"

-- the "command" module will help us register commands for our plugin.
local command = require "core.command"

-- the "style" module will allow us to use styling options
local style = require "core.style"

-- the "config" module will be used to store certain things like colors
-- and functions
local config = require "core.config"

-- the "keymap" module will allow us to set keybindings for our commands
local keymap = require "core.keymap"

-- since we wants to modify RootView, we'll need to require it first
local RootView = require "core.rootview"

-----------------------------------------------------------------------
-- per-plugin config must stay in config.plugins.(plugin name)
config.plugins.simple = {}

-- colors are just three or four comma separated values (RGBA) (range 0 - 255)
-- put inside of '{ }'. We will add our color to the config module.
config.plugins.simple.text_color = {200, 140, 220} -- or use `{ common.color "#C88CDC" }`
-----------------------------------------------------------------------
-- Let's create a function to calculate the coordinates of our text.
-- While we're at it, let's add out function to the `config` module.
-- We'll take the message we want to display as the argument to the
-- function to determine the x and y coordinates of the text.

function config.plugins.simple.get_text_coordinates(message)
   -- For this plugin, we want to display the text on the top right
   -- corner of the screen. For this, we need to know the editor's width
   -- and height.

   -- The current font's size can be obtained from the "style" module.
   -- The editor's dimensions can be obtained by
   --   1. WIDTH  : core.root_view.size.x
   --   2. HEIGHT : core.root_view.size.y

   local message_width = style.code_font:get_width(message.." ")
   local font_height = style.code_font:get_size()
   local x = core.root_view.size.x - message_width
   local y = font_height / 2

   return x, y
end
-----------------------------------------------------------------------
-- Let's now get to actually drawing the text inside the editor.
-- In order to "inject" our own code to draw text,
-- we'll need to save the original draw function
-- We'll save `RootView.draw` to a variable we call `parent_draw`

local parent_draw = RootView.draw

-- Now let's overload the original definition of `draw` in RootView
-- by redefining the function.

function RootView:draw()
   -- We call the parent's function to keep the editor functional...
   -- obviously we must still draw all the other stuff !
   -- So we call the `parent_draw` function before doing anything else.
   parent_draw(self)

   -- we'll add an option to toggle the message on and off. let's use a
   -- boolean variable to keep track of whether we want to display the
   -- message or not.
   if config.plugins.simple.show_my_message then
      -- We'll be getting the message to display as input from the user
      -- later. We'll store that user input in `config.plugins.simple.hw_message`.
      -- (NOTE: this variable does not come in-built in lite-xl;
      --        it is a variable that we will define later.)

      -- let's store the value of config.plugins.simple.hw_message in a local variable
      -- `message` in case config.plugins.simple.hw_message we set the message to
      -- "message not set yet!"
      local message

      if config.plugins.simple.hw_message then
          message = config.plugins.simple.hw_message
      else
          message = "Message not set yet !"
      end

      -- let's get the coordinates for our text
      local x, y = config.plugins.simple.get_text_coordinates(message)

      -- let's finally draw the text to the window !
      -- the draw_text function from `renderer` is an important function
      -- as it is used to display any and all text inside of the editor
      -- window
      renderer.draw_text(style.code_font, message, x, y, config.plugins.simple.text_color)
   end
end
-----------------------------------------------------------------------
-- Let's allow the user to turn the message on and off
-- we'll write a function to flip our "show" boolean variable.

local function toggle_helloworld()
   config.plugins.simple.show_my_message = not config.plugins.simple.show_my_message
end
-----------------------------------------------------------------------
-- Finally, let's add the toggle function to the command list so that
-- we can call it from the C-S-p command panel. Let's add one command
-- to toggle the visibility of the message on and off and one to get
-- the user's message and then display it.

command.add(nil, {
   -- Toggle the visibility of the message
   ["simple:toggle"] = toggle_helloworld,

   -- Set and show the message
   -- This is the way to get user input through the command bar.
   -- `core.command_view:enter` takes 2 arguments:
   --    * the prompt to display before taking input
   --    * a function that takes the "input" as its argument
   -- (NOTE: here the variable we are reading input into is `text`)
   ["simple:setshow"] = function()
      core.command_view:enter("Test to display", function(text)
         config.plugins.simple.hw_message = text
         config.plugins.simple.show_my_message = true
      end)
   end
}
-----------------------------------------------------------------------
-- Just for fun, let's assign our commands their own keybindings.
-- Here, we assign the keybinding the same string(its name) as the one
-- that we set while creating the command
keymap.add {
   ["alt+s"] = "simple:setshow",
   ["alt+t"] = "simple:toggle",
}

Further reading

Syntax Highlighting

How to create syntax highlighting plugins for Lite XL

Syntax highlighting plugins for Lite XL are Lua files. These define some patterns or regular expressions that match different parts of a given language, assigning token types to each match. These different token types are then given different colors by your chosen color scheme.

Like other plugins, syntax definitions are sourced from the following folders, in order:

  • /usr/share/lite-xl/plugins/
  • $HOME/.config/lite-xl/plugins/

NOTE: The exact location of these folders will depend on your OS and installation method. For example, on Windows, the variable $USERPROFILE will be used instead of $HOME.

The user module folder for Lite XL can generally be found in these places on different OSes:

  • Windows: C:\Users\(username)\.config\lite-xl
  • MacOS: /Users/(usernmame)/.config/lite-xl
  • Linux: /home/(username)/.config/lite-xl

So, to create a new syntax definition on Linux, you can just create a .lua file in your $HOME/.config/lite-xl/plugins/ folder.

What syntax token types are supported?

The supported syntax token types, defined by lite-xl/core/style.lua, are:

  • normal
  • symbol
  • comment
  • keyword
  • keyword2
  • number
  • literal
  • string
  • operator
  • function

In your syntax highlighting plugin, you write patterns to match parts of the language syntax, assigning these token types to matches. You don't have to use them all - just use as many as you need for your language.

Let's walk through an example syntax definition and see how this works.

Example syntax: ssh config files

This is a small, simple example of a syntax definition. It's intended to highlight SSH Config files and looks like this:

-- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax"

syntax.add {
  files = { "sshd?/?_?config$" },
  comment = '#',
  patterns = {
    { pattern = "#.*\n",        type = "comment"  },
    { pattern = "%d+",          type = "number"   },
    { pattern = "[%a_][%w_]*",  type = "symbol"   },
    { pattern = "@",            type = "operator" },
  },
  symbols = {
    -- ssh config
    ["Host"]                         = "function",
    ["ProxyCommand"]                 = "function",

    ["HostName"]                     = "keyword",
    ["IdentityFile"]                 = "keyword",
    ...

    -- sshd config
    ["Subsystem"]                    = "keyword2",

    -- Literals
    ["yes"]      = "literal",
    ["no"]       = "literal",
    ["any"]      = "literal",
    ["ask"]      = "literal",
  },
}

Let's take each section in turn and see how it works.

The first line is a Lua comment & tells Lite XL which version this plugin requires. The second imports the core.syntax module for us to use:

-- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax"

We then add a syntax definition to lite, using syntax.add {...}. The contents of this definition are covered next.

Files

The files property tells Lite XL which files this syntax should be used for. This is a Lua pattern that matches against the full path of the file being opened. For example, to match against Markdown files - with either a .md or a .markdown extension, you could do this:

files = { "%.md$", "%.markdown$" },

In our original example, we match against the end of the path rather than the extension, because SSH config files don't have extensions - and we don't want to match all config files. We expect the path for SSH config files to look something like one of these:

  • ~/.ssh/config
  • /etc/ssh/ssh_config
  • /etc/ssh/sshd_config

This pattern matches paths that look like that:

files = { "sshd?/?_?config$" },

Comment

The comment property doesn't define which parts of the syntax are comments - see Patterns for that, below. This property tells Lite XL which character to insert at the start of selected lines when you press ctrl+/.

Patterns

A given piece of text can only match one pattern. Once Lite XL decides that a piece of text matches a pattern, it will assign that token type to that piece and move on. Patterns are tested in the order that they are written in the syntax definition, so the first match will win.

Each pattern takes one of the following forms:

Simple Pattern

{ pattern = "#.*\n",        type = "comment" },

This form matches the line against the pattern and if it matches, assigns the matching text to the given token type - comment, in this case.

Start & End Pattern

{ pattern = { "%[", "%]" }, type = "keyword" },

This form has two patterns - one that matches against the start of the range and one that matches against the end. Everything between the start and the end will be assigned the given token type.

Start & End Pattern, with Escape

{ pattern = { '"', '"', '\' }, type = "string" },

This is the same as the previous form, but with an extra, third parameter. The 3rd part, the '\' part in this example, specifies the character that allows escaping the closing match.

For more on Lua Patterns, see: Lua Pattern Reference

If you need to use PCRE Regular Expressions, instead of Lua Patterns, you can use the regex keyword here, instead of pattern.

Symbols

The symbols section allows you to assign token types to particular keywords or strings - usually reserved words in the language you are highlighting.

For example this highlights Host using the function token type, HostName as a keyword and yes, no, any & ask as a literal:

["Host"]                         = "function",
["HostName"]                     = "keyword",

["yes"]      = "literal",
["no"]       = "literal",
["any"]      = "literal",
["ask"]      = "literal",

Testing Your New Syntax

To test your new syntax highlighting you need to do two things:

  • Reload the Lite XL core
  • Load a file in your chosen language and see how it looks

To reload the core, you can either restart Lite XL, or reload the core from the command palette, without needing to restart. To do this, type ctrl+shit+p to show the command palette, then select Core: Restart (or type crr or something similar to match it), then press Enter. You will need to restart the core after any changes you make to the syntax highlighting definition.

Downloads

Binary packages

Binary packages are available on the GitHub releases page.

Install via package management

Alternatively, you can install lite-xl from your distribution's package manager. These packages are maintained by the community and may be outdated.

choco install lite-xl                                        # chocolatey
scoop bucket add extras && scoop install lite-xl             # scoop
sudo port install lite-xl                                    # macports
yay -S lite-xl                                               # or your favorite AUR helper
nix-env -i lite-xl                                           # nixos
sudo dnf copr enable sentry/lite && sudo dnf install lite-xl # fedora (copr)

Source Code

Source code is available on GitHub, by downloading zip or tar archives, or directly via git:

git clone https://github.com/lite-xl/lite-xl.git

MacOS Keymap

Keymaps on different operating systems have the same functionality, just bound slightly differently in order to conform to normal expectations for that operating system.

Currently, there are only two operating system layouts. MacOS, and everything else.

Keymap

Key Combination Actions
cmd+/ doc:toggle-line-comments
cmd+1 root:switch-to-tab-1
cmd+2 root:switch-to-tab-2
cmd+3 root:switch-to-tab-3
cmd+4 root:switch-to-tab-4
cmd+5 root:switch-to-tab-5
cmd+6 root:switch-to-tab-6
cmd+7 root:switch-to-tab-7
cmd+8 root:switch-to-tab-8
cmd+9 root:switch-to-tab-9
cmd+[ doc:move-to-previous-block-start
cmd+] doc:move-to-next-block-end
cmd+a doc:select-all
cmd+backspace doc:delete-to-start-of-indentation
cmd+c doc:copy
cmd+ctrl+i root:switch-to-up
cmd+ctrl+j root:switch-to-left
cmd+ctrl+k root:switch-to-down
cmd+ctrl+l root:switch-to-right
cmd+ctrl+return core:toggle-fullscreen
cmd+ctrl+shift+i root:split-up
cmd+ctrl+shift+j root:split-left
cmd+ctrl+shift+k root:split-down
cmd+ctrl+shift+l root:split-right
cmd+d doc:select-word
cmd+d find-replace:select-add-next
cmd+delete doc:delete-to-end-of-line
cmd+down doc:move-to-end-of-doc
cmd+f3 find-replace:select-next
cmd+f find-replace:find
cmd+g doc:go-to-line
cmd+j doc:join-lines
cmd+l doc:select-lines
cmd+left doc:move-to-start-of-indentation
cmd+n core:new-doc
cmd+o core:open-file
cmd+option+down doc:create-cursor-next-line
cmd+option+up doc:create-cursor-previous-line
cmd+p core:find-file
cmd+pagedown root:move-tab-right
cmd+pageup root:move-tab-left
cmd+r find-replace:replace
cmd+return doc:newline-below
cmd+right doc:move-to-end-of-line
cmd+s doc:save
cmd+shift+[ doc:select-to-previous-block-start
cmd+shift+] doc:select-to-next-block-end
cmd+shift+backspace doc:delete-to-previous-word-start
cmd+shift+c core:change-project-folder
cmd+shift+d doc:duplicate-lines
cmd+shift+delete doc:delete-to-next-word-end
cmd+shift+down doc:select-to-end-of-doc
cmd+shift+k doc:delete-lines
cmd+shift+l doc:select-word
cmd+shift+l find-replace:select-add-all
cmd+shift+left doc:select-to-start-of-indentation
cmd+shift+o core:open-project-folder
cmd+shift+p core:find-command
cmd+shift+return doc:newline-above
cmd+shift+right doc:select-to-end-of-line
cmd+shift+s doc:save-as
cmd+shift+up doc:select-to-start-of-doc
cmd+up doc:move-to-start-of-doc
cmd+v doc:paste
cmd+w root:close-or-quit
cmd+x doc:cut
cmd+y doc:redo
cmd+z doc:undo
ctrl+1lclick doc:split-cursor
ctrl+insert doc:copy
ctrl+shift+tab root:switch-to-previous-tab
ctrl+tab root:switch-to-next-tab
f3 find-replace:repeat-find
option+backspace doc:delete-to-previous-word-start
option+delete doc:delete-to-next-word-end
option+down doc:move-lines-down
option+left doc:move-to-previous-word-start
option+right doc:move-to-next-word-end
option+shift+left doc:select-to-previous-word-start
option+shift+right doc:select-to-next-word-end
option+up doc:move-lines-up
shift+f3 find-replace:previous-find

Build

Once you have downloaded the source code, you can build Lite XL yourself using Meson. In addition, the build-packages.sh script can be used to compile Lite XL and create an OS-specific package for Linux, Windows or macOS.

The following libraries are required:

  • freetype2
  • SDL2

The following libraries are optional:

  • libagg
  • Lua 5.2

If they are not found, they will be downloaded and compiled by Meson. Otherwise, if they are present, they will be used to compile Lite XL.

Build Script

If you compile Lite XL yourself, it is recommended to use the script build-packages.sh:

bash build-packages.sh -h

The script will run Meson and create a tar compressed archive with the application or, for Windows, a zip file. Lite XL can be easily installed by unpacking the archive in any directory of your choice.

On Windows two packages will be created, one called "portable" using the "data" folder next to the executable and the other one using a unix-like file layout. Both packages works correctly. The one with unix-like file layout is meant for people using a unix-like shell and the command line.

Please note that there aren't any hard-coded directories in the executable, so that the package can be extracted and used in any directory.

Portable

When performing the meson setup command you may enable the -Dportable=true option to specify whether files should be installed as in a portable application.

If portable is enabled, Lite XL is built to use a data directory placed next to the executable. Otherwise, Lite XL will use unix-like directory locations. In this case, the data directory will be $prefix/share/lite-xl and the executable will be located in $prefix/bin. $prefix is determined when the application starts as a directory such that $prefix/bin corresponds to the location of the executable.

The user directory does not depend on the portable option and will always be $HOME/.config/lite-xl. $HOME is determined from the corresponding environment variable. As a special case on Windows the variable $USERPROFILE will be used instead.

Linux

On Debian-based systems the required libraries and Meson can be installed using the following commands:

# To install the required libraries:
sudo apt install libfreetype6-dev libsdl2-dev

# To install Meson:
sudo apt install meson
# or pip3 install --user meson

To build Lite XL with Meson the commands below can be used:

meson setup --buildtype=release --prefix <prefix> build
meson compile -C build
DESTDIR="$(pwd)/lite-xl" meson install --skip-subprojects -C build

where <prefix> depends on the OS you are using: - on Linux is /usr - on macOS application bundle can be "/Lite XL.app"

If you are using a version of Meson below 0.54 you need to use diffent commands to compile and install:

meson setup --buildtype=release build
ninja -C build
ninja -C build install

macOS

macOS is fully supported and a notarized app disk image is provided in the release page. In addition the application can be compiled using the generic instructions given above.

Windows MSYS2

The build environment chosen for Lite XL on Windows is MSYS2. Follow the install instructions in the link.

  • Open MinGW 64-bit or MinGW 32-bit shell from the start menu.
  • Update the MSYS2 installation with pacman -Syu
  • Restart the shell
  • Install the dependencies:
pacman -S \
  ${MINGW_PACKAGE_PREFIX}-freetype \
  ${MINGW_PACKAGE_PREFIX}-gcc \
  ${MINGW_PACKAGE_PREFIX}-ninja \
  ${MINGW_PACKAGE_PREFIX}-pcre2 \
  ${MINGW_PACKAGE_PREFIX}-pkg-config \
  ${MINGW_PACKAGE_PREFIX}-python-pip \
  ${MINGW_PACKAGE_PREFIX}-SDL2
pip3 install meson

${MINGW_PACKAGE_PREFIX} expands either to mingw-w64-i686 or mingw-w64-x86_64 depending if the current shell is 32 or 64 bit.

Default Keymap

Keymaps on different operating systems have the same functionality, just bound slightly differently in order to conform to normal expectations for that operating system.

Currently, there are only two operating system layouts. MacOS, and everything else.

Keymap

Key Combination Actions
alt+1 root:switch-to-tab-1
alt+2 root:switch-to-tab-2
alt+3 root:switch-to-tab-3
alt+4 root:switch-to-tab-4
alt+5 root:switch-to-tab-5
alt+6 root:switch-to-tab-6
alt+7 root:switch-to-tab-7
alt+8 root:switch-to-tab-8
alt+9 root:switch-to-tab-9
alt+i root:switch-to-up
alt+j root:switch-to-left
alt+k root:switch-to-down
alt+l root:switch-to-right
alt+return core:toggle-fullscreen
alt+shift+i root:split-up
alt+shift+j root:split-left
alt+shift+k root:split-down
alt+shift+l root:split-right
ctrl+/ doc:toggle-line-comments
ctrl+1lclick doc:split-cursor
ctrl+[ doc:move-to-previous-block-start
ctrl+] doc:move-to-next-block-end
ctrl+a doc:select-all
ctrl+backspace doc:delete-to-previous-word-start
ctrl+c doc:copy
ctrl+d doc:select-word
ctrl+d find-replace:select-add-next
ctrl+delete doc:delete-to-next-word-end
ctrl+down doc:move-lines-down
ctrl+end doc:move-to-end-of-doc
ctrl+f3 find-replace:select-next
ctrl+f find-replace:find
ctrl+g doc:go-to-line
ctrl+home doc:move-to-start-of-doc
ctrl+i find-replace:toggle-sensitivity
ctrl+insert doc:copy
ctrl+j doc:join-lines
ctrl+l doc:select-lines
ctrl+left doc:move-to-previous-word-start
ctrl+n core:new-doc
ctrl+o core:open-file
ctrl+p core:find-file
ctrl+pagedown root:move-tab-right
ctrl+pageup root:move-tab-left
ctrl+r find-replace:replace
ctrl+return doc:newline-below
ctrl+right doc:move-to-next-word-end
ctrl+s doc:save
ctrl+shift+[ doc:select-to-previous-block-start
ctrl+shift+] doc:select-to-next-block-end
ctrl+shift+backspace doc:delete-to-previous-word-start
ctrl+shift+c core:change-project-folder
ctrl+shift+d doc:duplicate-lines
ctrl+shift+delete doc:delete-to-next-word-end
ctrl+shift+down doc:create-cursor-next-line
ctrl+shift+end doc:select-to-end-of-doc
ctrl+shift+f3 find-replace:select-previous
ctrl+shift+home doc:select-to-start-of-doc
ctrl+shift+i find-replace:toggle-regex
ctrl+shift+k doc:delete-lines
ctrl+shift+l doc:select-word
ctrl+shift+l find-replace:select-add-all
ctrl+shift+left doc:select-to-previous-word-start
ctrl+shift+o core:open-project-folder
ctrl+shift+p core:find-command
ctrl+shift+return doc:newline-above
ctrl+shift+right doc:select-to-next-word-end
ctrl+shift+s doc:save-as
ctrl+shift+tab root:switch-to-previous-tab
ctrl+shift+up doc:create-cursor-previous-line
ctrl+tab root:switch-to-next-tab
ctrl+up doc:move-lines-up
ctrl+v doc:paste
ctrl+w root:close
ctrl+x doc:cut
ctrl+y doc:redo
ctrl+z doc:undo
f11 core:toggle-fullscreen
f3 find-replace:repeat-find
shift+f3 find-replace:previous-find

Usage

Lite XL is a lightweight text editor written mostly in Lua — it aims to provide something practical, pretty, small and fast, implemented as simply as possible; easy to modify and extend, or to use without doing either.

Lite XL is based on the Lite editor and provide some enhancements while remaining generally compatible with it.

Getting Started

Lite XL works using a project directory — this is the directory where your project's code and other data resides.

To open a specific project directory the directory name can be passed as a command-line argument (. can be passed to use the current directory) or the directory can be dragged onto either the executable or a running instance.

Once started the project directory can be changed using the command core:change-project-folder. The command will close all the documents currently opened and switch to the new project directory.

If you want to open a project directory in a new window the command core:open-project-folder will open a new editor window with the selected project directory.

The main way of opening files in Lite XL is through the core:find-file command — this provides a fuzzy finder over all of the project's files and can be opened using the ctrl+p shortcut by default.

Commands can be run using keyboard shortcuts, or by using the core:find-command command bound to ctrl+shift+p by default. For example, pressing the above combination and typing newdoc then pressing return would open a new document. The current keyboard shortcut for a command can be seen to the right of the command name on the command finder, thus to find the shortcut for a command ctrl+shift+p can be pressed and the command name typed.

User Data Directories

Lite XL uses standard systems user directories; the user data can be found in $HOME/.config/lite-xl on Linux and macOS. On Windows, the variable $USERPROFILE will be used instead of $HOME.

User Module

Lite XL can be configured through use of the user module. The user module can be used for changing options in the config module, adding additional key bindings, loading custom color themes, modifying the style or changing any other part of the editor to your personal preference.

The user module is loaded when the application starts, after the plugins have been loaded.

The user module can be modified by running the core:open-user-module command or otherwise directly opening the $HOME/.config/lite-xl/init.lua file.

On Windows, the variable $USERPROFILE will be used instead of $HOME.

tl;dr:

  • Windows: C:\Users\(username)\.config\lite-xl\init.lua
  • MacOS: /Users/(usernmame)/.config/lite-xl/init.lua
  • Linux: /home/(username)/.config/lite-xl/init.lua

These aren't the exact location, but it gives you an idea where to find.

Please note that Lite XL differs from the standard Lite editor for the location of the user's module.

Project Module

The project module is an optional module which is loaded from the current project's directory when Lite XL is started. Project modules can be useful for things like adding custom commands for project-specific build systems, or loading project-specific plugins.

The project module is loaded when the application starts, after both the plugins and user module have been loaded.

The project module can be edited by running the core:open-project-module command — if the module does not exist for the current project when the command is run it will be created.

Add directories to a project

In addition to the project directories it is possible to add other directories using the command core:add-directory. Once added a directory it will be shown in the tree-view on the left side and the additional files will be reachable using the ctrl+p command (find file). The additonal files will be also visible when searching across the project.

The additional directories can be removed using the command core:remove-directory.

When you will open again Lite XL on the same project folder the application will remember your workspace including the additonal project directories.

Since version 1.15 Lite XL does not need a workspace plugin as it is now bundled with the editor.

Create new empty directory

Using the command files:create-directory or control-click in a directory in the tree-view to create a new empty subdirectory.

Commands

Commands are used both through the command finder (ctrl+shift+p) and by Lite XL's keyboard shortcut system. Commands consist of 3 components:

  • Name — The command name in the form of namespace:action-name, for example: doc:select-all
  • Predicate — A function that returns true if the command can be ran, for example, for any document commands the predicate checks whether the active view is a document
  • Function — The function which performs the command itself

Commands can be added using the command.add function provided by the core.command module:

local core = require "core"
local command = require "core.command"

command.add("core.docview", {
  ["doc:save"] = function()
    core.active_view.doc:save()
    core.log("Saved '%s', core.active_view.doc.filename)
  end
})

Commands can be performed programatically (eg. from another command or by your user module) by calling the command.perform function after requiring the command module:

local command = require "core.command"
command.perform "core:quit"

Keymap

All keyboard shortcuts are handled by the core.keymap module. A key binding maps a "stroke" (eg. ctrl+q) to one or more commands (eg. core:quit). When the shortcut is pressed Lite XL will iterate each command assigned to that key and run the predicate function for that command — if the predicate passes it stops iterating and runs the command.

An example of where this used is the default binding of the tab key:

  ["tab"] = { "command:complete", "doc:indent" },

When tab is pressed the command:complete command is attempted which will only succeed if the command-input at the bottom of the window is active. Otherwise the doc:indent command is attempted which will only succeed if we have a document as our active view.

A new mapping can be added by your user module as follows:

local keymap = require "core.keymap"
keymap.add { ["ctrl+q"] = "core:quit" }

A list of default mappings can be viewed here.

Global variables

There are a few global variables set by the editor. These variables are available everywhere and shouldn't be overwritten.

  • ARGS: command-line arguments. argv[1] is the program name, argv[2] is the 1st parameter, ...
  • PLATFORM: Output from SDL_GetPlatform(). Can be Windows, Mac OS X, Linux, iOS and Android.
  • SCALE: Font scale. Usually 1, but can be higher on HiDPI systems.
  • EXEFILE: An absolute path to the executable.
  • EXEDIR: The executable directory. DO NOT WRITE TO THIS DIRECTORY.
  • VERSION: lite-xl version.
  • MOD_VERSION: mod-version used in plugins. This is usually incremented when there are API changes.
  • PATHSEP: Path seperator. \ (Windows) or / (Other OSes)
  • DATADIR: The data directory, where the Lua part of lite-xl resides. DO NOT WRITE TO THIS DIRECTORY.
  • USERDIR: User configuration directory.

USERDIR should be used instead of DATADIR when configuring the editor because DATADIR might not be writable. (for example, if the editor is installed in /usr, DATADIR will be /usr/share/lite-xl!) USERDIR on the other hand should always be writable for the user, and allows multiple users to customize their own editor.

Plugins

Plugins in Lite XL are normal lua modules and are treated as such — no complicated plugin manager is provided, and, once a plugin is loaded, it is never expected be to have to unload itself.

To install a plugin simply drop it in the plugins directory in the user module directory. When Lite XL starts it will first load the plugins included in the data directory and will then loads the plugins located in the user module directory.

To uninstall a plugin the plugin file can be deleted — any plugin (including those included with the default installation) can be deleted to remove its functionality.

If you want to load a plugin only under a certain circumstance (for example, only on a given project) the plugin can be placed somewhere other than the plugins directory so that it is not automatically loaded. The plugin can then be loaded manually as needed by using the require function.

Plugins can be downloaded from the plugins repository.

Restarting the editor

If you modifies the user configuration file or some of the Lua implementation files you may restart the editor using the command core:restart. All the application will be restarting by keeping the window that is already in use.

Color Themes

Colors themes in Lite XL are lua modules which overwrite the color fields of Lite XL's core.style module. Pre-defined color methods are located in the colors folder in the data directory. Additional color themes can be installed in the user's directory in a folder named colors.

A color theme can be set by requiring it in your user module:

core.reload_module "colors.winter"

In the Lite editor the function require is used instead of core.reload_module. In Lite XL core.reload_module should be used to ensure that the color module is actually reloaded when saving the user's configuration file.

Color themes can be downloaded from the color themes repository. They are included with Lite XL release packages.

Features

Currently, Lite XL offers a lot of features out of the box.

Cross-Platform

We currently support Windows, Linux and MacOS (with Retina display support).

Lightweight

We are currently around 3MB in size and takes about 10MB in RAM (can be lower). No Electron / WebView involved. The whole thing is just Lua running on a rendering engine.

Extensible

While the editor is minimal by default, it is very extensible using Lua. In fact, a lot of features are provided by plugins. For example, VSC-like intellisense

Better font rendering

The editor looks good in screen of any sizes. Some other options are also configurable, such as hinting and antialiasing.

Multi-cursor editing

You can now place multiple cursors by ctrl + lclick on lines or ctrl + shift + up or ctrl + shift + down.


Here are some features that aren't implemented with the rationales behind it. Some of these may be implemented via plugins. We encourage you to give it a shot.

Hardware accelerated rendering

tl;dr - franko stated that he isn't considering using OpenGL due to the skills and work involved.

Hardware acceleration was brought up in this discussion. Takase had made 2 attempts at this - at first using NanoVG and then forcing SDL to use GPU rendering. In both attempts, the performance gains at best is negligible, while at worst its completely unusable. Right now, we decided to focus on optimizing the software renderer and various part of Lua code.

System fonts

This is painful because various systems has their own mechanism of managing fonts. For now, users can use the fontconfig plugin. Fontconfig is widely available on Linux and installable on MacOS, while Windows builds are available. In the future, we might consider adding API to read font metadata, allowing us to write a fontconfig alternative in Lua. (no promises here)

Opening UNC paths on Windows (network drives, accessing WSL2 files from Windows)

Our path handling code can only handle POSIX and Windows paths. We also aren't sure how Lite XL will behave in these scenarios.

Inter-window communication (dragging tabs between windows and other magic)

This is by far the hardest to achieve. Lite XL has no intention to link to any widget toolkits (Qt and GTK) which are required for these features. An alternative approach is to create our own IPC mechanism, but that's reinventing the wheel.

Integrated terminal

A terminal is complex to implement. There are projects that can be ported to Lua, such as xterm.js. If someone is interested, they can do so.

Contributors

Name Contributions
rxi Original development of lite editor.
Francesco Creator of lite-xl fork from rxi/lite.
Takase NagView and X Window database resource query for Xft.dpi setting.
Nils Kvist Popup window replacement with CommandView dialog.
liquidev Tab style and animations improvements.
Adam Multi-language syntax highlighting and many other improvements.
Cukmekerb Syntax highlighting improvements.
Janis-Leuenberger Add keymap bindings help file and macOS testing.
Mat Mariani Help for macOS port. Some resources taken from mathewmariani/lite-macos.
daubaris Initial implementation of Xft.dpi query using xrdb command.
Robert Štojs Continuos integration configuration.

Screenshots

Screenshot 1 Screenshot 2

FAQ

Can I get smart autocompletion (intellisense/LSP)?

Check out the LSP plugin.

Where is the integrated terminal?

Work is being done on lite-xl-terminal and lite-xl-tmt. Both plugins has their own supported features and bugs so you should try both out and see which one works for you.

Tabs and indent size?

In your user config (the cog icon in the file tree):

config.tab_type = "soft" -- soft for spaces, hard for real tabs (\t)
config.indent_size = 4   -- 4 spaces

How to bind commands to keys?

local keymap = require "core.keymap"
keymap.add { ["ctrl+escape"] = "core:quit" }

How to unbind commands for certain keys?

-- the second parameter lets you override commands for certain keys
-- in this case it maps it to nothing
keymap.add({ ["ctrl+escape"] = {} }, true)

How to get commands for those keybinds?

You can search for commands in the command palette.

For each command, replace the spaces in the right side with dashes.

For example: Core: Find Commandcore:find-command

What version of Lua does Lite XL use?

Lua 5.2.4. There's some activity around using LuaJIT instead (which is 5.1) but it can provide some Lua 5.2 compatibility.

Vim mode?

You need to vibe.

Plugin recommendations

Just in case you don't want to comb through our plugin repository, these are a list of plugins that just makes Lite XL a lot more pleasant.

Plugin Use case
autoinsert Automatically insert closing brackets and quotes
bracketmatch Highlight matching brackets
ephemeral_tabs Ephemeral tabs (previewing files without creating multiple tabs)
gitdiff_highlight Git diff gutter
linecopypaste Copy/Paste lines when nothing is selected
lint+ Linter support
minimap Minimap
selectionhighlight Highlight code that matches the selection
lite-xl-discord Discord rich presence

Where's feature X? How about Y?

You can get more info in the Features page.

Lite XL

A lightweight, simple, fast, feature-filled, and extremely extensible text editor written in C, and Lua, adapted from lite.

Lite XL Editor

Not Found

This page couldn't be found. Please note, that the site is undergoing testing at present, so pages may be missing/moved.

Get Involved

Github Discord Matrix

Status