Merge branch 'master' into dev
This commit is contained in:
commit
a31e683281
158
README.md
158
README.md
|
@ -1,130 +1,76 @@
|
|||
# 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
|
||||
for Windows, Linux and Mac OS (notarized app).
|
||||
* **[Get started](doc/usage.md)** — A quick overview on how to get started
|
||||
* **[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
|
||||
* **[Get Lite XL]** — Download for Windows, Linux and Mac OS (notarized app).
|
||||
* **[Get plugins]** — Add additional functionality, adapted for Lite XL.
|
||||
* **[Get color themes]** — 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.
|
||||
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.
|
||||
The repository with modified plugins is http://github.com/franko/lite-plugins.
|
||||
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.
|
||||
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
|
||||
|
||||
Lite XL is derived from lite. 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.
|
||||
Lite XL is derived from lite.
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
[Lite XL plugins repository](https://github.com/franko/lite-plugins).
|
||||
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] 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.
|
||||
|
||||
## License
|
||||
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.
|
||||
## Licenses
|
||||
|
||||
[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,
|
||||
|
||||
["doc:indent"] = function()
|
||||
indent_text()
|
||||
doc():indent_text(false, doc_multiline_selection(true))
|
||||
end,
|
||||
|
||||
["doc:unindent"] = function()
|
||||
indent_text(true)
|
||||
doc():indent_text(true, doc_multiline_selection(true))
|
||||
end,
|
||||
|
||||
["doc:duplicate-lines"] = function()
|
||||
|
|
|
@ -411,6 +411,65 @@ function Doc:select_to(...)
|
|||
self:set_selection(line, col, line2, col2)
|
||||
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
|
||||
function Doc:on_text_change(type)
|
||||
end
|
||||
|
|
|
@ -33,7 +33,7 @@ function search.find(doc, line, col, text, opt)
|
|||
for line = line, #doc.lines do
|
||||
local line_text = doc.lines[line]
|
||||
if opt.regex then
|
||||
local s, e = re:cmatch(line_text, col, true)
|
||||
local s, e = re:cmatch(line_text, col)
|
||||
if s then
|
||||
return line, s, line, e
|
||||
end
|
||||
|
|
|
@ -1106,6 +1106,11 @@ function core.blink_reset()
|
|||
end
|
||||
|
||||
|
||||
function core.request_cursor(value)
|
||||
core.cursor_change_req = value
|
||||
end
|
||||
|
||||
|
||||
function core.on_error(err)
|
||||
-- write error to file
|
||||
local fp = io.open(USERDIR .. "/error.txt", "wb")
|
||||
|
|
|
@ -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).
|
||||
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
|
||||
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
|
||||
|
||||
-- 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.
|
||||
local function previous_character(str, index)
|
||||
local byte
|
||||
|
@ -32,38 +32,39 @@ end
|
|||
|
||||
-- Build off matching. For now, only support basic replacements, but capture
|
||||
-- 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.
|
||||
-- Should work on UTF-8 text.
|
||||
regex.gsub = function(pattern_string, str, replacement)
|
||||
local pattern = type(pattern_string) == "table" and
|
||||
pattern_string or regex.compile(pattern_string)
|
||||
local result, indices = ""
|
||||
local n = 0
|
||||
local matches, replacements = {}, {}
|
||||
repeat
|
||||
indices = { regex.cmatch(pattern, str) }
|
||||
if #indices > 0 then
|
||||
n = n + 1
|
||||
table.insert(matches, indices)
|
||||
local currentReplacement = replacement
|
||||
if #indices > 2 then
|
||||
for i = 1, (#indices/2 - 1) do
|
||||
currentReplacement = string.gsub(
|
||||
currentReplacement,
|
||||
"\\" .. i,
|
||||
currentReplacement,
|
||||
"\\" .. i,
|
||||
str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))
|
||||
)
|
||||
end
|
||||
end
|
||||
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
|
||||
table.insert(replacements, { indices[1], #currentReplacement+indices[1] })
|
||||
if indices[1] > 1 then
|
||||
result = result ..
|
||||
result = result ..
|
||||
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
|
||||
else
|
||||
result = result .. currentReplacement
|
||||
result = result .. currentReplacement
|
||||
end
|
||||
str = str:sub(indices[2])
|
||||
end
|
||||
until #indices == 0 or indices[1] == indices[2]
|
||||
return result .. str, n
|
||||
return result .. str, matches, replacements
|
||||
end
|
||||
|
||||
|
|
|
@ -783,7 +783,7 @@ end
|
|||
|
||||
function RootView:on_mouse_moved(x, y, dx, dy)
|
||||
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)
|
||||
return
|
||||
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 tab_index = node and node:get_tab_overlapping_point(x, y)
|
||||
if node and node:get_scroll_button_index(x, y) then
|
||||
system.set_cursor("arrow")
|
||||
core.request_cursor("arrow")
|
||||
elseif div then
|
||||
local axis = (div.type == "hsplit" and "x" or "y")
|
||||
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
|
||||
elseif tab_index then
|
||||
system.set_cursor("arrow")
|
||||
core.request_cursor("arrow")
|
||||
if self.dragged_node and self.dragged_node ~= tab_index then
|
||||
local tab = 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
|
||||
end
|
||||
else
|
||||
system.set_cursor(node.active_view.cursor)
|
||||
core.request_cursor(node.active_view.cursor)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -858,6 +858,10 @@ function RootView:draw()
|
|||
local t = table.remove(self.deferred_draws)
|
||||
t.fn(table.unpack(t))
|
||||
end
|
||||
if core.cursor_change_req then
|
||||
system.set_cursor(core.cursor_change_req)
|
||||
core.cursor_change_req = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -48,29 +48,6 @@ local function push_tokens(t, syn, pattern, full_text, find_results)
|
|||
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
|
||||
-- differnet delimiters we have open, and which subsyntaxes we have active.
|
||||
-- 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)
|
||||
current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||
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
|
||||
|
||||
while i <= #text do
|
||||
-- continue trying to match the end pattern of a pair if we have a state set
|
||||
if current_pattern_idx > 0 then
|
||||
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
|
||||
-- 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
|
||||
-- precedence over ending the delimiter in the subsyntax.
|
||||
if subsyntax_info then
|
||||
local ss, se = find_non_escaped(
|
||||
text,
|
||||
subsyntax_info.pattern[2],
|
||||
i,
|
||||
subsyntax_info.pattern[3]
|
||||
)
|
||||
local ss, se = find_text(text, subsyntax_info, i, false, true)
|
||||
-- If we find that we end the subsyntax before the
|
||||
-- delimiter, push the token, and signal we shouldn't
|
||||
-- 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
|
||||
-- just normally, upon finding a token.
|
||||
if subsyntax_info then
|
||||
local s, e = find_non_escaped(
|
||||
text,
|
||||
"^" .. subsyntax_info.pattern[2],
|
||||
i,
|
||||
nil
|
||||
)
|
||||
local s, e = find_text(text, subsyntax_info, i, true, true)
|
||||
if s then
|
||||
push_token(res, subsyntax_info.type, text:sub(i, e))
|
||||
-- On finding unescaped delimiter, pop it.
|
||||
|
@ -219,16 +209,12 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
|||
-- find matching pattern
|
||||
local matched = false
|
||||
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 = { text:find("^" .. pattern, i) }
|
||||
local start, fin = find_results[1], find_results[2]
|
||||
|
||||
if start then
|
||||
local find_results = { find_text(text, p, i, true, false) }
|
||||
if find_results[1] then
|
||||
-- matched pattern; make and add tokens
|
||||
push_tokens(res, current_syntax, p, text, find_results)
|
||||
|
||||
-- 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 p.syntax then
|
||||
push_subsyntax(p, n)
|
||||
|
@ -236,9 +222,8 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
|||
set_subsyntax_pattern_idx(n)
|
||||
end
|
||||
end
|
||||
|
||||
-- move cursor past this token
|
||||
i = fin + 1
|
||||
i = find_results[2] + 1
|
||||
matched = true
|
||||
break
|
||||
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 common = require "core.common"
|
||||
local DocView = require "core.docview"
|
||||
local LogView = require "core.logview"
|
||||
|
||||
|
||||
local function workspace_files_for(project_dir)
|
||||
|
@ -29,7 +30,7 @@ local function workspace_files_for(project_dir)
|
|||
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
|
||||
local load_f = loadfile(filename)
|
||||
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)
|
||||
}
|
||||
end
|
||||
if mt == LogView then return end
|
||||
for name, mod in pairs(package.loaded) do
|
||||
if mod == mt then
|
||||
return {
|
||||
|
@ -99,16 +101,26 @@ end
|
|||
|
||||
local function load_view(t)
|
||||
if t.type == "doc" then
|
||||
local ok, doc = pcall(core.open_doc, t.filename)
|
||||
if not ok then
|
||||
return DocView(core.open_doc())
|
||||
local dv
|
||||
if not t.filename then
|
||||
-- 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
|
||||
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
|
||||
end
|
||||
return require(t.module)()
|
||||
|
@ -141,13 +153,19 @@ end
|
|||
local function load_node(node, t)
|
||||
if t.type == "leaf" then
|
||||
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)
|
||||
if v.active then res = view end
|
||||
node:add_view(view)
|
||||
if view then
|
||||
if v.active then res = view end
|
||||
node:add_view(view)
|
||||
if t.active_view == i then
|
||||
active_view = view
|
||||
end
|
||||
end
|
||||
end
|
||||
if t.active_view then
|
||||
node:set_active_view(node.views[t.active_view])
|
||||
if active_view then
|
||||
node:set_active_view(active_view)
|
||||
end
|
||||
return res
|
||||
else
|
||||
|
@ -184,7 +202,7 @@ end
|
|||
|
||||
|
||||
local function load_workspace()
|
||||
local workspace = load_workspace_file(core.project_dir)
|
||||
local workspace = consume_workspace_file(core.project_dir)
|
||||
if workspace then
|
||||
local root = get_unlocked_root(core.root_view.root_node)
|
||||
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)
|
||||
endforeach
|
||||
|
||||
foreach file : ['usage.md', 'licenses.md', 'contributors.md', 'default-keymap.md']
|
||||
install_data('doc' / file, install_dir : lite_docdir)
|
||||
endforeach
|
||||
install_data('licenses/licenses.md', install_dir : lite_docdir)
|
||||
|
||||
lite_link_args = []
|
||||
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_get_error_message(errorNumber, buffer, sizeof(buffer));
|
||||
luaL_error(L, "regex compilation failed at offset %d: %s",
|
||||
(int)errorOffset, buffer);
|
||||
return 0;
|
||||
lua_pushnil(L);
|
||||
char message[1024];
|
||||
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
|
||||
// (including the whole match), if a match was found.
|
||||
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);
|
||||
const char* str = luaL_checklstring(L, 2, &len);
|
||||
if (lua_gettop(L) > 2)
|
||||
offset = luaL_checknumber(L, 3);
|
||||
if (lua_gettop(L) > 3)
|
||||
opts = luaL_checknumber(L, 4);
|
||||
lua_rawgeti(L, 1, 1);
|
||||
pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1);
|
||||
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) {
|
||||
pcre2_match_data_free(md);
|
||||
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);
|
||||
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,
|
||||
we just detect this case and give up. */
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
@ -97,5 +101,17 @@ int luaopen_regex(lua_State *L) {
|
|||
lua_setfield(L, -2, "__name");
|
||||
lua_pushvalue(L, -1);
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ static int f_set_tab_size(lua_State *L) {
|
|||
|
||||
static int f_gc(lua_State *L) {
|
||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||
font_desc_free(self);
|
||||
font_desc_clear(self);
|
||||
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[] = {
|
||||
{ "__gc", f_gc },
|
||||
{ "load", f_load },
|
||||
|
@ -112,6 +128,8 @@ static const luaL_Reg lib[] = {
|
|||
{ "get_width_subpixel", f_get_width_subpixel },
|
||||
{ "get_height", f_get_height },
|
||||
{ "subpixel_scale", f_subpixel_scale },
|
||||
{ "get_size", f_get_size },
|
||||
{ "set_size", f_set_size },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
|
|
@ -152,6 +152,14 @@ top:
|
|||
return 4;
|
||||
|
||||
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, key_name(buf, e.key.keysym.sym));
|
||||
return 2;
|
||||
|
@ -162,8 +170,7 @@ top:
|
|||
** we want to flush this event and let the keymapper
|
||||
** handle this key combination.
|
||||
** 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);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -4,8 +4,25 @@
|
|||
void set_macos_bundle_resources(lua_State *L)
|
||||
{ @autoreleasepool
|
||||
{
|
||||
NSString* resource_path = [[NSBundle mainBundle] resourcePath];
|
||||
lua_pushstring(L, [resource_path UTF8String]);
|
||||
/* Use resolved executablePath instead of resourcePath to allow lanching
|
||||
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");
|
||||
}}
|
||||
|
||||
|
|
|
@ -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. */
|
||||
}
|
||||
|
||||
void font_desc_free(FontDesc *font_desc) {
|
||||
void font_desc_clear(FontDesc *font_desc) {
|
||||
for (int i = 0; i < font_desc->cache_length; i++) {
|
||||
ren_free_font(font_desc->cache[i].font);
|
||||
}
|
||||
font_desc->cache_length = 0;
|
||||
font_desc->cache_last_index = 0;
|
||||
}
|
||||
|
||||
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_get_tab_size(FontDesc *font_desc);
|
||||
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);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL.h>
|
||||
#include "api/api.h"
|
||||
#include "rencache.h"
|
||||
|
@ -63,8 +64,13 @@ static void get_exe_filename(char *buf, int sz) {
|
|||
int len = readlink(path, buf, sz - 1);
|
||||
buf[len] = '\0';
|
||||
#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;
|
||||
_NSGetExecutablePath(buf, &size);
|
||||
char exepath[size];
|
||||
_NSGetExecutablePath(exepath, &size);
|
||||
realpath(exepath, buf);
|
||||
#else
|
||||
strcpy(buf, "./lite");
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue