Merge branch 'master' into dev

This commit is contained in:
jgmdev 2021-06-06 01:37:01 -04:00
commit a31e683281
23 changed files with 676 additions and 571 deletions

158
README.md
View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -1,15 +1,15 @@
-- So that in addition to regex.gsub(pattern, string), we can also do -- So that in addition to regex.gsub(pattern, string), we can also do
-- 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
-- mid character. -- mid character.
local function previous_character(str, index) local function previous_character(str, index)
local byte local byte
@ -32,38 +32,39 @@ end
-- Build off matching. For now, only support basic replacements, but capture -- Build off matching. For now, only support basic replacements, but capture
-- groupings should be doable. We can even have custom group replacements and -- groupings should be doable. We can even have custom group replacements and
-- transformations and stuff in lua. Currently, this takes group replacements -- transformations and stuff in lua. Currently, this takes group replacements
-- as \1 - \9. -- as \1 - \9.
-- Should work on UTF-8 text. -- Should work on UTF-8 text.
regex.gsub = function(pattern_string, str, replacement) 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
currentReplacement = string.gsub( currentReplacement = string.gsub(
currentReplacement, currentReplacement,
"\\" .. i, "\\" .. i,
str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1)) str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))
) )
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
else else
result = result .. currentReplacement result = result .. currentReplacement
end end
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

View File

@ -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

View File

@ -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

View File

@ -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

100
data/plugins/scale.lua Normal file
View File

@ -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",
}

View File

@ -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)

View File

@ -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

View File

@ -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 |

View File

@ -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.

View File

@ -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'

View File

@ -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)
@ -70,11 +74,11 @@ static int f_pcre_match(lua_State *L) {
} }
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(md); PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(md);
if (ovector[0] > ovector[1]) { if (ovector[0] > ovector[1]) {
/* We must guard against patterns such as /(?=.\K)/ that use \K in an /* We must guard against patterns such as /(?=.\K)/ that use \K in an
assertion to set the start of a match later than its end. In the editor, assertion to set the start of a match later than its end. In the editor,
we just detect this case and give up. */ we just detect this case and give up. */
luaL_error(L, "regex matching error: \\K was used in an assertion to " luaL_error(L, "regex matching error: \\K was used in an assertion to "
" set the match start after its end"); " set the match start after its end");
pcre2_match_data_free(md); pcre2_match_data_free(md);
return 0; return 0;
} }
@ -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;
} }

View File

@ -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 }
}; };

View File

@ -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

View File

@ -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");
}} }}

View File

@ -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) {

View File

@ -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

View File

@ -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