Merge branch 'master' into dev
This commit is contained in:
commit
a31e683281
158
README.md
158
README.md
|
@ -1,130 +1,76 @@
|
||||||
# Lite XL
|
# Lite XL
|
||||||
|
|
||||||
[![Discord Badge Image]](https://discord.gg/SR4ArdYr)
|
[![Discord Badge Image]](https://discord.gg/RWzqC3nx7K)
|
||||||
|
|
||||||
![screenshot-dark](https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png)
|
![screenshot-dark]
|
||||||
|
|
||||||
A lightweight text editor written in Lua, adapted from [lite](https://github.com/rxi/lite)
|
A lightweight text editor written in Lua, adapted from [lite].
|
||||||
|
|
||||||
* **[Get Lite XL](https://github.com/franko/lite-xl/releases/latest)** — Download
|
* **[Get Lite XL]** — Download for Windows, Linux and Mac OS (notarized app).
|
||||||
for Windows, Linux and Mac OS (notarized app).
|
* **[Get plugins]** — Add additional functionality, adapted for Lite XL.
|
||||||
* **[Get started](doc/usage.md)** — A quick overview on how to get started
|
* **[Get color themes]** — Add additional colors themes.
|
||||||
* **[Get plugins](https://github.com/franko/lite-plugins)** — Add additional
|
|
||||||
functionality, adapted for Lite XL
|
|
||||||
* **[Get color themes](https://github.com/rxi/lite-colors)** — Add additional colors
|
|
||||||
themes
|
|
||||||
|
|
||||||
Lite XL has support for high DPI display on Windows and Linux and, since 1.16.7 release, it supports **retina displays** on Mac OS.
|
Please refer to our [website] for the user and developer documentation,
|
||||||
|
including [build] instructions.
|
||||||
|
|
||||||
|
Lite XL has support for high DPI display on Windows and Linux and,
|
||||||
|
since 1.16.7 release, it supports **retina displays** on macOS.
|
||||||
|
|
||||||
Please note that Lite XL is compatible with lite for most plugins and all color themes.
|
Please note that Lite XL is compatible with lite for most plugins and all color themes.
|
||||||
We provide a separate lite-plugins repository for Lite XL, because in some cases some adaptations may be needed to make them work better with Lite XL.
|
We provide a separate lite-plugins repository for Lite XL, because in some cases
|
||||||
The repository with modified plugins is http://github.com/franko/lite-plugins.
|
some adaptations may be needed to make them work better with Lite XL.
|
||||||
|
The repository with modified plugins is https://github.com/franko/lite-plugins.
|
||||||
|
|
||||||
The changes and differences between Lite XL and rxi/lite are listed in the [changelog](https://github.com/franko/lite-xl/blob/master/changelog.md).
|
The changes and differences between Lite XL and rxi/lite are listed in the
|
||||||
|
[changelog].
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Lite XL is derived from lite. It is a lightweight text editor written mostly in Lua — it aims to provide
|
Lite XL is derived from lite.
|
||||||
something practical, pretty, *small* and fast easy to modify and extend, or to use without doing either.
|
It is a lightweight text editor written mostly in Lua — it aims to provide
|
||||||
|
something practical, pretty, *small* and fast easy to modify and extend,
|
||||||
|
or to use without doing either.
|
||||||
|
|
||||||
The aim of Lite XL compared to lite is to be more user friendly, improve the quality of font rendering, and reduce CPU usage.
|
The aim of Lite XL compared to lite is to be more user friendly,
|
||||||
|
improve the quality of font rendering, and reduce CPU usage.
|
||||||
|
|
||||||
## Customization
|
## Customization
|
||||||
Additional functionality can be added through plugins which are available in
|
|
||||||
the [plugins repository](https://github.com/rxi/lite-plugins) or in the [Lite XL-specific plugins repository](https://github.com/franko/lite-plugins).
|
|
||||||
|
|
||||||
Additional color themes can be found in the [colors repository](https://github.com/rxi/lite-colors).
|
Additional functionality can be added through plugins which are available in
|
||||||
|
the [plugins repository] or in the [Lite XL plugins repository].
|
||||||
|
|
||||||
|
Additional color themes can be found in the [colors repository].
|
||||||
These color themes are bundled with all releases of Lite XL by default.
|
These color themes are bundled with all releases of Lite XL by default.
|
||||||
|
|
||||||
The editor can be customized by making changes to the [user module](data/user/init.lua).
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
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 Mac OS.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
On Debian-based systems the required libraries and Meson can be installed using the following commands:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# 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:
|
|
||||||
```sh
|
|
||||||
meson setup --buildtype=release build
|
|
||||||
meson compile -C build
|
|
||||||
meson install -C build
|
|
||||||
```
|
|
||||||
|
|
||||||
If you are using a version of Meson below 0.54 you need to use diffent commands to compile and install:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
meson setup --buildtype=release build
|
|
||||||
ninja -C build
|
|
||||||
ninja -C build install
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
If you compile Lite XL yourself, it is recommended to use the script `build-packages.sh`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
bash build-packages.sh <arch>
|
|
||||||
```
|
|
||||||
|
|
||||||
The script will run Meson and create a zip file with the application or, for linux, a tar compressed archive.
|
|
||||||
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.
|
|
||||||
|
|
||||||
Mac OS X is fully supported and a notarized app disk image is provided in the [release page](https://github.com/franko/lite-xl/releases).
|
|
||||||
In addition the application can be compiled using the generic instructions given above.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Any additional functionality that can be added through a plugin should be done
|
|
||||||
as a plugin, after which a pull request to the
|
|
||||||
[plugins repository](https://github.com/rxi/lite-plugins) can be made.
|
|
||||||
|
|
||||||
If the plugin uses any Lite XL-specific functionality, please open a pull request to the
|
Any additional functionality that can be added through a plugin should be done
|
||||||
[Lite XL plugins repository](https://github.com/franko/lite-plugins).
|
as a plugin, after which a pull request to the [plugins repository] can be made.
|
||||||
|
|
||||||
|
If the plugin uses any Lite XL-specific functionality,
|
||||||
|
please open a pull request to the [Lite XL plugins repository].
|
||||||
|
|
||||||
Pull requests to improve or modify the editor itself are welcome.
|
Pull requests to improve or modify the editor itself are welcome.
|
||||||
|
|
||||||
## License
|
## Licenses
|
||||||
This project is free software; you can redistribute it and/or modify it under
|
|
||||||
the terms of the MIT license. See [LICENSE](LICENSE) for details.
|
|
||||||
|
|
||||||
[Discord Badge Image]: https://img.shields.io/discord/847122429742809208?label=discord&logo=discord
|
This project is free software; you can redistribute it and/or modify it under
|
||||||
|
the terms of the MIT license. See [LICENSE] for details.
|
||||||
|
|
||||||
|
See the [licenses] file for details on licenses used by the required dependencies.
|
||||||
|
|
||||||
|
|
||||||
|
[Discord Badge Image]: https://img.shields.io/discord/847122429742809208?label=discord&logo=discord
|
||||||
|
[screenshot-dark]: https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png
|
||||||
|
[lite]: https://github.com/rxi/lite
|
||||||
|
[website]: https://lite-xl.github.io
|
||||||
|
[build]: https://lite-xl.github.io/en/build
|
||||||
|
[Get Lite XL]: https://github.com/franko/lite-xl/releases/latest
|
||||||
|
[Get plugins]: https://github.com/franko/lite-plugins
|
||||||
|
[Get color themes]: https://github.com/rxi/lite-colors
|
||||||
|
[changelog]: https://github.com/franko/lite-xl/blob/master/changelog.md
|
||||||
|
[Lite XL plugins repository]: https://github.com/franko/lite-plugins
|
||||||
|
[plugins repository]: https://github.com/rxi/lite-plugins
|
||||||
|
[colors repository]: https://github.com/rxi/lite-colors
|
||||||
|
[LICENSE]: LICENSE
|
||||||
|
[licenses]: licenses/licenses.md
|
||||||
|
|
|
@ -211,11 +211,11 @@ local commands = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:indent"] = function()
|
["doc:indent"] = function()
|
||||||
indent_text()
|
doc():indent_text(false, doc_multiline_selection(true))
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:unindent"] = function()
|
["doc:unindent"] = function()
|
||||||
indent_text(true)
|
doc():indent_text(true, doc_multiline_selection(true))
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:duplicate-lines"] = function()
|
["doc:duplicate-lines"] = function()
|
||||||
|
|
|
@ -411,6 +411,65 @@ function Doc:select_to(...)
|
||||||
self:set_selection(line, col, line2, col2)
|
self:set_selection(line, col, line2, col2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_indent_string()
|
||||||
|
if config.tab_type == "hard" then
|
||||||
|
return "\t"
|
||||||
|
end
|
||||||
|
return string.rep(" ", config.indent_size)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- returns the size of the original indent, and the indent
|
||||||
|
-- in your config format, rounded either up or down
|
||||||
|
local function get_line_indent(line, rnd_up)
|
||||||
|
local _, e = line:find("^[ \t]+")
|
||||||
|
local soft_tab = string.rep(" ", config.indent_size)
|
||||||
|
if config.tab_type == "hard" then
|
||||||
|
local indent = e and line:sub(1, e):gsub(soft_tab, "\t") or ""
|
||||||
|
return e, indent:gsub(" +", rnd_up and "\t" or "")
|
||||||
|
else
|
||||||
|
local indent = e and line:sub(1, e):gsub("\t", soft_tab) or ""
|
||||||
|
local number = #indent / #soft_tab
|
||||||
|
return e, indent:sub(1,
|
||||||
|
(rnd_up and math.ceil(number) or math.floor(number))*#soft_tab)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- un/indents text; behaviour varies based on selection and un/indent.
|
||||||
|
-- * if there's a selection, it will stay static around the
|
||||||
|
-- text for both indenting and unindenting.
|
||||||
|
-- * if you are in the beginning whitespace of a line, and are indenting, the
|
||||||
|
-- cursor will insert the exactly appropriate amount of spaces, and jump the
|
||||||
|
-- cursor to the beginning of first non whitespace characters
|
||||||
|
-- * if you are not in the beginning whitespace of a line, and you indent, it
|
||||||
|
-- inserts the appropriate whitespace, as if you typed them normally.
|
||||||
|
-- * if you are unindenting, the cursor will jump to the start of the line,
|
||||||
|
-- and remove the appropriate amount of spaces (or a tab).
|
||||||
|
function Doc:indent_text(unindent, line1, col1, line2, col2, swap)
|
||||||
|
local text = get_indent_string()
|
||||||
|
local _, se = self.lines[line1]:find("^[ \t]+")
|
||||||
|
local in_beginning_whitespace = col1 == 1 or (se and col1 <= se + 1)
|
||||||
|
local has_selection = line1 ~= line2 or col1 ~= col2
|
||||||
|
if unindent or has_selection or in_beginning_whitespace then
|
||||||
|
local l1d, l2d = #self.lines[line1], #self.lines[line2]
|
||||||
|
for line = line1, line2 do
|
||||||
|
local e, rnded = get_line_indent(self.lines[line], unindent)
|
||||||
|
self:remove(line, 1, line, (e or 0) + 1)
|
||||||
|
self:insert(line, 1,
|
||||||
|
unindent and rnded:sub(1, #rnded - #text) or rnded .. text)
|
||||||
|
end
|
||||||
|
l1d, l2d = #self.lines[line1] - l1d, #self.lines[line2] - l2d
|
||||||
|
if (unindent or in_beginning_whitespace) and not self:has_selection() then
|
||||||
|
local start_cursor = (se and se + 1 or 1) + l1d or #(self.lines[line1])
|
||||||
|
self:set_selection(line1, start_cursor, line2, start_cursor, swap)
|
||||||
|
else
|
||||||
|
self:set_selection(line1, col1 + l1d, line2, col2 + l2d, swap)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self:text_input(text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- For plugins to add custom actions of document change
|
-- For plugins to add custom actions of document change
|
||||||
function Doc:on_text_change(type)
|
function Doc:on_text_change(type)
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,7 +33,7 @@ function search.find(doc, line, col, text, opt)
|
||||||
for line = line, #doc.lines do
|
for line = line, #doc.lines do
|
||||||
local line_text = doc.lines[line]
|
local line_text = doc.lines[line]
|
||||||
if opt.regex then
|
if opt.regex then
|
||||||
local s, e = re:cmatch(line_text, col, true)
|
local s, e = re:cmatch(line_text, col)
|
||||||
if s then
|
if s then
|
||||||
return line, s, line, e
|
return line, s, line, e
|
||||||
end
|
end
|
||||||
|
|
|
@ -1106,6 +1106,11 @@ function core.blink_reset()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function core.request_cursor(value)
|
||||||
|
core.cursor_change_req = value
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function core.on_error(err)
|
function core.on_error(err)
|
||||||
-- write error to file
|
-- write error to file
|
||||||
local fp = io.open(USERDIR .. "/error.txt", "wb")
|
local fp = io.open(USERDIR .. "/error.txt", "wb")
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
-- pattern:gsub(string).
|
-- pattern:gsub(string).
|
||||||
regex.__index = function(table, key) return regex[key]; end
|
regex.__index = function(table, key) return regex[key]; end
|
||||||
|
|
||||||
regex.match = function(pattern_string, string, offset)
|
regex.match = function(pattern_string, string, offset, options)
|
||||||
local pattern = type(pattern_string) == "table" and
|
local pattern = type(pattern_string) == "table" and
|
||||||
pattern_string or regex.compile(pattern_string)
|
pattern_string or regex.compile(pattern_string)
|
||||||
return regex.cmatch(pattern, string, offset)
|
return regex.cmatch(pattern, string, offset or 1, options or 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Will iterate back through any UTF-8 bytes so that we don't replace bits
|
-- Will iterate back through any UTF-8 bytes so that we don't replace bits
|
||||||
|
@ -39,11 +39,11 @@ regex.gsub = function(pattern_string, str, replacement)
|
||||||
local pattern = type(pattern_string) == "table" and
|
local pattern = type(pattern_string) == "table" and
|
||||||
pattern_string or regex.compile(pattern_string)
|
pattern_string or regex.compile(pattern_string)
|
||||||
local result, indices = ""
|
local result, indices = ""
|
||||||
local n = 0
|
local matches, replacements = {}, {}
|
||||||
repeat
|
repeat
|
||||||
indices = { regex.cmatch(pattern, str) }
|
indices = { regex.cmatch(pattern, str) }
|
||||||
if #indices > 0 then
|
if #indices > 0 then
|
||||||
n = n + 1
|
table.insert(matches, indices)
|
||||||
local currentReplacement = replacement
|
local currentReplacement = replacement
|
||||||
if #indices > 2 then
|
if #indices > 2 then
|
||||||
for i = 1, (#indices/2 - 1) do
|
for i = 1, (#indices/2 - 1) do
|
||||||
|
@ -55,6 +55,7 @@ regex.gsub = function(pattern_string, str, replacement)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
|
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
|
||||||
|
table.insert(replacements, { indices[1], #currentReplacement+indices[1] })
|
||||||
if indices[1] > 1 then
|
if indices[1] > 1 then
|
||||||
result = result ..
|
result = result ..
|
||||||
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
|
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
|
||||||
|
@ -64,6 +65,6 @@ regex.gsub = function(pattern_string, str, replacement)
|
||||||
str = str:sub(indices[2])
|
str = str:sub(indices[2])
|
||||||
end
|
end
|
||||||
until #indices == 0 or indices[1] == indices[2]
|
until #indices == 0 or indices[1] == indices[2]
|
||||||
return result .. str, n
|
return result .. str, matches, replacements
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -783,7 +783,7 @@ end
|
||||||
|
|
||||||
function RootView:on_mouse_moved(x, y, dx, dy)
|
function RootView:on_mouse_moved(x, y, dx, dy)
|
||||||
if core.active_view == core.nag_view then
|
if core.active_view == core.nag_view then
|
||||||
system.set_cursor("arrow")
|
core.request_cursor("arrow")
|
||||||
core.active_view:on_mouse_moved(x, y, dx, dy)
|
core.active_view:on_mouse_moved(x, y, dx, dy)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -808,14 +808,14 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
||||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||||
local tab_index = node and node:get_tab_overlapping_point(x, y)
|
local tab_index = node and node:get_tab_overlapping_point(x, y)
|
||||||
if node and node:get_scroll_button_index(x, y) then
|
if node and node:get_scroll_button_index(x, y) then
|
||||||
system.set_cursor("arrow")
|
core.request_cursor("arrow")
|
||||||
elseif div then
|
elseif div then
|
||||||
local axis = (div.type == "hsplit" and "x" or "y")
|
local axis = (div.type == "hsplit" and "x" or "y")
|
||||||
if div.a:is_resizable(axis) and div.b:is_resizable(axis) then
|
if div.a:is_resizable(axis) and div.b:is_resizable(axis) then
|
||||||
system.set_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||||
end
|
end
|
||||||
elseif tab_index then
|
elseif tab_index then
|
||||||
system.set_cursor("arrow")
|
core.request_cursor("arrow")
|
||||||
if self.dragged_node and self.dragged_node ~= tab_index then
|
if self.dragged_node and self.dragged_node ~= tab_index then
|
||||||
local tab = node.views[self.dragged_node]
|
local tab = node.views[self.dragged_node]
|
||||||
table.remove(node.views, self.dragged_node)
|
table.remove(node.views, self.dragged_node)
|
||||||
|
@ -823,7 +823,7 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
||||||
self.dragged_node = tab_index
|
self.dragged_node = tab_index
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
system.set_cursor(node.active_view.cursor)
|
core.request_cursor(node.active_view.cursor)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -858,6 +858,10 @@ function RootView:draw()
|
||||||
local t = table.remove(self.deferred_draws)
|
local t = table.remove(self.deferred_draws)
|
||||||
t.fn(table.unpack(t))
|
t.fn(table.unpack(t))
|
||||||
end
|
end
|
||||||
|
if core.cursor_change_req then
|
||||||
|
system.set_cursor(core.cursor_change_req)
|
||||||
|
core.cursor_change_req = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -48,29 +48,6 @@ local function push_tokens(t, syn, pattern, full_text, find_results)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function is_escaped(text, idx, esc)
|
|
||||||
local byte = esc:byte()
|
|
||||||
local count = 0
|
|
||||||
for i = idx - 1, 1, -1 do
|
|
||||||
if text:byte(i) ~= byte then break end
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
return count % 2 == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function find_non_escaped(text, pattern, offset, esc)
|
|
||||||
while true do
|
|
||||||
local s, e = text:find(pattern, offset)
|
|
||||||
if not s then break end
|
|
||||||
if esc and is_escaped(text, s, esc) then
|
|
||||||
offset = e + 1
|
|
||||||
else
|
|
||||||
return s, e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- State is a 32-bit number that is four separate bytes, illustrating how many
|
-- State is a 32-bit number that is four separate bytes, illustrating how many
|
||||||
-- differnet delimiters we have open, and which subsyntaxes we have active.
|
-- differnet delimiters we have open, and which subsyntaxes we have active.
|
||||||
-- At most, there are 3 subsyntaxes active at the same time. Beyond that,
|
-- At most, there are 3 subsyntaxes active at the same time. Beyond that,
|
||||||
|
@ -155,26 +132,44 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
set_subsyntax_pattern_idx(0)
|
set_subsyntax_pattern_idx(0)
|
||||||
current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||||
retrieve_syntax_state(incoming_syntax, state)
|
retrieve_syntax_state(incoming_syntax, state)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function find_text(text, p, offset, at_start, close)
|
||||||
|
local target, res = p.pattern or p.regex, { 1, offset - 1 }, p.regex
|
||||||
|
local code = type(target) == "table" and target[close and 2 or 1] or target
|
||||||
|
if p.regex and type(p.regex) ~= "table" then
|
||||||
|
p._regex = p._regex or regex.compile(p.regex)
|
||||||
|
code = p._regex
|
||||||
|
end
|
||||||
|
repeat
|
||||||
|
res = p.pattern and { text:find(at_start and "^" .. code or code, res[2]+1) }
|
||||||
|
or { regex.match(code, text, res[2]+1, at_start and regex.ANCHORED or 0) }
|
||||||
|
if res[1] and close and target[3] then
|
||||||
|
local count = 0
|
||||||
|
for i = res[1] - 1, 1, -1 do
|
||||||
|
if text:byte(i) ~= target[3]:byte() then break end
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
-- Check to see if the escaped character is there,
|
||||||
|
-- and if it is not itself escaped.
|
||||||
|
if count % 2 == 0 then break end
|
||||||
|
end
|
||||||
|
until not res[1] or not close or not target[3]
|
||||||
|
return unpack(res)
|
||||||
end
|
end
|
||||||
|
|
||||||
while i <= #text do
|
while i <= #text do
|
||||||
-- continue trying to match the end pattern of a pair if we have a state set
|
-- continue trying to match the end pattern of a pair if we have a state set
|
||||||
if current_pattern_idx > 0 then
|
if current_pattern_idx > 0 then
|
||||||
local p = current_syntax.patterns[current_pattern_idx]
|
local p = current_syntax.patterns[current_pattern_idx]
|
||||||
local s, e = find_non_escaped(text, p.pattern[2], i, p.pattern[3])
|
local s, e = find_text(text, p, i, false, true)
|
||||||
|
|
||||||
local cont = true
|
local cont = true
|
||||||
-- If we're in subsyntax mode, always check to see if we end our syntax
|
-- If we're in subsyntax mode, always check to see if we end our syntax
|
||||||
-- first, before the found delimeter, as ending the subsyntax takes
|
-- first, before the found delimeter, as ending the subsyntax takes
|
||||||
-- precedence over ending the delimiter in the subsyntax.
|
-- precedence over ending the delimiter in the subsyntax.
|
||||||
if subsyntax_info then
|
if subsyntax_info then
|
||||||
local ss, se = find_non_escaped(
|
local ss, se = find_text(text, subsyntax_info, i, false, true)
|
||||||
text,
|
|
||||||
subsyntax_info.pattern[2],
|
|
||||||
i,
|
|
||||||
subsyntax_info.pattern[3]
|
|
||||||
)
|
|
||||||
-- If we find that we end the subsyntax before the
|
-- If we find that we end the subsyntax before the
|
||||||
-- delimiter, push the token, and signal we shouldn't
|
-- delimiter, push the token, and signal we shouldn't
|
||||||
-- treat the bit after as a token to be normally parsed
|
-- treat the bit after as a token to be normally parsed
|
||||||
|
@ -202,12 +197,7 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
-- we're ending early in the middle of a delimiter, or
|
-- we're ending early in the middle of a delimiter, or
|
||||||
-- just normally, upon finding a token.
|
-- just normally, upon finding a token.
|
||||||
if subsyntax_info then
|
if subsyntax_info then
|
||||||
local s, e = find_non_escaped(
|
local s, e = find_text(text, subsyntax_info, i, true, true)
|
||||||
text,
|
|
||||||
"^" .. subsyntax_info.pattern[2],
|
|
||||||
i,
|
|
||||||
nil
|
|
||||||
)
|
|
||||||
if s then
|
if s then
|
||||||
push_token(res, subsyntax_info.type, text:sub(i, e))
|
push_token(res, subsyntax_info.type, text:sub(i, e))
|
||||||
-- On finding unescaped delimiter, pop it.
|
-- On finding unescaped delimiter, pop it.
|
||||||
|
@ -219,16 +209,12 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
-- find matching pattern
|
-- find matching pattern
|
||||||
local matched = false
|
local matched = false
|
||||||
for n, p in ipairs(current_syntax.patterns) do
|
for n, p in ipairs(current_syntax.patterns) do
|
||||||
local pattern = (type(p.pattern) == "table") and p.pattern[1] or p.pattern
|
local find_results = { find_text(text, p, i, true, false) }
|
||||||
local find_results = { text:find("^" .. pattern, i) }
|
if find_results[1] then
|
||||||
local start, fin = find_results[1], find_results[2]
|
|
||||||
|
|
||||||
if start then
|
|
||||||
-- matched pattern; make and add tokens
|
-- matched pattern; make and add tokens
|
||||||
push_tokens(res, current_syntax, p, text, find_results)
|
push_tokens(res, current_syntax, p, text, find_results)
|
||||||
|
|
||||||
-- update state if this was a start|end pattern pair
|
-- update state if this was a start|end pattern pair
|
||||||
if type(p.pattern) == "table" then
|
if type(p.pattern or p.regex) == "table" then
|
||||||
-- If we have a subsyntax, push that onto the subsyntax stack.
|
-- If we have a subsyntax, push that onto the subsyntax stack.
|
||||||
if p.syntax then
|
if p.syntax then
|
||||||
push_subsyntax(p, n)
|
push_subsyntax(p, n)
|
||||||
|
@ -236,9 +222,8 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
set_subsyntax_pattern_idx(n)
|
set_subsyntax_pattern_idx(n)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- move cursor past this token
|
-- move cursor past this token
|
||||||
i = fin + 1
|
i = find_results[2] + 1
|
||||||
matched = true
|
matched = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,290 @@
|
||||||
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
|
local core = require "core"
|
||||||
|
local common = require "core.common"
|
||||||
|
local config = require "core.config"
|
||||||
|
local command = require "core.command"
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
local style = require "core.style"
|
||||||
|
local Object = require "core.object"
|
||||||
|
local RootView = require "core.rootview"
|
||||||
|
|
||||||
|
local border_width = 1
|
||||||
|
local divider_width = 1
|
||||||
|
local DIVIDER = {}
|
||||||
|
|
||||||
|
local ContextMenu = Object:extend()
|
||||||
|
|
||||||
|
ContextMenu.DIVIDER = DIVIDER
|
||||||
|
|
||||||
|
function ContextMenu:new()
|
||||||
|
self.itemset = {}
|
||||||
|
self.show_context_menu = false
|
||||||
|
self.selected = -1
|
||||||
|
self.height = 0
|
||||||
|
self.position = { x = 0, y = 0 }
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_item_size(item)
|
||||||
|
local lw, lh
|
||||||
|
if item == DIVIDER then
|
||||||
|
lw = 0
|
||||||
|
lh = divider_width
|
||||||
|
else
|
||||||
|
lw = style.font:get_width(item.text)
|
||||||
|
if item.info then
|
||||||
|
lw = lw + style.padding.x + style.font:get_width(item.info)
|
||||||
|
end
|
||||||
|
lh = style.font:get_height() + style.padding.y
|
||||||
|
end
|
||||||
|
return lw, lh
|
||||||
|
end
|
||||||
|
|
||||||
|
function ContextMenu:register(predicate, items)
|
||||||
|
if type(predicate) == "string" then
|
||||||
|
predicate = require(predicate)
|
||||||
|
end
|
||||||
|
if type(predicate) == "table" then
|
||||||
|
local class = predicate
|
||||||
|
predicate = function() return core.active_view:is(class) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local width, height = 0, 0 --precalculate the size of context menu
|
||||||
|
for i, item in ipairs(items) do
|
||||||
|
if item ~= DIVIDER then
|
||||||
|
item.info = keymap.reverse_map[item.command]
|
||||||
|
end
|
||||||
|
local lw, lh = get_item_size(item)
|
||||||
|
width = math.max(width, lw)
|
||||||
|
height = height + lh
|
||||||
|
end
|
||||||
|
width = width + style.padding.x * 2
|
||||||
|
items.width, items.height = width, height
|
||||||
|
table.insert(self.itemset, { predicate = predicate, items = items })
|
||||||
|
end
|
||||||
|
|
||||||
|
function ContextMenu:show(x, y)
|
||||||
|
self.items = nil
|
||||||
|
for _, items in ipairs(self.itemset) do
|
||||||
|
if items.predicate(x, y) then
|
||||||
|
self.items = items.items
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.items then
|
||||||
|
local w, h = self.items.width, self.items.height
|
||||||
|
|
||||||
|
-- by default the box is opened on the right and below
|
||||||
|
if x + w >= core.root_view.size.x then
|
||||||
|
x = x - w
|
||||||
|
end
|
||||||
|
if y + h >= core.root_view.size.y then
|
||||||
|
y = y - h
|
||||||
|
end
|
||||||
|
|
||||||
|
self.position.x, self.position.y = x, y
|
||||||
|
self.show_context_menu = true
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function ContextMenu:hide()
|
||||||
|
self.show_context_menu = false
|
||||||
|
self.items = nil
|
||||||
|
self.selected = -1
|
||||||
|
self.height = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function ContextMenu:each_item()
|
||||||
|
local x, y, w = self.position.x, self.position.y, self.items.width
|
||||||
|
local oy = y
|
||||||
|
return coroutine.wrap(function()
|
||||||
|
for i, item in ipairs(self.items) do
|
||||||
|
local _, lh = get_item_size(item)
|
||||||
|
if y - oy > self.height then break end
|
||||||
|
coroutine.yield(i, item, x, y, w, lh)
|
||||||
|
y = y + lh
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ContextMenu:on_mouse_moved(px, py)
|
||||||
|
if not self.show_context_menu then return end
|
||||||
|
|
||||||
|
self.selected = -1
|
||||||
|
for i, item, x, y, w, h in self:each_item() do
|
||||||
|
if px > x and px <= x + w and py > y and py <= y + h then
|
||||||
|
self.selected = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self.selected >= 0 then
|
||||||
|
core.request_cursor("arrow")
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function ContextMenu:on_selected(item)
|
||||||
|
if type(item.command) == "string" then
|
||||||
|
command.perform(item.command)
|
||||||
|
else
|
||||||
|
item.command()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ContextMenu:on_mouse_pressed(button, x, y, clicks)
|
||||||
|
local selected = (self.items or {})[self.selected]
|
||||||
|
local caught = false
|
||||||
|
|
||||||
|
self:hide()
|
||||||
|
if button == "left" then
|
||||||
|
if selected then
|
||||||
|
self:on_selected(selected)
|
||||||
|
caught = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if button == "right" then
|
||||||
|
caught = self:show(x, y)
|
||||||
|
end
|
||||||
|
return caught
|
||||||
|
end
|
||||||
|
|
||||||
|
-- copied from core.docview
|
||||||
|
function ContextMenu:move_towards(t, k, dest, rate)
|
||||||
|
if type(t) ~= "table" then
|
||||||
|
return self:move_towards(self, t, k, dest, rate)
|
||||||
|
end
|
||||||
|
local val = t[k]
|
||||||
|
if not config.transitions or math.abs(val - dest) < 0.5 then
|
||||||
|
t[k] = dest
|
||||||
|
else
|
||||||
|
rate = rate or 0.5
|
||||||
|
if config.fps ~= 60 or config.animation_rate ~= 1 then
|
||||||
|
local dt = 60 / config.fps
|
||||||
|
rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt)
|
||||||
|
end
|
||||||
|
t[k] = common.lerp(val, dest, rate)
|
||||||
|
end
|
||||||
|
if val ~= dest then
|
||||||
|
core.redraw = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ContextMenu:update()
|
||||||
|
if self.show_context_menu then
|
||||||
|
self:move_towards("height", self.items.height)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ContextMenu:draw()
|
||||||
|
if not self.show_context_menu then return end
|
||||||
|
core.root_view:defer_draw(self.draw_context_menu, self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ContextMenu:draw_context_menu()
|
||||||
|
if not self.items then return end
|
||||||
|
local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height
|
||||||
|
|
||||||
|
renderer.draw_rect(
|
||||||
|
bx - border_width,
|
||||||
|
by - border_width,
|
||||||
|
bw + (border_width * 2),
|
||||||
|
bh + (border_width * 2),
|
||||||
|
style.divider
|
||||||
|
)
|
||||||
|
renderer.draw_rect(bx, by, bw, bh, style.background3)
|
||||||
|
|
||||||
|
for i, item, x, y, w, h in self:each_item() do
|
||||||
|
if item == DIVIDER then
|
||||||
|
renderer.draw_rect(x, y, w, h, style.caret)
|
||||||
|
else
|
||||||
|
if i == self.selected then
|
||||||
|
renderer.draw_rect(x, y, w, h, style.selection)
|
||||||
|
end
|
||||||
|
|
||||||
|
common.draw_text(style.font, style.text, item.text, "left", x + style.padding.x, y, w, h)
|
||||||
|
if item.info then
|
||||||
|
common.draw_text(style.font, style.dim, item.info, "right", x, y, w - style.padding.x, h)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local menu = ContextMenu()
|
||||||
|
local root_view_on_mouse_pressed = RootView.on_mouse_pressed
|
||||||
|
local root_view_on_mouse_moved = RootView.on_mouse_moved
|
||||||
|
local root_view_update = RootView.update
|
||||||
|
local root_view_draw = RootView.draw
|
||||||
|
|
||||||
|
function RootView:on_mouse_moved(...)
|
||||||
|
if menu:on_mouse_moved(...) then return end
|
||||||
|
root_view_on_mouse_moved(self, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- this function is mostly copied from lite-xl's source
|
||||||
|
function RootView:on_mouse_pressed(button, x,y, clicks)
|
||||||
|
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||||
|
if div then
|
||||||
|
self.dragged_divider = div
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||||
|
if node.hovered_scroll_button > 0 then
|
||||||
|
node:scroll_tabs(node.hovered_scroll_button)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local idx = node:get_tab_overlapping_point(x, y)
|
||||||
|
if idx then
|
||||||
|
if button == "middle" or node.hovered_close == idx then
|
||||||
|
node:close_view(self.root_node, node.views[idx])
|
||||||
|
else
|
||||||
|
self.dragged_node = idx
|
||||||
|
node:set_active_view(node.views[idx])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
core.set_active_view(node.active_view)
|
||||||
|
-- send to context menu first
|
||||||
|
if not menu:on_mouse_pressed(button, x, y, clicks) then
|
||||||
|
node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function RootView:update(...)
|
||||||
|
root_view_update(self, ...)
|
||||||
|
menu:update()
|
||||||
|
end
|
||||||
|
|
||||||
|
function RootView:draw(...)
|
||||||
|
root_view_draw(self, ...)
|
||||||
|
menu:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
command.add(nil, {
|
||||||
|
["context:show"] = function()
|
||||||
|
menu:show(core.active_view.position.x, core.active_view.position.y)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
keymap.add {
|
||||||
|
["menu"] = "context:show"
|
||||||
|
}
|
||||||
|
|
||||||
|
if require("plugins.scale") then
|
||||||
|
menu:register("core.docview", {
|
||||||
|
{ text = "Font +", command = "scale:increase" },
|
||||||
|
{ text = "Font -", command = "scale:decrease" },
|
||||||
|
{ text = "Font Reset", command = "scale:reset" },
|
||||||
|
DIVIDER,
|
||||||
|
{ text = "Find", command = "find-replace:find" },
|
||||||
|
{ text = "Replace", command = "find-replace:replace" },
|
||||||
|
DIVIDER,
|
||||||
|
{ text = "Find Pattern", command = "find-replace:find-pattern" },
|
||||||
|
{ text = "Replace Pattern", command = "find-replace:replace-pattern" },
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return menu
|
|
@ -0,0 +1,100 @@
|
||||||
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
|
local core = require "core"
|
||||||
|
local common = require "core.common"
|
||||||
|
local command = require "core.command"
|
||||||
|
local config = require "core.config"
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
local style = require "core.style"
|
||||||
|
local RootView = require "core.rootview"
|
||||||
|
local CommandView = require "core.commandview"
|
||||||
|
|
||||||
|
config.scale_mode = "code"
|
||||||
|
config.scale_use_mousewheel = true
|
||||||
|
|
||||||
|
local scale_level = 0
|
||||||
|
local scale_steps = 0.05
|
||||||
|
|
||||||
|
local current_scale = SCALE
|
||||||
|
local default_scale = SCALE
|
||||||
|
|
||||||
|
local function set_scale(scale)
|
||||||
|
scale = common.clamp(scale, 0.2, 6)
|
||||||
|
|
||||||
|
-- save scroll positions
|
||||||
|
local scrolls = {}
|
||||||
|
for _, view in ipairs(core.root_view.root_node:get_children()) do
|
||||||
|
local n = view:get_scrollable_size()
|
||||||
|
if n ~= math.huge and not view:is(CommandView) and n > view.size.y then
|
||||||
|
scrolls[view] = view.scroll.y / (n - view.size.y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local s = scale / current_scale
|
||||||
|
current_scale = scale
|
||||||
|
|
||||||
|
if config.scale_mode == "ui" then
|
||||||
|
SCALE = scale
|
||||||
|
|
||||||
|
style.padding.x = style.padding.x * s
|
||||||
|
style.padding.y = style.padding.y * s
|
||||||
|
style.divider_size = style.divider_size * s
|
||||||
|
style.scrollbar_size = style.scrollbar_size * s
|
||||||
|
style.caret_width = style.caret_width * s
|
||||||
|
style.tab_width = style.tab_width * s
|
||||||
|
|
||||||
|
for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do
|
||||||
|
renderer.font.set_size(style[name], s * style[name]:get_size())
|
||||||
|
end
|
||||||
|
else
|
||||||
|
renderer.font.set_size(style.code_font, s * style.code_font:get_size())
|
||||||
|
end
|
||||||
|
|
||||||
|
-- restore scroll positions
|
||||||
|
for view, n in pairs(scrolls) do
|
||||||
|
view.scroll.y = n * (view:get_scrollable_size() - view.size.y)
|
||||||
|
view.scroll.to.y = view.scroll.y
|
||||||
|
end
|
||||||
|
|
||||||
|
core.redraw = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local on_mouse_wheel = RootView.on_mouse_wheel
|
||||||
|
|
||||||
|
function RootView:on_mouse_wheel(d, ...)
|
||||||
|
if keymap.modkeys["ctrl"] and config.scale_use_mousewheel then
|
||||||
|
if d < 0 then command.perform "scale:decrease" end
|
||||||
|
if d > 0 then command.perform "scale:increase" end
|
||||||
|
else
|
||||||
|
return on_mouse_wheel(self, d, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function res_scale()
|
||||||
|
scale_level = 0
|
||||||
|
set_scale(default_scale)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function inc_scale()
|
||||||
|
scale_level = scale_level + 1
|
||||||
|
set_scale(default_scale + scale_level * scale_steps)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dec_scale()
|
||||||
|
scale_level = scale_level - 1
|
||||||
|
set_scale(default_scale + scale_level * scale_steps)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
command.add(nil, {
|
||||||
|
["scale:reset" ] = function() res_scale() end,
|
||||||
|
["scale:decrease"] = function() dec_scale() end,
|
||||||
|
["scale:increase"] = function() inc_scale() end,
|
||||||
|
})
|
||||||
|
|
||||||
|
keymap.add {
|
||||||
|
["ctrl+0"] = "scale:reset",
|
||||||
|
["ctrl+-"] = "scale:decrease",
|
||||||
|
["ctrl+="] = "scale:increase",
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
|
local LogView = require "core.logview"
|
||||||
|
|
||||||
|
|
||||||
local function workspace_files_for(project_dir)
|
local function workspace_files_for(project_dir)
|
||||||
|
@ -29,7 +30,7 @@ local function workspace_files_for(project_dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function load_workspace_file(project_dir)
|
local function consume_workspace_file(project_dir)
|
||||||
for filename, id in workspace_files_for(project_dir) do
|
for filename, id in workspace_files_for(project_dir) do
|
||||||
local load_f = loadfile(filename)
|
local load_f = loadfile(filename)
|
||||||
local workspace = load_f and load_f()
|
local workspace = load_f and load_f()
|
||||||
|
@ -85,6 +86,7 @@ local function save_view(view)
|
||||||
text = not view.doc.filename and view.doc:get_text(1, 1, math.huge, math.huge)
|
text = not view.doc.filename and view.doc:get_text(1, 1, math.huge, math.huge)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
if mt == LogView then return end
|
||||||
for name, mod in pairs(package.loaded) do
|
for name, mod in pairs(package.loaded) do
|
||||||
if mod == mt then
|
if mod == mt then
|
||||||
return {
|
return {
|
||||||
|
@ -99,16 +101,26 @@ end
|
||||||
|
|
||||||
local function load_view(t)
|
local function load_view(t)
|
||||||
if t.type == "doc" then
|
if t.type == "doc" then
|
||||||
local ok, doc = pcall(core.open_doc, t.filename)
|
local dv
|
||||||
if not ok then
|
if not t.filename then
|
||||||
return DocView(core.open_doc())
|
-- document not associated to a file
|
||||||
|
dv = DocView(core.open_doc())
|
||||||
|
if t.text then dv.doc:insert(1, 1, t.text) end
|
||||||
|
else
|
||||||
|
-- we have a filename, try to read the file
|
||||||
|
local ok, doc = pcall(core.open_doc, t.filename)
|
||||||
|
if ok then
|
||||||
|
dv = DocView(doc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- doc view "dv" can be nil here if the filename associated to the document
|
||||||
|
-- cannot be read.
|
||||||
|
if dv and dv.doc then
|
||||||
|
dv.doc:set_selection(table.unpack(t.selection))
|
||||||
|
dv.last_line, dv.last_col = dv.doc:get_selection()
|
||||||
|
dv.scroll.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
|
||||||
|
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
|
||||||
end
|
end
|
||||||
local dv = DocView(doc)
|
|
||||||
if t.text then doc:insert(1, 1, t.text) end
|
|
||||||
doc:set_selection(table.unpack(t.selection))
|
|
||||||
dv.last_line, dv.last_col = doc:get_selection()
|
|
||||||
dv.scroll.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
|
|
||||||
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
|
|
||||||
return dv
|
return dv
|
||||||
end
|
end
|
||||||
return require(t.module)()
|
return require(t.module)()
|
||||||
|
@ -141,13 +153,19 @@ end
|
||||||
local function load_node(node, t)
|
local function load_node(node, t)
|
||||||
if t.type == "leaf" then
|
if t.type == "leaf" then
|
||||||
local res
|
local res
|
||||||
for _, v in ipairs(t.views) do
|
local active_view
|
||||||
|
for i, v in ipairs(t.views) do
|
||||||
local view = load_view(v)
|
local view = load_view(v)
|
||||||
if v.active then res = view end
|
if view then
|
||||||
node:add_view(view)
|
if v.active then res = view end
|
||||||
|
node:add_view(view)
|
||||||
|
if t.active_view == i then
|
||||||
|
active_view = view
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if t.active_view then
|
if active_view then
|
||||||
node:set_active_view(node.views[t.active_view])
|
node:set_active_view(active_view)
|
||||||
end
|
end
|
||||||
return res
|
return res
|
||||||
else
|
else
|
||||||
|
@ -184,7 +202,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
local function load_workspace()
|
local function load_workspace()
|
||||||
local workspace = load_workspace_file(core.project_dir)
|
local workspace = consume_workspace_file(core.project_dir)
|
||||||
if workspace then
|
if workspace then
|
||||||
local root = get_unlocked_root(core.root_view.root_node)
|
local root = get_unlocked_root(core.root_view.root_node)
|
||||||
local active_view = load_node(root, workspace.documents)
|
local active_view = load_node(root, workspace.documents)
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
## rxi
|
|
||||||
|
|
||||||
Original development of lite editor.
|
|
||||||
|
|
||||||
## Francesco Abbate (franko)
|
|
||||||
|
|
||||||
Creator of lite-xl fork from rxi/lite.
|
|
||||||
|
|
||||||
## Takase (takase1121)
|
|
||||||
|
|
||||||
NagView and X Window database resource query for Xft.dpi setting.
|
|
||||||
|
|
||||||
## Nils Kvist (budRich)
|
|
||||||
|
|
||||||
Popup window replacement with CommandView dialog.
|
|
||||||
|
|
||||||
## liquidev
|
|
||||||
|
|
||||||
Tab style and animations improvements.
|
|
||||||
|
|
||||||
## adamharrison
|
|
||||||
|
|
||||||
Multi-language syntax highlighting and many other improvements.
|
|
||||||
|
|
||||||
## vincens2005
|
|
||||||
|
|
||||||
Syntax highlighting improvements.
|
|
||||||
|
|
||||||
## Janis-Leuenberger
|
|
||||||
|
|
||||||
Add keymap bindings help file and macOS testing.
|
|
||||||
|
|
||||||
## Mat Mariani (mathewmariani)
|
|
||||||
|
|
||||||
Help for Mac OS port. Some resources taken from mathewmariani/lite-macos.
|
|
||||||
|
|
||||||
## daubaris
|
|
||||||
|
|
||||||
Initial implementation of Xft.dpi query using xrdb command.
|
|
||||||
|
|
||||||
## Robert Štojs (netrobert)
|
|
||||||
|
|
||||||
Continuos integration configuration
|
|
|
@ -1,114 +0,0 @@
|
||||||
# Default keymap
|
|
||||||
|
|
||||||
*Note: When using macOS `ctrl` refers to the command key.*
|
|
||||||
|
|
||||||
| Key combination | Actions |
|
|
||||||
| --------------------- | ----------------------------------- |
|
|
||||||
| ctrl+shift+p | core:find-command |
|
|
||||||
| ctrl+p | core:find-file |
|
|
||||||
| ctrl+o | core:open-file |
|
|
||||||
| ctrl+n | core:new-doc |
|
|
||||||
| ctrl+shift+c | core:change-project-folder |
|
|
||||||
| ctrl+shift+o | core:open-project-folder |
|
|
||||||
| alt+return | core:toggle-fullscreen |
|
|
||||||
| f11 | core:toggle-fullscreen |
|
|
||||||
| alt+shift+j | root:split-left |
|
|
||||||
| alt+shift+l | root:split-right |
|
|
||||||
| alt+shift+i | root:split-up |
|
|
||||||
| alt+shift+k | root:split-down |
|
|
||||||
| alt+j | root:switch-to-left |
|
|
||||||
| alt+l | root:switch-to-right |
|
|
||||||
| alt+i | root:switch-to-up |
|
|
||||||
| alt+k | root:switch-to-down |
|
|
||||||
| ctrl+w | root:close |
|
|
||||||
| ctrl+tab | root:switch-to-next-tab |
|
|
||||||
| ctrl+shift+tab | root:switch-to-previous-tab |
|
|
||||||
| ctrl+pageup | root:move-tab-left |
|
|
||||||
| ctrl+pagedown | root:move-tab-right |
|
|
||||||
| 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 |
|
|
||||||
| ctrl+f | find-replace:find |
|
|
||||||
| ctrl+r | find-replace:replace |
|
|
||||||
| f3 | find-replace:repeat-find |
|
|
||||||
| shift+f3 | find-replace:previous-find |
|
|
||||||
| ctrl+g | doc:go-to-line |
|
|
||||||
| ctrl+s | doc:save |
|
|
||||||
| ctrl+shift+s | doc:save-as |
|
|
||||||
| ctrl+z | doc:undo |
|
|
||||||
| ctrl+y | doc:redo |
|
|
||||||
| ctrl+x | doc:cut |
|
|
||||||
| ctrl+c | doc:copy |
|
|
||||||
| ctrl+v | doc:paste |
|
|
||||||
| ctrl+insert | doc:copy |
|
|
||||||
| shift+insert | doc:paste |
|
|
||||||
| escape | command:escape |
|
|
||||||
| escape | doc:select-none |
|
|
||||||
| escape | dialog:select-no |
|
|
||||||
| tab | command:complete |
|
|
||||||
| tab | doc:indent |
|
|
||||||
| shift+tab | doc:unindent |
|
|
||||||
| backspace | doc:backspace |
|
|
||||||
| shift+backspace | doc:backspace |
|
|
||||||
| ctrl+backspace | doc:delete-to-previous-word-start |
|
|
||||||
| ctrl+shift+backspace | doc:delete-to-previous-word-start |
|
|
||||||
| delete | doc:delete |
|
|
||||||
| shift+delete | doc:delete |
|
|
||||||
| ctrl+delete | doc:delete-to-next-word-end |
|
|
||||||
| ctrl+shift+delete | doc:delete-to-next-word-end |
|
|
||||||
| return | command:submit |
|
|
||||||
| return | doc:newline |
|
|
||||||
| return | dialog:select |
|
|
||||||
| keypad enter | command:submit |
|
|
||||||
| keypad enter | doc:newline |
|
|
||||||
| keypad enter | dialog:select |
|
|
||||||
| ctrl+return | doc:newline-below |
|
|
||||||
| ctrl+shift+return | doc:newline-above |
|
|
||||||
| ctrl+j | doc:join-lines |
|
|
||||||
| ctrl+a | doc:select-all |
|
|
||||||
| ctrl+d | find-replace:select-next |
|
|
||||||
| ctrl+d | doc:select-word |
|
|
||||||
| ctrl+l | doc:select-lines |
|
|
||||||
| ctrl+/ | doc:toggle-line-comments |
|
|
||||||
| ctrl+up | doc:move-lines-up |
|
|
||||||
| ctrl+down | doc:move-lines-down |
|
|
||||||
| ctrl+shift+d | doc:duplicate-lines |
|
|
||||||
| ctrl+shift+k | doc:delete-lines |
|
|
||||||
| left | doc:move-to-previous-char |
|
|
||||||
| left | dialog:previous-entry |
|
|
||||||
| right | doc:move-to-next-char |
|
|
||||||
| right | dialog:next-entry |
|
|
||||||
| up | command:select-previous |
|
|
||||||
| up | doc:move-to-previous-line |
|
|
||||||
| down | command:select-next |
|
|
||||||
| down | doc:move-to-next-line |
|
|
||||||
| ctrl+left | doc:move-to-previous-word-start |
|
|
||||||
| ctrl+right | doc:move-to-next-word-end |
|
|
||||||
| ctrl+[ | doc:move-to-previous-block-start |
|
|
||||||
| ctrl+] | doc:move-to-next-block-end |
|
|
||||||
| home | doc:move-to-start-of-line |
|
|
||||||
| end | doc:move-to-end-of-line |
|
|
||||||
| ctrl+home | doc:move-to-start-of-doc |
|
|
||||||
| ctrl+end | doc:move-to-end-of-doc |
|
|
||||||
| pageup | doc:move-to-previous-page |
|
|
||||||
| pagedown | doc:move-to-next-page |
|
|
||||||
| shift+left | doc:select-to-previous-char |
|
|
||||||
| shift+right | doc:select-to-next-char |
|
|
||||||
| shift+up | doc:select-to-previous-line |
|
|
||||||
| shift+down | doc:select-to-next-line |
|
|
||||||
| ctrl+shift+left | doc:select-to-previous-word-start |
|
|
||||||
| ctrl+shift+right | doc:select-to-next-word-end |
|
|
||||||
| ctrl+shift+[ | doc:select-to-previous-block-start |
|
|
||||||
| ctrl+shift+] | doc:select-to-next-block-end |
|
|
||||||
| shift+home | doc:select-to-start-of-line |
|
|
||||||
| shift+end | doc:select-to-end-of-line |
|
|
||||||
| ctrl+shift+home | doc:select-to-start-of-doc |
|
|
||||||
| ctrl+shift+end | doc:select-to-end-of-doc |
|
|
||||||
| shift+pageup | doc:select-to-previous-page |
|
|
||||||
| shift+pagedown | doc:select-to-next-page |
|
|
209
doc/usage.md
209
doc/usage.md
|
@ -1,209 +0,0 @@
|
||||||
# lite
|
|
||||||
|
|
||||||
![screenshot](https://user-images.githubusercontent.com/3920290/81471642-6c165880-91ea-11ea-8cd1-fae7ae8f0bc4.png)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Lite 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 itself and provide some enhancements
|
|
||||||
while remaining generally compatible with Lite.
|
|
||||||
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
Lite works using a *project directory* — this is the directory where your
|
|
||||||
project's code and other data resides.
|
|
||||||
|
|
||||||
To open lite with 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 lite executable or a running
|
|
||||||
instance of lite.
|
|
||||||
|
|
||||||
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 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
|
|
||||||
`ctrl+shift+p` 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 Module
|
|
||||||
lite 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
|
|
||||||
lite to your personal preference.
|
|
||||||
|
|
||||||
The user module is loaded by lite 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`.
|
|
||||||
|
|
||||||
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 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 by lite 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.
|
|
||||||
|
|
||||||
## Big directories
|
|
||||||
Often projects contain compiled, bundled or downloaded files which you don't want to edit. These files can be excluded from projects by configuring `config.ignore_files`. Such a configuration might look like `config.ignore_files = { "^%.", "node_modules" }`. This will exclude the `node_modules` folder and any file starting with `.`. You can add this to a user or project module.
|
|
||||||
|
|
||||||
If a project has more files than the maximum (configured with `config.max_project_files`) lite-xl will switch to a different mode where files are lazily loaded.
|
|
||||||
|
|
||||||
_Note: Because of lazy loading `core:find-file` will open `core:open-file` instead._
|
|
||||||
|
|
||||||
## 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 in lite are used both through the command finder (`ctrl+shift+p`) and
|
|
||||||
by lite'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:
|
|
||||||
```lua
|
|
||||||
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:
|
|
||||||
```lua
|
|
||||||
local command = require "core.command"
|
|
||||||
command.perform "core:quit"
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Keymap
|
|
||||||
All keyboard shortcuts in lite are handled by the `core.keymap` module. A key
|
|
||||||
binding in lite maps a "stroke" (eg. `ctrl+q`) to one or more commands (eg.
|
|
||||||
`core:quit`). When the shortcut is pressed lite 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:
|
|
||||||
``` lua
|
|
||||||
["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:
|
|
||||||
```lua
|
|
||||||
local keymap = require "core.keymap"
|
|
||||||
keymap.add { ["ctrl+q"] = "core:quit" }
|
|
||||||
```
|
|
||||||
|
|
||||||
A list of default mappings can be viewed [here](./default-keymap.md).
|
|
||||||
|
|
||||||
|
|
||||||
## Plugins
|
|
||||||
Plugins in lite 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 lite's
|
|
||||||
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](https://github.com/rxi/lite-plugins).
|
|
||||||
|
|
||||||
|
|
||||||
## 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 are lua modules which overwrite the color fields of lite'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:
|
|
||||||
```lua
|
|
||||||
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](https://github.com/rxi/lite-colors).
|
|
||||||
They are included with Lite XL release packages.
|
|
||||||
|
|
|
@ -42,9 +42,7 @@ foreach data_module : ['core', 'fonts', 'plugins', 'colors']
|
||||||
install_subdir('data' / data_module , install_dir : lite_datadir)
|
install_subdir('data' / data_module , install_dir : lite_datadir)
|
||||||
endforeach
|
endforeach
|
||||||
|
|
||||||
foreach file : ['usage.md', 'licenses.md', 'contributors.md', 'default-keymap.md']
|
install_data('licenses/licenses.md', install_dir : lite_docdir)
|
||||||
install_data('doc' / file, install_dir : lite_docdir)
|
|
||||||
endforeach
|
|
||||||
|
|
||||||
lite_link_args = []
|
lite_link_args = []
|
||||||
if cc.get_id() == 'gcc' and get_option('buildtype') == 'release'
|
if cc.get_id() == 'gcc' and get_option('buildtype') == 'release'
|
||||||
|
|
|
@ -45,23 +45,27 @@ static int f_pcre_compile(lua_State *L) {
|
||||||
}
|
}
|
||||||
PCRE2_UCHAR buffer[256];
|
PCRE2_UCHAR buffer[256];
|
||||||
pcre2_get_error_message(errorNumber, buffer, sizeof(buffer));
|
pcre2_get_error_message(errorNumber, buffer, sizeof(buffer));
|
||||||
luaL_error(L, "regex compilation failed at offset %d: %s",
|
lua_pushnil(L);
|
||||||
(int)errorOffset, buffer);
|
char message[1024];
|
||||||
return 0;
|
len = snprintf(message, sizeof(message), "regex compilation failed at offset %d: %s", (int)errorOffset, buffer);
|
||||||
|
lua_pushlstring(L, message, len);
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Takes string, compiled regex, returns list of indices of matched groups
|
// Takes string, compiled regex, returns list of indices of matched groups
|
||||||
// (including the whole match), if a match was found.
|
// (including the whole match), if a match was found.
|
||||||
static int f_pcre_match(lua_State *L) {
|
static int f_pcre_match(lua_State *L) {
|
||||||
size_t len, offset = 0;
|
size_t len, offset = 1, opts = 0;
|
||||||
luaL_checktype(L, 1, LUA_TTABLE);
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
const char* str = luaL_checklstring(L, 2, &len);
|
const char* str = luaL_checklstring(L, 2, &len);
|
||||||
if (lua_gettop(L) > 2)
|
if (lua_gettop(L) > 2)
|
||||||
offset = luaL_checknumber(L, 3);
|
offset = luaL_checknumber(L, 3);
|
||||||
|
if (lua_gettop(L) > 3)
|
||||||
|
opts = luaL_checknumber(L, 4);
|
||||||
lua_rawgeti(L, 1, 1);
|
lua_rawgeti(L, 1, 1);
|
||||||
pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1);
|
pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1);
|
||||||
pcre2_match_data* md = pcre2_match_data_create_from_pattern(re, NULL);
|
pcre2_match_data* md = pcre2_match_data_create_from_pattern(re, NULL);
|
||||||
int rc = pcre2_match(re, (PCRE2_SPTR)str, len, offset, 0, md, NULL);
|
int rc = pcre2_match(re, (PCRE2_SPTR)str, len, offset - 1, opts, md, NULL);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
pcre2_match_data_free(md);
|
pcre2_match_data_free(md);
|
||||||
if (rc != PCRE2_ERROR_NOMATCH)
|
if (rc != PCRE2_ERROR_NOMATCH)
|
||||||
|
@ -97,5 +101,17 @@ int luaopen_regex(lua_State *L) {
|
||||||
lua_setfield(L, -2, "__name");
|
lua_setfield(L, -2, "__name");
|
||||||
lua_pushvalue(L, -1);
|
lua_pushvalue(L, -1);
|
||||||
lua_setfield(L, LUA_REGISTRYINDEX, "regex");
|
lua_setfield(L, LUA_REGISTRYINDEX, "regex");
|
||||||
|
lua_pushnumber(L, PCRE2_ANCHORED);
|
||||||
|
lua_setfield(L, -2, "ANCHORED");
|
||||||
|
lua_pushnumber(L, PCRE2_ANCHORED) ;
|
||||||
|
lua_setfield(L, -2, "ENDANCHORED");
|
||||||
|
lua_pushnumber(L, PCRE2_NOTBOL);
|
||||||
|
lua_setfield(L, -2, "NOTBOL");
|
||||||
|
lua_pushnumber(L, PCRE2_NOTEOL);
|
||||||
|
lua_setfield(L, -2, "NOTEOL");
|
||||||
|
lua_pushnumber(L, PCRE2_NOTEMPTY);
|
||||||
|
lua_setfield(L, -2, "NOTEMPTY");
|
||||||
|
lua_pushnumber(L, PCRE2_NOTEMPTY_ATSTART);
|
||||||
|
lua_setfield(L, -2, "NOTEMPTY_ATSTART");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ static int f_set_tab_size(lua_State *L) {
|
||||||
|
|
||||||
static int f_gc(lua_State *L) {
|
static int f_gc(lua_State *L) {
|
||||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||||
font_desc_free(self);
|
font_desc_clear(self);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +104,22 @@ static int f_get_height(lua_State *L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int f_get_size(lua_State *L) {
|
||||||
|
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||||
|
lua_pushnumber(L, self->size);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int f_set_size(lua_State *L) {
|
||||||
|
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||||
|
float new_size = luaL_checknumber(L, 2);
|
||||||
|
font_desc_clear(self);
|
||||||
|
self->size = new_size;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static const luaL_Reg lib[] = {
|
static const luaL_Reg lib[] = {
|
||||||
{ "__gc", f_gc },
|
{ "__gc", f_gc },
|
||||||
{ "load", f_load },
|
{ "load", f_load },
|
||||||
|
@ -112,6 +128,8 @@ static const luaL_Reg lib[] = {
|
||||||
{ "get_width_subpixel", f_get_width_subpixel },
|
{ "get_width_subpixel", f_get_width_subpixel },
|
||||||
{ "get_height", f_get_height },
|
{ "get_height", f_get_height },
|
||||||
{ "subpixel_scale", f_subpixel_scale },
|
{ "subpixel_scale", f_subpixel_scale },
|
||||||
|
{ "get_size", f_get_size },
|
||||||
|
{ "set_size", f_set_size },
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,14 @@ top:
|
||||||
return 4;
|
return 4;
|
||||||
|
|
||||||
case SDL_KEYDOWN:
|
case SDL_KEYDOWN:
|
||||||
|
#ifdef __APPLE__
|
||||||
|
/* on macos 11.2.3 with sdl 2.0.14 the keyup handler for cmd+w below
|
||||||
|
** was not enough. Maybe the quit event started to be triggered from the
|
||||||
|
** keydown handler? In any case, flushing the quit event here too helped. */
|
||||||
|
if ((e.key.keysym.sym == SDLK_w) && (e.key.keysym.mod & KMOD_GUI)) {
|
||||||
|
SDL_FlushEvent(SDL_QUIT);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
lua_pushstring(L, "keypressed");
|
lua_pushstring(L, "keypressed");
|
||||||
lua_pushstring(L, key_name(buf, e.key.keysym.sym));
|
lua_pushstring(L, key_name(buf, e.key.keysym.sym));
|
||||||
return 2;
|
return 2;
|
||||||
|
@ -162,8 +170,7 @@ top:
|
||||||
** we want to flush this event and let the keymapper
|
** we want to flush this event and let the keymapper
|
||||||
** handle this key combination.
|
** handle this key combination.
|
||||||
** Thanks to mathewmariani, taken from his lite-macos github repository. */
|
** Thanks to mathewmariani, taken from his lite-macos github repository. */
|
||||||
if ((e.key.keysym.sym == SDLK_w) && (e.key.keysym.mod & KMOD_GUI))
|
if ((e.key.keysym.sym == SDLK_w) && (e.key.keysym.mod & KMOD_GUI)) {
|
||||||
{
|
|
||||||
SDL_FlushEvent(SDL_QUIT);
|
SDL_FlushEvent(SDL_QUIT);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -4,8 +4,25 @@
|
||||||
void set_macos_bundle_resources(lua_State *L)
|
void set_macos_bundle_resources(lua_State *L)
|
||||||
{ @autoreleasepool
|
{ @autoreleasepool
|
||||||
{
|
{
|
||||||
NSString* resource_path = [[NSBundle mainBundle] resourcePath];
|
/* Use resolved executablePath instead of resourcePath to allow lanching
|
||||||
lua_pushstring(L, [resource_path UTF8String]);
|
the lite-xl binary via a symlink, like typically done by Homebrew:
|
||||||
|
|
||||||
|
/usr/local/bin/lite-xl -> /Applications/lite-xl.app/Contents/MacOS/lite-xl
|
||||||
|
|
||||||
|
The resourcePath returns /usr/local in this case instead of
|
||||||
|
/Applications/lite-xl.app/Contents/Resources, which makes later
|
||||||
|
access to the resource files fail. Resolving the symlink to the
|
||||||
|
executable and then the relative path to the expected directory
|
||||||
|
Resources is a workaround for starting the application from both
|
||||||
|
the launcher directly and the command line via the symlink.
|
||||||
|
*/
|
||||||
|
NSString* executable_path = [[NSBundle mainBundle] executablePath];
|
||||||
|
char resolved_path[PATH_MAX + 16 + 1];
|
||||||
|
realpath([executable_path UTF8String], resolved_path);
|
||||||
|
strcat(resolved_path, "/../../Resources");
|
||||||
|
char resource_path[PATH_MAX + 1];
|
||||||
|
realpath(resolved_path, resource_path);
|
||||||
|
lua_pushstring(L, resource_path);
|
||||||
lua_setglobal(L, "MACOS_RESOURCES");
|
lua_setglobal(L, "MACOS_RESOURCES");
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,12 @@ void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsig
|
||||||
font_desc->cache_last_index = 0; /* Normally no need to initialize. */
|
font_desc->cache_last_index = 0; /* Normally no need to initialize. */
|
||||||
}
|
}
|
||||||
|
|
||||||
void font_desc_free(FontDesc *font_desc) {
|
void font_desc_clear(FontDesc *font_desc) {
|
||||||
for (int i = 0; i < font_desc->cache_length; i++) {
|
for (int i = 0; i < font_desc->cache_length; i++) {
|
||||||
ren_free_font(font_desc->cache[i].font);
|
ren_free_font(font_desc->cache[i].font);
|
||||||
}
|
}
|
||||||
font_desc->cache_length = 0;
|
font_desc->cache_length = 0;
|
||||||
|
font_desc->cache_last_index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size) {
|
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsig
|
||||||
int font_desc_alloc_size(const char *filename);
|
int font_desc_alloc_size(const char *filename);
|
||||||
int font_desc_get_tab_size(FontDesc *font_desc);
|
int font_desc_get_tab_size(FontDesc *font_desc);
|
||||||
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size);
|
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size);
|
||||||
void font_desc_free(FontDesc *font_desc);
|
void font_desc_clear(FontDesc *font_desc);
|
||||||
RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale);
|
RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include "api/api.h"
|
#include "api/api.h"
|
||||||
#include "rencache.h"
|
#include "rencache.h"
|
||||||
|
@ -63,8 +64,13 @@ static void get_exe_filename(char *buf, int sz) {
|
||||||
int len = readlink(path, buf, sz - 1);
|
int len = readlink(path, buf, sz - 1);
|
||||||
buf[len] = '\0';
|
buf[len] = '\0';
|
||||||
#elif __APPLE__
|
#elif __APPLE__
|
||||||
|
/* use realpath to resolve a symlink if the process was launched from one.
|
||||||
|
** This happens when Homebrew installs a cack and creates a symlink in
|
||||||
|
** /usr/loca/bin for launching the executable from the command line. */
|
||||||
unsigned size = sz;
|
unsigned size = sz;
|
||||||
_NSGetExecutablePath(buf, &size);
|
char exepath[size];
|
||||||
|
_NSGetExecutablePath(exepath, &size);
|
||||||
|
realpath(exepath, buf);
|
||||||
#else
|
#else
|
||||||
strcpy(buf, "./lite");
|
strcpy(buf, "./lite");
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue