Merge branch 'lite-xl:master' into master
This commit is contained in:
commit
eed5b79030
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2020 rxi
|
Copyright (c) 2020-2021 Francesco Abbate
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|
156
README.md
156
README.md
|
@ -1,126 +1,76 @@
|
||||||
# Lite XL
|
# Lite XL
|
||||||
|
|
||||||
![screenshot-dark](https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png)
|
[![Discord Badge Image]](https://discord.gg/RWzqC3nx7K)
|
||||||
|
|
||||||
A lightweight text editor written in Lua, adapted from [lite](https://github.com/rxi/lite)
|
![screenshot-dark]
|
||||||
|
|
||||||
* **[Get Lite XL](https://github.com/franko/lite-xl/releases/latest)** — Download
|
A lightweight text editor written in Lua, adapted from [lite].
|
||||||
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
|
|
||||||
|
|
||||||
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.
|
* **[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.
|
||||||
|
|
||||||
|
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.
|
|
||||||
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
|
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.
|
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
|
||||||
|
|
2
build.sh
2
build.sh
|
@ -3,7 +3,7 @@
|
||||||
cflags+="-Wall -O3 -g -std=gnu11 -fno-strict-aliasing -Isrc -Ilib/font_renderer"
|
cflags+="-Wall -O3 -g -std=gnu11 -fno-strict-aliasing -Isrc -Ilib/font_renderer"
|
||||||
cflags+=" $(pkg-config --cflags lua5.2) $(sdl2-config --cflags)"
|
cflags+=" $(pkg-config --cflags lua5.2) $(sdl2-config --cflags)"
|
||||||
lflags="-static-libgcc -static-libstdc++"
|
lflags="-static-libgcc -static-libstdc++"
|
||||||
for package in libagg freetype2 lua5.2 x11; do
|
for package in libagg freetype2 lua5.2 x11 libpcre2-8; do
|
||||||
lflags+=" $(pkg-config --libs $package)"
|
lflags+=" $(pkg-config --libs $package)"
|
||||||
done
|
done
|
||||||
lflags+=" $(sdl2-config --libs) -lm"
|
lflags+=" $(sdl2-config --libs) -lm"
|
||||||
|
|
44
changelog.md
44
changelog.md
|
@ -1,7 +1,47 @@
|
||||||
Lite XL is following closely [rxi/lite](https://github.com/rxi/lite) but with some enhancements.
|
|
||||||
|
|
||||||
This files document the changes done in Lite XL for each release.
|
This files document the changes done in Lite XL for each release.
|
||||||
|
|
||||||
|
### 1.16.11
|
||||||
|
|
||||||
|
When opening directories with too many files lite-xl now keep diplaying files and directories in the treeview.
|
||||||
|
The application remains functional and the directories can be explored without using too much memory.
|
||||||
|
In this operating mode the files of the project are not indexed so the command "Core: Find File" will act as the "Core: Open File" command.
|
||||||
|
The "Project Search: Find" will work by searching all the files present in the project directory even if they are not indexed.
|
||||||
|
|
||||||
|
Implemented changing fonts per syntax group by @liquidev.
|
||||||
|
|
||||||
|
Example user module snippet that makes all comments italic:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local style = require "core.style"
|
||||||
|
|
||||||
|
-- italic.ttf must be provided by the user
|
||||||
|
local italic = renderer.font.load("italic.ttf", 14)
|
||||||
|
style.syntax_fonts["comment"] = italic
|
||||||
|
```
|
||||||
|
|
||||||
|
Improved indentation behavior by @adamharrison.
|
||||||
|
|
||||||
|
Fix bug with close button not working in borderless window mode.
|
||||||
|
|
||||||
|
Fix problem with normalization of filename for opened documents.
|
||||||
|
|
||||||
|
### 1.16.10
|
||||||
|
|
||||||
|
Improved syntax highlight system thanks to @liquidev and @adamharrison.
|
||||||
|
Thanks to the new system we provide more a accurate syntax highlighting for Lua, C and C++.
|
||||||
|
Other syntax improvements contributed by @vincens2005.
|
||||||
|
|
||||||
|
Move to JetBrains Mono and Fira Sans fonts for code and UI respectively.
|
||||||
|
Thet are provided under the SIL Open Font License, Version 1.1.
|
||||||
|
See `doc/licenses.md` for license details.
|
||||||
|
|
||||||
|
Fixed bug with fonts and rencache module.
|
||||||
|
Under very specific situations the application was crashing due to invalid memory access.
|
||||||
|
|
||||||
|
Add documentation for keymap binding, thanks to @Janis-Leuenberger.
|
||||||
|
|
||||||
|
Added a contributors page in `doc/contributors.md`.
|
||||||
|
|
||||||
### 1.16.9
|
### 1.16.9
|
||||||
|
|
||||||
Fix a bug related to nested panes resizing.
|
Fix a bug related to nested panes resizing.
|
||||||
|
|
|
@ -59,7 +59,10 @@ end
|
||||||
|
|
||||||
|
|
||||||
function command.add_defaults()
|
function command.add_defaults()
|
||||||
local reg = { "core", "root", "command", "doc", "findreplace", "files", "drawwhitespace" }
|
local reg = {
|
||||||
|
"core", "root", "command", "doc", "findreplace",
|
||||||
|
"files", "drawwhitespace", "dialog"
|
||||||
|
}
|
||||||
for _, name in ipairs(reg) do
|
for _, name in ipairs(reg) do
|
||||||
require("core.commands." .. name)
|
require("core.commands." .. name)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local CommandView = require "core.commandview"
|
|
||||||
|
|
||||||
local function has_commandview()
|
command.add("core.commandview", {
|
||||||
return core.active_view:is(CommandView)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
command.add(has_commandview, {
|
|
||||||
["command:submit"] = function()
|
["command:submit"] = function()
|
||||||
core.active_view:submit()
|
core.active_view:submit()
|
||||||
end,
|
end,
|
||||||
|
|
|
@ -66,6 +66,9 @@ command.add(nil, {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:find-file"] = function()
|
["core:find-file"] = function()
|
||||||
|
if core.project_files_limit then
|
||||||
|
return command.perform "core:open-file"
|
||||||
|
end
|
||||||
local files = {}
|
local files = {}
|
||||||
for dir, item in core.get_project_files() do
|
for dir, item in core.get_project_files() do
|
||||||
if item.type == "file" then
|
if item.type == "file" then
|
||||||
|
@ -88,10 +91,16 @@ command.add(nil, {
|
||||||
["core:open-file"] = function()
|
["core:open-file"] = function()
|
||||||
local view = core.active_view
|
local view = core.active_view
|
||||||
if view.doc and view.doc.abs_filename then
|
if view.doc and view.doc.abs_filename then
|
||||||
core.command_view:set_text(common.home_encode(view.doc.abs_filename))
|
local dirname, filename = view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
||||||
|
if dirname then
|
||||||
|
dirname = core.normalize_to_project_dir(dirname)
|
||||||
|
local text = dirname == core.project_dir and "" or common.home_encode(dirname) .. PATHSEP
|
||||||
|
core.command_view:set_text(text)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
core.command_view:enter("Open File", function(text)
|
core.command_view:enter("Open File", function(text)
|
||||||
core.root_view:open_doc(core.open_doc(common.home_expand(text)))
|
local filename = system.absolute_path(common.home_expand(text))
|
||||||
|
core.root_view:open_doc(core.open_doc(filename))
|
||||||
end, function (text)
|
end, function (text)
|
||||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||||
end, nil, function(text)
|
end, nil, function(text)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
local core = require "core"
|
||||||
|
local command = require "core.command"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
command.add("core.nagview", {
|
||||||
|
["dialog:previous-entry"] = function()
|
||||||
|
local v = core.active_view
|
||||||
|
local hover = v.hovered_item or 1
|
||||||
|
v:change_hovered(hover == 1 and #v.options or hover - 1)
|
||||||
|
end,
|
||||||
|
["dialog:next-entry"] = function()
|
||||||
|
local v = core.active_view
|
||||||
|
local hover = v.hovered_item or 1
|
||||||
|
v:change_hovered(hover == #v.options and 1 or hover + 1)
|
||||||
|
end,
|
||||||
|
["dialog:select-yes"] = function()
|
||||||
|
local v = core.active_view
|
||||||
|
if v ~= core.nag_view then return end
|
||||||
|
v:change_hovered(common.find_index(v.options, "default_yes"))
|
||||||
|
command.perform "dialog:select"
|
||||||
|
end,
|
||||||
|
["dialog:select-no"] = function()
|
||||||
|
local v = core.active_view
|
||||||
|
if v ~= core.nag_view then return end
|
||||||
|
v:change_hovered(common.find_index(v.options, "default_no"))
|
||||||
|
command.perform "dialog:select"
|
||||||
|
end,
|
||||||
|
["dialog:select"] = function()
|
||||||
|
local v = core.active_view
|
||||||
|
if v.hovered_item then
|
||||||
|
v.on_selected(v.options[v.hovered_item])
|
||||||
|
v:next()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
|
@ -33,33 +33,6 @@ local function doc_multiline_selection(sort)
|
||||||
return line1, col1, line2, col2, swap
|
return line1, col1, line2, col2, swap
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function insert_at_start_of_selected_lines(text, skip_empty)
|
|
||||||
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
|
|
||||||
for line = line1, line2 do
|
|
||||||
local line_text = doc().lines[line]
|
|
||||||
if (not skip_empty or line_text:find("%S")) then
|
|
||||||
doc():insert(line, 1, text)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
doc():set_selection(line1, col1 + #text, line2, col2 + #text, swap)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function remove_from_start_of_selected_lines(text, skip_empty)
|
|
||||||
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
|
|
||||||
for line = line1, line2 do
|
|
||||||
local line_text = doc().lines[line]
|
|
||||||
if line_text:sub(1, #text) == text
|
|
||||||
and (not skip_empty or line_text:find("%S"))
|
|
||||||
then
|
|
||||||
doc():remove(line, 1, line, #text + 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
doc():set_selection(line1, col1 - #text, line2, col2 - #text, swap)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function append_line_if_last_line(line)
|
local function append_line_if_last_line(line)
|
||||||
if line >= #doc().lines then
|
if line >= #doc().lines then
|
||||||
doc():insert(line, math.huge, "\n")
|
doc():insert(line, math.huge, "\n")
|
||||||
|
@ -74,7 +47,6 @@ local function save(filename)
|
||||||
core.log("Saved \"%s\"", saved_filename)
|
core.log("Saved \"%s\"", saved_filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local commands = {
|
local commands = {
|
||||||
["doc:undo"] = function()
|
["doc:undo"] = function()
|
||||||
doc():undo()
|
doc():undo()
|
||||||
|
@ -183,17 +155,11 @@ local commands = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:indent"] = function()
|
["doc:indent"] = function()
|
||||||
local text = get_indent_string()
|
doc():indent_text(false, doc_multiline_selection(true))
|
||||||
if doc():has_selection() then
|
|
||||||
insert_at_start_of_selected_lines(text)
|
|
||||||
else
|
|
||||||
doc():text_input(text)
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:unindent"] = function()
|
["doc:unindent"] = function()
|
||||||
local text = get_indent_string()
|
doc():indent_text(true, doc_multiline_selection(true))
|
||||||
remove_from_start_of_selected_lines(text)
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:duplicate-lines"] = function()
|
["doc:duplicate-lines"] = function()
|
||||||
|
@ -237,19 +203,31 @@ local commands = {
|
||||||
["doc:toggle-line-comments"] = function()
|
["doc:toggle-line-comments"] = function()
|
||||||
local comment = doc().syntax.comment
|
local comment = doc().syntax.comment
|
||||||
if not comment then return end
|
if not comment then return end
|
||||||
|
local indentation = get_indent_string()
|
||||||
local comment_text = comment .. " "
|
local comment_text = comment .. " "
|
||||||
local line1, _, line2 = doc():get_selection(true)
|
local line1, _, line2 = doc_multiline_selection(true)
|
||||||
local uncomment = true
|
local uncomment = true
|
||||||
|
local start_offset = math.huge
|
||||||
for line = line1, line2 do
|
for line = line1, line2 do
|
||||||
local text = doc().lines[line]
|
local text = doc().lines[line]
|
||||||
if text:find("%S") and text:find(comment_text, 1, true) ~= 1 then
|
local s = text:find("%S")
|
||||||
|
local cs, ce = text:find(comment_text, s, true)
|
||||||
|
if s and cs ~= s then
|
||||||
uncomment = false
|
uncomment = false
|
||||||
|
start_offset = math.min(start_offset, s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
for line = line1, line2 do
|
||||||
|
local text = doc().lines[line]
|
||||||
|
local s = text:find("%S")
|
||||||
if uncomment then
|
if uncomment then
|
||||||
remove_from_start_of_selected_lines(comment_text, true)
|
local cs, ce = text:find(comment_text, s, true)
|
||||||
else
|
if ce then
|
||||||
insert_at_start_of_selected_lines(comment_text, true)
|
doc():remove(line, cs, line, ce + 1)
|
||||||
|
end
|
||||||
|
elseif s then
|
||||||
|
doc():insert(line, start_offset, comment_text)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -297,8 +275,12 @@ local commands = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:save-as"] = function()
|
["doc:save-as"] = function()
|
||||||
|
local last_doc = core.last_active_view and core.last_active_view.doc
|
||||||
if doc().filename then
|
if doc().filename then
|
||||||
core.command_view:set_text(doc().filename)
|
core.command_view:set_text(doc().filename)
|
||||||
|
elseif last_doc and last_doc.filename then
|
||||||
|
local dirname, filename = core.last_active_view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
||||||
|
core.command_view:set_text(core.normalize_to_project_dir(dirname) .. PATHSEP)
|
||||||
end
|
end
|
||||||
core.command_view:enter("Save As", function(filename)
|
core.command_view:enter("Save As", function(filename)
|
||||||
save(common.home_expand(filename))
|
save(common.home_expand(filename))
|
||||||
|
@ -315,7 +297,7 @@ local commands = {
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:rename"] = function()
|
["file:rename"] = function()
|
||||||
local old_filename = doc().filename
|
local old_filename = doc().filename
|
||||||
if not old_filename then
|
if not old_filename then
|
||||||
core.error("Cannot rename unsaved doc")
|
core.error("Cannot rename unsaved doc")
|
||||||
|
@ -330,6 +312,21 @@ local commands = {
|
||||||
end
|
end
|
||||||
end, common.path_suggest)
|
end, common.path_suggest)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
|
||||||
|
["file:delete"] = function()
|
||||||
|
local filename = doc().abs_filename
|
||||||
|
if not filename then
|
||||||
|
core.error("Cannot remove unsaved doc")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for i,docview in ipairs(core.get_views_referencing_doc(doc())) do
|
||||||
|
local node = core.root_view.root_node:get_node_for_view(docview)
|
||||||
|
node:close_view(core.root_view, docview)
|
||||||
|
end
|
||||||
|
os.remove(filename)
|
||||||
|
core.log("Removed \"%s\"", filename)
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
command.add(nil, {
|
command.add(nil, {
|
||||||
["files:create-directory"] = function()
|
["files:create-directory"] = function()
|
||||||
core.command_view:enter("New directory name", function(text)
|
core.command_view:enter("New directory name", function(text)
|
||||||
local success, err = system.mkdir(text)
|
local success, err, path = common.mkdirp(text)
|
||||||
if not success then
|
if not success then
|
||||||
core.error("cannot create directory %q: %s", text, err)
|
core.error("cannot create directory %q: %s", path, err)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
|
@ -90,6 +90,7 @@ local function has_selection()
|
||||||
and core.active_view.doc:has_selection()
|
and core.active_view.doc:has_selection()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
command.add(has_selection, {
|
command.add(has_selection, {
|
||||||
["find-replace:select-next"] = function()
|
["find-replace:select-next"] = function()
|
||||||
local l1, c1, l2, c2 = doc():get_selection(true)
|
local l1, c1, l2, c2 = doc():get_selection(true)
|
||||||
|
@ -107,9 +108,9 @@ command.add("core.docview", {
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["find-replace:find-pattern"] = function()
|
["find-replace:find-regex"] = function()
|
||||||
find("Find Text Pattern", function(doc, line, col, text)
|
find("Find Text Regex", function(doc, line, col, text)
|
||||||
local opt = { wrap = true, no_case = true, pattern = true }
|
local opt = { wrap = true, no_case = true, regex = true }
|
||||||
return search.find(doc, line, col, text, opt)
|
return search.find(doc, line, col, text, opt)
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
@ -144,9 +145,10 @@ command.add("core.docview", {
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["find-replace:replace-pattern"] = function()
|
["find-replace:replace-regex"] = function()
|
||||||
replace("Pattern", "", function(text, old, new)
|
replace("Regex", "", function(text, old, new)
|
||||||
return text:gsub(old, new)
|
local re = regex.compile(old)
|
||||||
|
return regex.gsub(re, text, new)
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,13 @@ function common.round(n)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function common.find_index(tbl, prop)
|
||||||
|
for i, o in ipairs(tbl) do
|
||||||
|
if o[prop] then return i end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.lerp(a, b, t)
|
function common.lerp(a, b, t)
|
||||||
if type(a) ~= "table" then
|
if type(a) ~= "table" then
|
||||||
return a + (b - a) * t
|
return a + (b - a) * t
|
||||||
|
@ -206,7 +213,9 @@ end
|
||||||
function common.home_encode(text)
|
function common.home_encode(text)
|
||||||
if HOME and string.find(text, HOME, 1, true) == 1 then
|
if HOME and string.find(text, HOME, 1, true) == 1 then
|
||||||
local dir_pos = #HOME + 1
|
local dir_pos = #HOME + 1
|
||||||
if string.find(text, PATHSEP, dir_pos, true) == dir_pos then
|
-- ensure we don't replace if the text is just "$HOME" or "$HOME/" so
|
||||||
|
-- it must have a "/" following the $HOME and some characters following.
|
||||||
|
if string.find(text, PATHSEP, dir_pos, true) == dir_pos and #text > dir_pos then
|
||||||
return "~" .. text:sub(dir_pos)
|
return "~" .. text:sub(dir_pos)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -271,4 +280,26 @@ function common.relative_path(ref_dir, dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function common.mkdirp(path)
|
||||||
|
local stat = system.get_file_info(path)
|
||||||
|
if stat and stat.type then
|
||||||
|
return false, "path exists", path
|
||||||
|
end
|
||||||
|
local subdirs = {}
|
||||||
|
while path and path ~= "" do
|
||||||
|
local success_mkdir = system.mkdir(path)
|
||||||
|
if success_mkdir then break end
|
||||||
|
local updir, basedir = path:match("(.*)[/\\](.+)$")
|
||||||
|
table.insert(subdirs, 1, basedir or path)
|
||||||
|
path = updir
|
||||||
|
end
|
||||||
|
for _, dirname in ipairs(subdirs) do
|
||||||
|
path = path and path .. PATHSEP .. dirname or dirname
|
||||||
|
if not system.mkdir(path) then
|
||||||
|
return false, "cannot create directory", path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
return common
|
return common
|
||||||
|
|
|
@ -3,7 +3,7 @@ local config = {}
|
||||||
config.project_scan_rate = 5
|
config.project_scan_rate = 5
|
||||||
config.fps = 60
|
config.fps = 60
|
||||||
config.max_log_items = 80
|
config.max_log_items = 80
|
||||||
config.message_timeout = 3
|
config.message_timeout = 5
|
||||||
config.mouse_wheel_scroll = 50 * SCALE
|
config.mouse_wheel_scroll = 50 * SCALE
|
||||||
config.file_size_limit = 10
|
config.file_size_limit = 10
|
||||||
config.ignore_files = "^%."
|
config.ignore_files = "^%."
|
||||||
|
@ -11,6 +11,7 @@ config.symbol_pattern = "[%a_][%w_]*"
|
||||||
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||||
config.undo_merge_timeout = 0.3
|
config.undo_merge_timeout = 0.3
|
||||||
config.max_undos = 10000
|
config.max_undos = 10000
|
||||||
|
config.max_tabs = 10
|
||||||
config.highlight_current_line = true
|
config.highlight_current_line = true
|
||||||
config.line_height = 1.2
|
config.line_height = 1.2
|
||||||
config.indent_size = 2
|
config.indent_size = 2
|
||||||
|
|
|
@ -405,6 +405,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
|
||||||
|
|
|
@ -15,13 +15,9 @@ local function init_args(doc, line, col, text, opt)
|
||||||
opt = opt or default_opt
|
opt = opt or default_opt
|
||||||
line, col = doc:sanitize_position(line, col)
|
line, col = doc:sanitize_position(line, col)
|
||||||
|
|
||||||
if opt.no_case then
|
if opt.no_case and not opt.regex then
|
||||||
if opt.pattern then
|
|
||||||
text = text:gsub("%%?.", pattern_lower)
|
|
||||||
else
|
|
||||||
text = text:lower()
|
text = text:lower()
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
return doc, line, col, text, opt
|
return doc, line, col, text, opt
|
||||||
end
|
end
|
||||||
|
@ -30,20 +26,32 @@ end
|
||||||
function search.find(doc, line, col, text, opt)
|
function search.find(doc, line, col, text, opt)
|
||||||
doc, line, col, text, opt = init_args(doc, line, col, text, opt)
|
doc, line, col, text, opt = init_args(doc, line, col, text, opt)
|
||||||
|
|
||||||
|
local re
|
||||||
|
if opt.regex then
|
||||||
|
re = regex.compile(text, opt.no_case and "i" or "")
|
||||||
|
end
|
||||||
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
|
||||||
|
local s, e = re:cmatch(line_text, col)
|
||||||
|
if s then
|
||||||
|
return line, s, line, e
|
||||||
|
end
|
||||||
|
col = 1
|
||||||
|
else
|
||||||
if opt.no_case then
|
if opt.no_case then
|
||||||
line_text = line_text:lower()
|
line_text = line_text:lower()
|
||||||
end
|
end
|
||||||
local s, e = line_text:find(text, col, not opt.pattern)
|
local s, e = line_text:find(text, col, true)
|
||||||
if s then
|
if s then
|
||||||
return line, s, line, e + 1
|
return line, s, line, e + 1
|
||||||
end
|
end
|
||||||
col = 1
|
col = 1
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if opt.wrap then
|
if opt.wrap then
|
||||||
opt = { no_case = opt.no_case, pattern = opt.pattern }
|
opt = { no_case = opt.no_case, regex = opt.regex }
|
||||||
return search.find(doc, 1, 1, text, opt)
|
return search.find(doc, 1, 1, text, opt)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -141,20 +141,35 @@ end
|
||||||
|
|
||||||
|
|
||||||
function DocView:get_col_x_offset(line, col)
|
function DocView:get_col_x_offset(line, col)
|
||||||
local text = self.doc.lines[line]
|
local default_font = self:get_font()
|
||||||
if not text then return 0 end
|
local column = 1
|
||||||
return self:get_font():get_width(text:sub(1, col - 1))
|
local xoffset = 0
|
||||||
|
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||||
|
local font = style.syntax_fonts[type] or default_font
|
||||||
|
for char in common.utf8_chars(text) do
|
||||||
|
if column == col then
|
||||||
|
return xoffset / font:subpixel_scale()
|
||||||
|
end
|
||||||
|
xoffset = xoffset + font:get_width_subpixel(char)
|
||||||
|
column = column + #char
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return xoffset / default_font:subpixel_scale()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:get_x_offset_col(line, x)
|
function DocView:get_x_offset_col(line, x)
|
||||||
local text = self.doc.lines[line]
|
local line_text = self.doc.lines[line]
|
||||||
|
|
||||||
local xoffset, last_i, i = 0, 1, 1
|
local xoffset, last_i, i = 0, 1, 1
|
||||||
local subpixel_scale = self:get_font():subpixel_scale();
|
local default_font = self:get_font()
|
||||||
|
local subpixel_scale = default_font:subpixel_scale()
|
||||||
local x_subpixel = subpixel_scale * x + subpixel_scale / 2
|
local x_subpixel = subpixel_scale * x + subpixel_scale / 2
|
||||||
|
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||||
|
local font = style.syntax_fonts[type] or default_font
|
||||||
for char in common.utf8_chars(text) do
|
for char in common.utf8_chars(text) do
|
||||||
local w = self:get_font():get_width_subpixel(char)
|
local w = font:get_width_subpixel(char)
|
||||||
if xoffset >= subpixel_scale * x then
|
if xoffset >= subpixel_scale * x then
|
||||||
return (xoffset - x_subpixel > w / 2) and last_i or i
|
return (xoffset - x_subpixel > w / 2) and last_i or i
|
||||||
end
|
end
|
||||||
|
@ -162,8 +177,9 @@ function DocView:get_x_offset_col(line, x)
|
||||||
last_i = i
|
last_i = i
|
||||||
i = i + #char
|
i = i + #char
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return #text
|
return #line_text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -308,11 +324,12 @@ end
|
||||||
|
|
||||||
|
|
||||||
function DocView:draw_line_text(idx, x, y)
|
function DocView:draw_line_text(idx, x, y)
|
||||||
local font = self:get_font()
|
local default_font = self:get_font()
|
||||||
local subpixel_scale = font:subpixel_scale()
|
local subpixel_scale = default_font:subpixel_scale()
|
||||||
local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset()
|
local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset()
|
||||||
for _, type, text in self.doc.highlighter:each_token(idx) do
|
for _, type, text in self.doc.highlighter:each_token(idx) do
|
||||||
local color = style.syntax[type]
|
local color = style.syntax[type]
|
||||||
|
local font = style.syntax_fonts[type] or default_font
|
||||||
if config.draw_whitespace then
|
if config.draw_whitespace then
|
||||||
tx = renderer.draw_text_subpixel(font, text, tx, ty, color, core.replacements, style.syntax.comment)
|
tx = renderer.draw_text_subpixel(font, text, tx, ty, color, core.replacements, style.syntax.comment)
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require "core.strict"
|
require "core.strict"
|
||||||
|
require "core.regex"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local style = require "core.style"
|
local style = require "core.style"
|
||||||
|
@ -17,7 +18,7 @@ local core = {}
|
||||||
local function load_session()
|
local function load_session()
|
||||||
local ok, t = pcall(dofile, USERDIR .. "/session.lua")
|
local ok, t = pcall(dofile, USERDIR .. "/session.lua")
|
||||||
if ok then
|
if ok then
|
||||||
return t.recents, t.window
|
return t.recents, t.window, t.window_mode
|
||||||
end
|
end
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
@ -28,6 +29,7 @@ local function save_session()
|
||||||
if fp then
|
if fp then
|
||||||
fp:write("return {recents=", common.serialize(core.recent_projects),
|
fp:write("return {recents=", common.serialize(core.recent_projects),
|
||||||
", window=", common.serialize(table.pack(system.get_window_size())),
|
", window=", common.serialize(table.pack(system.get_window_size())),
|
||||||
|
", window_mode=", common.serialize(system.get_window_mode()),
|
||||||
"}\n")
|
"}\n")
|
||||||
fp:close()
|
fp:close()
|
||||||
end
|
end
|
||||||
|
@ -72,6 +74,7 @@ function core.set_project_dir(new_dir, change_project_fn)
|
||||||
core.project_directories = {}
|
core.project_directories = {}
|
||||||
core.add_project_directory(new_dir)
|
core.add_project_directory(new_dir)
|
||||||
core.project_files = {}
|
core.project_files = {}
|
||||||
|
core.project_files_limit = false
|
||||||
core.reschedule_project_scan()
|
core.reschedule_project_scan()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
@ -99,32 +102,20 @@ local function strip_trailing_slash(filename)
|
||||||
return filename
|
return filename
|
||||||
end
|
end
|
||||||
|
|
||||||
local function project_scan_thread()
|
local function compare_file(a, b)
|
||||||
local function diff_files(a, b)
|
|
||||||
if #a ~= #b then return true end
|
|
||||||
for i, v in ipairs(a) do
|
|
||||||
if b[i].filename ~= v.filename
|
|
||||||
or b[i].modified ~= v.modified then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function compare_file(a, b)
|
|
||||||
return a.filename < b.filename
|
return a.filename < b.filename
|
||||||
end
|
end
|
||||||
|
|
||||||
-- "root" will by an absolute path without trailing '/'
|
-- "root" will by an absolute path without trailing '/'
|
||||||
-- "path" will be a path starting with '/' and without trailing '/'
|
-- "path" will be a path starting with '/' and without trailing '/'
|
||||||
-- or the empty string.
|
-- or the empty string.
|
||||||
-- It will identifies a sub-path within "root.
|
-- It will identifies a sub-path within "root.
|
||||||
-- The current path location will therefore always be: root .. path.
|
-- The current path location will therefore always be: root .. path.
|
||||||
-- When recursing "root" will always be the same, only "path" will change.
|
-- When recursing "root" will always be the same, only "path" will change.
|
||||||
-- Returns a list of file "items". In eash item the "filename" will be the
|
-- Returns a list of file "items". In eash item the "filename" will be the
|
||||||
-- complete file path relative to "root" *without* the trailing '/'.
|
-- complete file path relative to "root" *without* the trailing '/'.
|
||||||
local function get_files(root, path, t)
|
local function get_directory_files(root, path, t, recursive, begin_hook)
|
||||||
coroutine.yield()
|
if begin_hook then begin_hook() end
|
||||||
t = t or {}
|
|
||||||
local size_limit = config.file_size_limit * 10e5
|
local size_limit = config.file_size_limit * 10e5
|
||||||
local all = system.list_dir(root .. path) or {}
|
local all = system.list_dir(root .. path) or {}
|
||||||
local dirs, files = {}, {}
|
local dirs, files = {}, {}
|
||||||
|
@ -139,7 +130,7 @@ local function project_scan_thread()
|
||||||
info.filename = strip_leading_path(file)
|
info.filename = strip_leading_path(file)
|
||||||
table.insert(info.type == "dir" and dirs or files, info)
|
table.insert(info.type == "dir" and dirs or files, info)
|
||||||
entries_count = entries_count + 1
|
entries_count = entries_count + 1
|
||||||
if entries_count > max_entries then break end
|
if recursive and entries_count > max_entries then return nil, entries_count end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -147,9 +138,10 @@ local function project_scan_thread()
|
||||||
table.sort(dirs, compare_file)
|
table.sort(dirs, compare_file)
|
||||||
for _, f in ipairs(dirs) do
|
for _, f in ipairs(dirs) do
|
||||||
table.insert(t, f)
|
table.insert(t, f)
|
||||||
if entries_count <= max_entries then
|
if recursive and entries_count <= max_entries then
|
||||||
local subdir_t, subdir_count = get_files(root, PATHSEP .. f.filename, t)
|
local subdir_t, subdir_count = get_directory_files(root, PATHSEP .. f.filename, t, recursive)
|
||||||
entries_count = entries_count + subdir_count
|
entries_count = entries_count + subdir_count
|
||||||
|
f.scanned = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -159,22 +151,34 @@ local function project_scan_thread()
|
||||||
end
|
end
|
||||||
|
|
||||||
return t, entries_count
|
return t, entries_count
|
||||||
|
end
|
||||||
|
|
||||||
|
local function project_scan_thread()
|
||||||
|
local function diff_files(a, b)
|
||||||
|
if #a ~= #b then return true end
|
||||||
|
for i, v in ipairs(a) do
|
||||||
|
if b[i].filename ~= v.filename
|
||||||
|
or b[i].modified ~= v.modified then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
-- get project files and replace previous table if the new table is
|
-- get project files and replace previous table if the new table is
|
||||||
-- different
|
-- different
|
||||||
for i = 1, #core.project_directories do
|
local i = 1
|
||||||
|
while not core.project_files_limit and i <= #core.project_directories do
|
||||||
local dir = core.project_directories[i]
|
local dir = core.project_directories[i]
|
||||||
local t, entries_count = get_files(dir.name, "")
|
local t, entries_count = get_directory_files(dir.name, "", {}, true)
|
||||||
if diff_files(dir.files, t) then
|
if diff_files(dir.files, t) then
|
||||||
if entries_count > config.max_project_files then
|
if entries_count > config.max_project_files then
|
||||||
|
core.project_files_limit = true
|
||||||
core.status_view:show_message("!", style.accent,
|
core.status_view:show_message("!", style.accent,
|
||||||
"Too many files in project directory: stopping reading at "..
|
"Too many files in project directory: stopped reading at "..
|
||||||
config.max_project_files.." files according to config.max_project_files. "..
|
config.max_project_files.." files. For more information see "..
|
||||||
"Either tweak this variable, or ignore certain files/directories by "..
|
"usage.md at github.com/franko/lite-xl."
|
||||||
"using the config.ignore_files variable in your user plugin or "..
|
)
|
||||||
"project config.")
|
|
||||||
end
|
end
|
||||||
dir.files = t
|
dir.files = t
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
|
@ -182,6 +186,7 @@ local function project_scan_thread()
|
||||||
if dir.name == core.project_dir then
|
if dir.name == core.project_dir then
|
||||||
core.project_files = dir.files
|
core.project_files = dir.files
|
||||||
end
|
end
|
||||||
|
i = i + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- wait for next scan
|
-- wait for next scan
|
||||||
|
@ -190,6 +195,56 @@ local function project_scan_thread()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function core.is_project_folder(dirname)
|
||||||
|
for _, dir in ipairs(core.project_directories) do
|
||||||
|
if dir.name == dirname then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function core.scan_project_folder(dirname, filename)
|
||||||
|
for _, dir in ipairs(core.project_directories) do
|
||||||
|
if dir.name == dirname then
|
||||||
|
for i, file in ipairs(dir.files) do
|
||||||
|
local file = dir.files[i]
|
||||||
|
if file.filename == filename then
|
||||||
|
if file.scanned then return end
|
||||||
|
local new_files = get_directory_files(dirname, PATHSEP .. filename, {})
|
||||||
|
for j, new_file in ipairs(new_files) do
|
||||||
|
table.insert(dir.files, i + j, new_file)
|
||||||
|
end
|
||||||
|
file.scanned = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function find_project_files_co(root, path)
|
||||||
|
local size_limit = config.file_size_limit * 10e5
|
||||||
|
local all = system.list_dir(root .. path) or {}
|
||||||
|
for _, file in ipairs(all) do
|
||||||
|
if not common.match_pattern(file, config.ignore_files) then
|
||||||
|
local file = path .. PATHSEP .. file
|
||||||
|
local info = system.get_file_info(root .. file)
|
||||||
|
if info and info.size < size_limit then
|
||||||
|
info.filename = strip_leading_path(file)
|
||||||
|
if info.type == "file" then
|
||||||
|
coroutine.yield(root, info)
|
||||||
|
else
|
||||||
|
find_project_files_co(root, PATHSEP .. info.filename)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
local function project_files_iter(state)
|
local function project_files_iter(state)
|
||||||
local dir = core.project_directories[state.dir_index]
|
local dir = core.project_directories[state.dir_index]
|
||||||
state.file_index = state.file_index + 1
|
state.file_index = state.file_index + 1
|
||||||
|
@ -204,42 +259,39 @@ end
|
||||||
|
|
||||||
|
|
||||||
function core.get_project_files()
|
function core.get_project_files()
|
||||||
|
if core.project_files_limit then
|
||||||
|
return coroutine.wrap(function()
|
||||||
|
for _, dir in ipairs(core.project_directories) do
|
||||||
|
find_project_files_co(dir.name, "")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
local state = { dir_index = 1, file_index = 0 }
|
local state = { dir_index = 1, file_index = 0 }
|
||||||
return project_files_iter, state
|
return project_files_iter, state
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function core.project_files_number()
|
function core.project_files_number()
|
||||||
|
if not core.project_files_limit then
|
||||||
local n = 0
|
local n = 0
|
||||||
for i = 1, #core.project_directories do
|
for i = 1, #core.project_directories do
|
||||||
n = n + #core.project_directories[i].files
|
n = n + #core.project_directories[i].files
|
||||||
end
|
end
|
||||||
return n
|
return n
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- create a directory using mkdir but may need to create the parent
|
-- create a directory using mkdir but may need to create the parent
|
||||||
-- directories as well.
|
-- directories as well.
|
||||||
local function create_user_directory()
|
local function create_user_directory()
|
||||||
local dirname_create = USERDIR
|
local success, err = common.mkdirp(USERDIR)
|
||||||
local basedir
|
if not success then
|
||||||
local subdirs = {}
|
error("cannot create directory \"" .. USERDIR .. "\": " .. err)
|
||||||
while dirname_create and dirname_create ~= "" do
|
|
||||||
local success_mkdir = system.mkdir(dirname_create)
|
|
||||||
if success_mkdir then break end
|
|
||||||
dirname_create, basedir = dirname_create:match("(.*)[/\\](.+)$")
|
|
||||||
if basedir then
|
|
||||||
subdirs[#subdirs + 1] = basedir
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for _, dirname in ipairs(subdirs) do
|
|
||||||
dirname_create = dirname_create .. '/' .. dirname
|
|
||||||
if not system.mkdir(dirname_create) then
|
|
||||||
error("cannot create directory: \"" .. dirname_create .. "\"")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
for _, modname in ipairs {'plugins', 'colors', 'fonts'} do
|
for _, modname in ipairs {'plugins', 'colors', 'fonts'} do
|
||||||
local subdirname = dirname_create .. '/' .. modname
|
local subdirname = USERDIR .. PATHSEP .. modname
|
||||||
if not system.mkdir(subdirname) then
|
if not system.mkdir(subdirname) then
|
||||||
error("cannot create directory: \"" .. subdirname .. "\"")
|
error("cannot create directory: \"" .. subdirname .. "\"")
|
||||||
end
|
end
|
||||||
|
@ -274,8 +326,8 @@ local style = require "core.style"
|
||||||
------------------------------- Fonts ----------------------------------------
|
------------------------------- Fonts ----------------------------------------
|
||||||
|
|
||||||
-- customize fonts:
|
-- customize fonts:
|
||||||
-- style.font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 13 * SCALE)
|
-- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
|
||||||
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/monospace.ttf", 12 * SCALE)
|
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
|
||||||
--
|
--
|
||||||
-- font names used by lite:
|
-- font names used by lite:
|
||||||
-- style.font : user interface
|
-- style.font : user interface
|
||||||
|
@ -357,6 +409,20 @@ local function whitespace_replacements()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function reload_on_user_module_save()
|
||||||
|
-- auto-realod style when user's module is saved by overriding Doc:Save()
|
||||||
|
local doc_save = Doc.save
|
||||||
|
local user_filename = system.absolute_path(USERDIR .. PATHSEP .. "init.lua")
|
||||||
|
function Doc:save(filename, abs_filename)
|
||||||
|
doc_save(self, filename, abs_filename)
|
||||||
|
if self.abs_filename == user_filename then
|
||||||
|
core.reload_module("core.style")
|
||||||
|
core.load_user_directory()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function core.init()
|
function core.init()
|
||||||
command = require "core.command"
|
command = require "core.command"
|
||||||
keymap = require "core.keymap"
|
keymap = require "core.keymap"
|
||||||
|
@ -375,9 +441,11 @@ function core.init()
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do
|
||||||
local recent_projects, window_position = load_session()
|
local recent_projects, window_position, window_mode = load_session()
|
||||||
if window_position then
|
if window_mode == "normal" then
|
||||||
system.set_window_size(table.unpack(window_position))
|
system.set_window_size(table.unpack(window_position))
|
||||||
|
elseif window_mode == "maximized" then
|
||||||
|
system.set_window_mode("maximized")
|
||||||
end
|
end
|
||||||
core.recent_projects = recent_projects
|
core.recent_projects = recent_projects
|
||||||
end
|
end
|
||||||
|
@ -497,6 +565,8 @@ function core.init()
|
||||||
if item.text == "Exit" then os.exit(1) end
|
if item.text == "Exit" then os.exit(1) end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
reload_on_user_module_save()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -556,13 +626,19 @@ do
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- DEPRECATED function
|
||||||
core.doc_save_hooks = {}
|
core.doc_save_hooks = {}
|
||||||
function core.add_save_hook(fn)
|
function core.add_save_hook(fn)
|
||||||
|
core.error("The function core.add_save_hook is deprecated." ..
|
||||||
|
" Modules should now directly override the Doc:save function.")
|
||||||
core.doc_save_hooks[#core.doc_save_hooks + 1] = fn
|
core.doc_save_hooks[#core.doc_save_hooks + 1] = fn
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- DEPRECATED function
|
||||||
function core.on_doc_save(filename)
|
function core.on_doc_save(filename)
|
||||||
|
-- for backward compatibility in modules. Hooks are deprecated, the function Doc:save
|
||||||
|
-- should be directly overidded.
|
||||||
for _, hook in ipairs(core.doc_save_hooks) do
|
for _, hook in ipairs(core.doc_save_hooks) do
|
||||||
hook(filename)
|
hook(filename)
|
||||||
end
|
end
|
||||||
|
@ -870,26 +946,18 @@ end
|
||||||
function core.step()
|
function core.step()
|
||||||
-- handle events
|
-- handle events
|
||||||
local did_keymap = false
|
local did_keymap = false
|
||||||
local mouse_moved = false
|
|
||||||
local mouse = { x = 0, y = 0, dx = 0, dy = 0 }
|
|
||||||
|
|
||||||
|
|
||||||
for type, a,b,c,d in system.poll_event do
|
for type, a,b,c,d in system.poll_event do
|
||||||
if type == "mousemoved" then
|
if type == "textinput" and did_keymap then
|
||||||
mouse_moved = true
|
|
||||||
mouse.x, mouse.y = a, b
|
|
||||||
mouse.dx, mouse.dy = mouse.dx + c, mouse.dy + d
|
|
||||||
elseif type == "textinput" and did_keymap then
|
|
||||||
did_keymap = false
|
did_keymap = false
|
||||||
|
elseif type == "mousemoved" then
|
||||||
|
core.try(core.on_event, type, a, b, c, d)
|
||||||
else
|
else
|
||||||
local _, res = core.try(core.on_event, type, a, b, c, d)
|
local _, res = core.try(core.on_event, type, a, b, c, d)
|
||||||
did_keymap = res or did_keymap
|
did_keymap = res or did_keymap
|
||||||
end
|
end
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
end
|
end
|
||||||
if mouse_moved then
|
|
||||||
core.try(core.on_event, "mousemoved", mouse.x, mouse.y, mouse.dx, mouse.dy)
|
|
||||||
end
|
|
||||||
|
|
||||||
local width, height = renderer.get_size()
|
local width, height = renderer.get_size()
|
||||||
|
|
||||||
|
@ -994,6 +1062,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")
|
||||||
|
@ -1009,14 +1082,4 @@ function core.on_error(err)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
core.add_save_hook(function(filename)
|
|
||||||
local doc = core.active_view.doc
|
|
||||||
if doc and doc:is(Doc) and doc.abs_filename == USERDIR .. PATHSEP .. "init.lua" then
|
|
||||||
core.reload_module("core.style")
|
|
||||||
core.load_user_directory()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return core
|
return core
|
||||||
|
|
|
@ -6,31 +6,31 @@ local function keymap_macos(keymap)
|
||||||
["cmd+n"] = "core:new-doc",
|
["cmd+n"] = "core:new-doc",
|
||||||
["cmd+shift+c"] = "core:change-project-folder",
|
["cmd+shift+c"] = "core:change-project-folder",
|
||||||
["cmd+shift+o"] = "core:open-project-folder",
|
["cmd+shift+o"] = "core:open-project-folder",
|
||||||
["alt+return"] = "core:toggle-fullscreen",
|
["cmd+ctrl+return"] = "core:toggle-fullscreen",
|
||||||
|
|
||||||
["alt+shift+j"] = "root:split-left",
|
["cmd+ctrl+shift+j"] = "root:split-left",
|
||||||
["alt+shift+l"] = "root:split-right",
|
["cmd+ctrl+shift+l"] = "root:split-right",
|
||||||
["alt+shift+i"] = "root:split-up",
|
["cmd+ctrl+shift+i"] = "root:split-up",
|
||||||
["alt+shift+k"] = "root:split-down",
|
["cmd+ctrl+shift+k"] = "root:split-down",
|
||||||
["alt+j"] = "root:switch-to-left",
|
["cmd+ctrl+j"] = "root:switch-to-left",
|
||||||
["alt+l"] = "root:switch-to-right",
|
["cmd+ctrl+l"] = "root:switch-to-right",
|
||||||
["alt+i"] = "root:switch-to-up",
|
["cmd+ctrl+i"] = "root:switch-to-up",
|
||||||
["alt+k"] = "root:switch-to-down",
|
["cmd+ctrl+k"] = "root:switch-to-down",
|
||||||
|
|
||||||
["ctrl+w"] = "root:close",
|
["ctrl+w"] = "root:close",
|
||||||
["cmd+tab"] = "root:switch-to-next-tab",
|
["ctrl+tab"] = "root:switch-to-next-tab",
|
||||||
["cmd+shift+tab"] = "root:switch-to-previous-tab",
|
["ctrl+shift+tab"] = "root:switch-to-previous-tab",
|
||||||
["cmd+pageup"] = "root:move-tab-left",
|
["cmd+pageup"] = "root:move-tab-left",
|
||||||
["cmd+pagedown"] = "root:move-tab-right",
|
["cmd+pagedown"] = "root:move-tab-right",
|
||||||
["alt+1"] = "root:switch-to-tab-1",
|
["cmd+1"] = "root:switch-to-tab-1",
|
||||||
["alt+2"] = "root:switch-to-tab-2",
|
["cmd+2"] = "root:switch-to-tab-2",
|
||||||
["alt+3"] = "root:switch-to-tab-3",
|
["cmd+3"] = "root:switch-to-tab-3",
|
||||||
["alt+4"] = "root:switch-to-tab-4",
|
["cmd+4"] = "root:switch-to-tab-4",
|
||||||
["alt+5"] = "root:switch-to-tab-5",
|
["cmd+5"] = "root:switch-to-tab-5",
|
||||||
["alt+6"] = "root:switch-to-tab-6",
|
["cmd+6"] = "root:switch-to-tab-6",
|
||||||
["alt+7"] = "root:switch-to-tab-7",
|
["cmd+7"] = "root:switch-to-tab-7",
|
||||||
["alt+8"] = "root:switch-to-tab-8",
|
["cmd+8"] = "root:switch-to-tab-8",
|
||||||
["alt+9"] = "root:switch-to-tab-9",
|
["cmd+9"] = "root:switch-to-tab-9",
|
||||||
|
|
||||||
["cmd+f"] = "find-replace:find",
|
["cmd+f"] = "find-replace:find",
|
||||||
["cmd+r"] = "find-replace:replace",
|
["cmd+r"] = "find-replace:replace",
|
||||||
|
|
|
@ -170,12 +170,6 @@ function NagView:draw()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function findindex(tbl, prop)
|
|
||||||
for i, o in ipairs(tbl) do
|
|
||||||
if o[prop] then return i end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:get_message_height()
|
function NagView:get_message_height()
|
||||||
local h = 0
|
local h = 0
|
||||||
for str in string.gmatch(self.message, "(.-)\n") do
|
for str in string.gmatch(self.message, "(.-)\n") do
|
||||||
|
@ -196,7 +190,7 @@ function NagView:next()
|
||||||
-- self.target_height is the nagview height needed to display the message and
|
-- self.target_height is the nagview height needed to display the message and
|
||||||
-- the buttons, excluding the top and bottom padding space.
|
-- the buttons, excluding the top and bottom padding space.
|
||||||
self.target_height = math.max(message_height, self:get_buttons_height())
|
self.target_height = math.max(message_height, self:get_buttons_height())
|
||||||
self:change_hovered(findindex(self.options, "default_yes"))
|
self:change_hovered(common.find_index(self.options, "default_yes"))
|
||||||
end
|
end
|
||||||
self.force_focus = self.message ~= nil
|
self.force_focus = self.message ~= nil
|
||||||
core.set_active_view(self.message ~= nil and self or core.last_active_view)
|
core.set_active_view(self.message ~= nil and self or core.last_active_view)
|
||||||
|
@ -212,36 +206,4 @@ function NagView:show(title, message, options, on_select)
|
||||||
if #self.queue > 0 and not self.title then self:next() end
|
if #self.queue > 0 and not self.title then self:next() end
|
||||||
end
|
end
|
||||||
|
|
||||||
command.add(NagView, {
|
|
||||||
["dialog:previous-entry"] = function()
|
|
||||||
local v = core.active_view
|
|
||||||
local hover = v.hovered_item or 1
|
|
||||||
v:change_hovered(hover == 1 and #v.options or hover - 1)
|
|
||||||
end,
|
|
||||||
["dialog:next-entry"] = function()
|
|
||||||
local v = core.active_view
|
|
||||||
local hover = v.hovered_item or 1
|
|
||||||
v:change_hovered(hover == #v.options and 1 or hover + 1)
|
|
||||||
end,
|
|
||||||
["dialog:select-yes"] = function()
|
|
||||||
local v = core.active_view
|
|
||||||
if v ~= core.nag_view then return end
|
|
||||||
v:change_hovered(findindex(v.options, "default_yes"))
|
|
||||||
command.perform "dialog:select"
|
|
||||||
end,
|
|
||||||
["dialog:select-no"] = function()
|
|
||||||
local v = core.active_view
|
|
||||||
if v ~= core.nag_view then return end
|
|
||||||
v:change_hovered(findindex(v.options, "default_no"))
|
|
||||||
command.perform "dialog:select"
|
|
||||||
end,
|
|
||||||
["dialog:select"] = function()
|
|
||||||
local v = core.active_view
|
|
||||||
if v.hovered_item then
|
|
||||||
v.on_selected(v.options[v.hovered_item])
|
|
||||||
v:next()
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NagView
|
return NagView
|
|
@ -0,0 +1,70 @@
|
||||||
|
|
||||||
|
-- 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, options)
|
||||||
|
local pattern = type(pattern_string) == "table" and
|
||||||
|
pattern_string or regex.compile(pattern_string)
|
||||||
|
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
|
||||||
|
-- mid character.
|
||||||
|
local function previous_character(str, index)
|
||||||
|
local byte
|
||||||
|
repeat
|
||||||
|
index = index - 1
|
||||||
|
byte = string.byte(str, index)
|
||||||
|
until byte < 128 or byte >= 192
|
||||||
|
return index
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Moves to the end of the identified character.
|
||||||
|
local function end_character(str, index)
|
||||||
|
local byte = string.byte(str, index + 1)
|
||||||
|
while byte >= 128 and byte < 192 do
|
||||||
|
index = index + 1
|
||||||
|
byte = string.byte(str, index + 1)
|
||||||
|
end
|
||||||
|
return index
|
||||||
|
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
|
||||||
|
-- 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 matches, replacements = {}, {}
|
||||||
|
repeat
|
||||||
|
indices = { regex.cmatch(pattern, str) }
|
||||||
|
if #indices > 0 then
|
||||||
|
table.insert(matches, indices)
|
||||||
|
local currentReplacement = replacement
|
||||||
|
if #indices > 2 then
|
||||||
|
for i = 1, (#indices/2 - 1) do
|
||||||
|
currentReplacement = string.gsub(
|
||||||
|
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 ..
|
||||||
|
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
|
||||||
|
else
|
||||||
|
result = result .. currentReplacement
|
||||||
|
end
|
||||||
|
str = str:sub(indices[2])
|
||||||
|
end
|
||||||
|
until #indices == 0 or indices[1] == indices[2]
|
||||||
|
return result .. str, matches, replacements
|
||||||
|
end
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
|
local config = require "core.config"
|
||||||
local style = require "core.style"
|
local style = require "core.style"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
local Object = require "core.object"
|
local Object = require "core.object"
|
||||||
|
@ -59,7 +60,9 @@ function Node:new(type)
|
||||||
end
|
end
|
||||||
self.hovered = {x = -1, y = -1 }
|
self.hovered = {x = -1, y = -1 }
|
||||||
self.hovered_close = 0
|
self.hovered_close = 0
|
||||||
self.tab_margin = style.padding.x
|
self.tab_shift = 0
|
||||||
|
self.tab_offset = 1
|
||||||
|
self.tab_width = style.tab_width
|
||||||
self.move_towards = View.move_towards
|
self.move_towards = View.move_towards
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -126,14 +129,14 @@ function Node:split(dir, view, locked, resizable)
|
||||||
return self.b
|
return self.b
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Node:remove_view(root, view)
|
||||||
function Node:close_view(root, view)
|
|
||||||
local new_active_view = view == self.active_view
|
|
||||||
local do_close = function()
|
|
||||||
if #self.views > 1 then
|
if #self.views > 1 then
|
||||||
local idx = self:get_view_idx(view)
|
local idx = self:get_view_idx(view)
|
||||||
|
if idx < self.tab_offset then
|
||||||
|
self.tab_offset = self.tab_offset - 1
|
||||||
|
end
|
||||||
table.remove(self.views, idx)
|
table.remove(self.views, idx)
|
||||||
if new_active_view then
|
if self.active_view == view then
|
||||||
self:set_active_view(self.views[idx] or self.views[#self.views])
|
self:set_active_view(self.views[idx] or self.views[#self.views])
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -156,6 +159,11 @@ function Node:close_view(root, view)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
core.last_active_view = nil
|
core.last_active_view = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Node:close_view(root, view)
|
||||||
|
local do_close = function()
|
||||||
|
self:remove_view(root, view)
|
||||||
end
|
end
|
||||||
view:try_close(do_close)
|
view:try_close(do_close)
|
||||||
end
|
end
|
||||||
|
@ -166,13 +174,13 @@ function Node:close_active_view(root)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Node:add_view(view)
|
function Node:add_view(view, idx)
|
||||||
assert(self.type == "leaf", "Tried to add view to non-leaf node")
|
assert(self.type == "leaf", "Tried to add view to non-leaf node")
|
||||||
assert(not self.locked, "Tried to add view to locked node")
|
assert(not self.locked, "Tried to add view to locked node")
|
||||||
if self.views[1] and self.views[1]:is(EmptyView) then
|
if self.views[1] and self.views[1]:is(EmptyView) then
|
||||||
table.remove(self.views)
|
table.remove(self.views)
|
||||||
end
|
end
|
||||||
table.insert(self.views, view)
|
table.insert(self.views, idx or (#self.views + 1), view)
|
||||||
self:set_active_view(view)
|
self:set_active_view(view)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -221,6 +229,15 @@ function Node:get_children(t)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- return the width including the padding space and separately
|
||||||
|
-- the padding space itself
|
||||||
|
local function get_scroll_button_width()
|
||||||
|
local w = style.icon_font:get_width(">")
|
||||||
|
local pad = w
|
||||||
|
return w + 2 * pad, pad
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Node:get_divider_overlapping_point(px, py)
|
function Node:get_divider_overlapping_point(px, py)
|
||||||
if self.type ~= "leaf" then
|
if self.type ~= "leaf" then
|
||||||
local p = 6
|
local p = 6
|
||||||
|
@ -236,11 +253,18 @@ function Node:get_divider_overlapping_point(px, py)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_visible_tabs_number()
|
||||||
|
return math.min(#self.views - self.tab_offset + 1, config.max_tabs)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Node:get_tab_overlapping_point(px, py)
|
function Node:get_tab_overlapping_point(px, py)
|
||||||
if #self.views == 1 then return nil end
|
if #self.views == 1 then return nil end
|
||||||
local x, y, w, h = self:get_tab_rect(1)
|
local tabs_number = self:get_visible_tabs_number()
|
||||||
if px >= x and py >= y and px < x + w * #self.views and py < y + h then
|
local x1, y1, w, h = self:get_tab_rect(self.tab_offset)
|
||||||
return math.floor((px - x) / w) + 1
|
local x2, y2 = self:get_tab_rect(self.tab_offset + tabs_number)
|
||||||
|
if px >= x1 and py >= y1 and px < x2 and py < y1 + h then
|
||||||
|
return math.floor((px - x1) / w) + self.tab_offset
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -252,18 +276,31 @@ local function close_button_location(x, w)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_scroll_button_index(px, py)
|
||||||
|
if #self.views == 1 then return end
|
||||||
|
for i = 1, 2 do
|
||||||
|
local x, y, w, h = self:get_scroll_button_rect(i)
|
||||||
|
if px >= x and px < x + w and py >= y and py < y + h then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Node:tab_hovered_update(px, py)
|
function Node:tab_hovered_update(px, py)
|
||||||
local tab_index = self:get_tab_overlapping_point(px, py)
|
local tab_index = self:get_tab_overlapping_point(px, py)
|
||||||
self.hovered_tab = tab_index
|
self.hovered_tab = tab_index
|
||||||
|
self.hovered_close = 0
|
||||||
|
self.hovered_scroll_button = 0
|
||||||
if tab_index then
|
if tab_index then
|
||||||
local x, y, w, h = self:get_tab_rect(tab_index)
|
local x, y, w, h = self:get_tab_rect(tab_index)
|
||||||
local cx, cw = close_button_location(x, w)
|
local cx, cw = close_button_location(x, w)
|
||||||
if px >= cx and px < cx + cw and py >= y and py < y + h then
|
if px >= cx and px < cx + cw and py >= y and py < y + h then
|
||||||
self.hovered_close = tab_index
|
self.hovered_close = tab_index
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
self.hovered_scroll_button = self:get_scroll_button_index(px, py) or 0
|
||||||
end
|
end
|
||||||
self.hovered_close = 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -280,10 +317,20 @@ function Node:get_child_overlapping_point(x, y)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_scroll_button_rect(index)
|
||||||
|
local w, pad = get_scroll_button_width()
|
||||||
|
local h = style.font:get_height() + style.padding.y * 2
|
||||||
|
local x = self.position.x + (index == 1 and 0 or self.size.x - w)
|
||||||
|
return x, self.position.y, w, h, pad
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Node:get_tab_rect(idx)
|
function Node:get_tab_rect(idx)
|
||||||
local tw = math.min(style.tab_width, (self.size.x - self.tab_margin) / #self.views)
|
local sbw = get_scroll_button_width()
|
||||||
local x_left = self.position.x + tw * (idx - 1)
|
local maxw = self.size.x - 2 * sbw
|
||||||
local x1, x2 = math.floor(x_left), math.floor(x_left + tw)
|
local x0 = self.position.x + sbw
|
||||||
|
local x1 = x0 + common.clamp(self.tab_width * (idx - 1) - self.tab_shift, 0, maxw)
|
||||||
|
local x2 = x0 + common.clamp(self.tab_width * idx - self.tab_shift, 0, maxw)
|
||||||
local h = style.font:get_height() + style.padding.y * 2
|
local h = style.font:get_height() + style.padding.y * 2
|
||||||
return x1, self.position.y, x2 - x1, h
|
return x1, self.position.y, x2 - x1, h
|
||||||
end
|
end
|
||||||
|
@ -386,13 +433,61 @@ function Node:update_layout()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:scroll_tabs_to_visible()
|
||||||
|
local index = self:get_view_idx(self.active_view)
|
||||||
|
if index then
|
||||||
|
local tabs_number = self:get_visible_tabs_number()
|
||||||
|
if self.tab_offset > index then
|
||||||
|
self.tab_offset = index
|
||||||
|
elseif self.tab_offset + tabs_number - 1 < index then
|
||||||
|
self.tab_offset = index - tabs_number + 1
|
||||||
|
elseif tabs_number < config.max_tabs and self.tab_offset > 1 then
|
||||||
|
self.tab_offset = #self.views - config.max_tabs + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:scroll_tabs(dir)
|
||||||
|
local view_index = self:get_view_idx(self.active_view)
|
||||||
|
if dir == 1 then
|
||||||
|
if self.tab_offset > 1 then
|
||||||
|
self.tab_offset = self.tab_offset - 1
|
||||||
|
local last_index = self.tab_offset + self:get_visible_tabs_number() - 1
|
||||||
|
if view_index > last_index then
|
||||||
|
self:set_active_view(self.views[last_index])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif dir == 2 then
|
||||||
|
local tabs_number = self:get_visible_tabs_number()
|
||||||
|
if self.tab_offset + tabs_number - 1 < #self.views then
|
||||||
|
self.tab_offset = self.tab_offset + 1
|
||||||
|
local view_index = self:get_view_idx(self.active_view)
|
||||||
|
if view_index < self.tab_offset then
|
||||||
|
self:set_active_view(self.views[self.tab_offset])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:target_tab_width()
|
||||||
|
local n = self:get_visible_tabs_number()
|
||||||
|
local w = self.size.x - get_scroll_button_width() * 2
|
||||||
|
return common.clamp(style.tab_width, w / config.max_tabs, w / n)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Node:update()
|
function Node:update()
|
||||||
if self.type == "leaf" then
|
if self.type == "leaf" then
|
||||||
|
self:scroll_tabs_to_visible()
|
||||||
for _, view in ipairs(self.views) do
|
for _, view in ipairs(self.views) do
|
||||||
view:update()
|
view:update()
|
||||||
end
|
end
|
||||||
self:tab_hovered_update(self.hovered.x, self.hovered.y)
|
self:tab_hovered_update(self.hovered.x, self.hovered.y)
|
||||||
self:move_towards("tab_margin", style.padding.x)
|
local tab_width = self:target_tab_width()
|
||||||
|
self:move_towards("tab_shift", tab_width * (self.tab_offset - 1))
|
||||||
|
self:move_towards("tab_width", tab_width)
|
||||||
else
|
else
|
||||||
self.a:update()
|
self.a:update()
|
||||||
self.b:update()
|
self.b:update()
|
||||||
|
@ -401,14 +496,27 @@ end
|
||||||
|
|
||||||
|
|
||||||
function Node:draw_tabs()
|
function Node:draw_tabs()
|
||||||
local x, y, _, h = self:get_tab_rect(1)
|
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
||||||
local ds = style.divider_size
|
local ds = style.divider_size
|
||||||
local dots_width = style.font:get_width("…")
|
local dots_width = style.font:get_width("…")
|
||||||
core.push_clip_rect(x, y, self.size.x, h)
|
core.push_clip_rect(x, y, self.size.x, h)
|
||||||
renderer.draw_rect(x, y, self.size.x, h, style.background2)
|
renderer.draw_rect(x, y, self.size.x, h, style.background2)
|
||||||
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
|
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
|
||||||
|
|
||||||
for i, view in ipairs(self.views) do
|
if self.tab_offset > 1 then
|
||||||
|
local button_style = self.hovered_scroll_button == 1 and style.text or style.dim
|
||||||
|
common.draw_text(style.icon_font, button_style, "<", nil, x + scroll_padding, y, 0, h)
|
||||||
|
end
|
||||||
|
|
||||||
|
local tabs_number = self:get_visible_tabs_number()
|
||||||
|
if #self.views > self.tab_offset + tabs_number - 1 then
|
||||||
|
local xrb, yrb, wrb = self:get_scroll_button_rect(2)
|
||||||
|
local button_style = self.hovered_scroll_button == 2 and style.text or style.dim
|
||||||
|
common.draw_text(style.icon_font, button_style, ">", nil, xrb + scroll_padding, yrb, 0, h)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
||||||
|
local view = self.views[i]
|
||||||
local x, y, w, h = self:get_tab_rect(i)
|
local x, y, w, h = self:get_tab_rect(i)
|
||||||
local text = view:get_name()
|
local text = view:get_name()
|
||||||
local color = style.dim
|
local color = style.dim
|
||||||
|
@ -630,6 +738,12 @@ function RootView:close_all_docviews()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Function to intercept mouse pressed events on the active view.
|
||||||
|
-- Do nothing by default.
|
||||||
|
function RootView.on_view_mouse_pressed(button, x, y, clicks)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function RootView:on_mouse_pressed(button, x, y, clicks)
|
function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||||
if div then
|
if div then
|
||||||
|
@ -637,20 +751,24 @@ function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
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)
|
local idx = node:get_tab_overlapping_point(x, y)
|
||||||
if idx then
|
if idx then
|
||||||
if button == "middle" or node.hovered_close == idx then
|
if button == "middle" or node.hovered_close == idx then
|
||||||
local _, _, tw = node:get_tab_rect(idx)
|
|
||||||
node.tab_margin = node.tab_margin + tw
|
|
||||||
node:close_view(self.root_node, node.views[idx])
|
node:close_view(self.root_node, node.views[idx])
|
||||||
else
|
else
|
||||||
self.dragged_node = idx
|
self.dragged_node = { node, idx }
|
||||||
node:set_active_view(node.views[idx])
|
node:set_active_view(node.views[idx])
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
core.set_active_view(node.active_view)
|
core.set_active_view(node.active_view)
|
||||||
|
if not self.on_view_mouse_pressed(button, x, y, clicks) then
|
||||||
node.active_view:on_mouse_pressed(button, x, y, clicks)
|
node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -678,7 +796,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
|
||||||
|
@ -702,21 +820,35 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
||||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||||
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 div then
|
if node and node:get_scroll_button_index(x, y) then
|
||||||
|
core.request_cursor("arrow")
|
||||||
|
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
|
elseif node then
|
||||||
local tab = node.views[self.dragged_node]
|
core.request_cursor(node.active_view.cursor)
|
||||||
table.remove(node.views, self.dragged_node)
|
end
|
||||||
table.insert(node.views, tab_index, tab)
|
if node and self.dragged_node and (self.dragged_node[1] ~= node or (tab_index and self.dragged_node[2] ~= tab_index))
|
||||||
self.dragged_node = tab_index
|
and node.type == "leaf" and #node.views > 0 and node.views[1]:is(DocView) then
|
||||||
|
local tab = self.dragged_node[1].views[self.dragged_node[2]]
|
||||||
|
if self.dragged_node[1] ~= node then
|
||||||
|
for i, v in ipairs(node.views) do if v.doc == tab.doc then tab = nil break end end
|
||||||
|
if tab then
|
||||||
|
self.dragged_node[1]:remove_view(self.root_node, tab)
|
||||||
|
node:add_view(tab, tab_index)
|
||||||
|
self.root_node:update_layout()
|
||||||
|
self.dragged_node = { node, tab_index or #node.views }
|
||||||
|
core.redraw = true
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
system.set_cursor(node.active_view.cursor)
|
table.remove(self.dragged_node[1].views, self.dragged_node[2])
|
||||||
|
table.insert(node.views, tab_index, tab)
|
||||||
|
self.dragged_node = { node, tab_index }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -751,6 +883,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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
-- this file is used by lite-xl to setup the Lua environment
|
-- this file is used by lite-xl to setup the Lua environment when starting
|
||||||
-- when starting
|
VERSION = "1.16.11"
|
||||||
VERSION = "1.16.9"
|
|
||||||
MOD_VERSION = "1"
|
MOD_VERSION = "1"
|
||||||
|
|
||||||
SCALE = tonumber(os.getenv("LITE_SCALE")) or SCALE
|
SCALE = tonumber(os.getenv("LITE_SCALE")) or SCALE
|
||||||
|
@ -13,7 +12,7 @@ else
|
||||||
local prefix = EXEDIR:match("^(.+)[/\\]bin$")
|
local prefix = EXEDIR:match("^(.+)[/\\]bin$")
|
||||||
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
|
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
|
||||||
end
|
end
|
||||||
USERDIR = HOME and (HOME .. '/.config/lite-xl') or (EXEDIR .. '/user')
|
USERDIR = os.getenv("XDG_CONFIG_HOME") or (HOME and (HOME .. '/.config/lite-xl') or (EXEDIR .. '/user'))
|
||||||
|
|
||||||
package.path = DATADIR .. '/?.lua;' .. package.path
|
package.path = DATADIR .. '/?.lua;' .. package.path
|
||||||
package.path = DATADIR .. '/?/init.lua;' .. package.path
|
package.path = DATADIR .. '/?/init.lua;' .. package.path
|
||||||
|
|
|
@ -21,11 +21,11 @@ style.tab_width = common.round(170 * SCALE)
|
||||||
--
|
--
|
||||||
-- On High DPI monitor or non RGB monitor you may consider using antialiasing grayscale instead.
|
-- On High DPI monitor or non RGB monitor you may consider using antialiasing grayscale instead.
|
||||||
-- The antialiasing grayscale with full hinting is interesting for crisp font rendering.
|
-- The antialiasing grayscale with full hinting is interesting for crisp font rendering.
|
||||||
style.font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 13 * SCALE)
|
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
|
||||||
style.big_font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 34 * SCALE)
|
style.big_font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 40 * SCALE)
|
||||||
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 14 * SCALE, {antialiasing="grayscale", hinting="full"})
|
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 14 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||||
style.icon_big_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 20 * SCALE, {antialiasing="grayscale", hinting="full"})
|
style.icon_big_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 20 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/monospace.ttf", 12 * SCALE)
|
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
|
||||||
|
|
||||||
style.background = { common.color "#2e2e32" }
|
style.background = { common.color "#2e2e32" }
|
||||||
style.background2 = { common.color "#252529" }
|
style.background2 = { common.color "#252529" }
|
||||||
|
@ -57,4 +57,11 @@ style.syntax["string"] = { common.color "#f7c95c" }
|
||||||
style.syntax["operator"] = { common.color "#93DDFA" }
|
style.syntax["operator"] = { common.color "#93DDFA" }
|
||||||
style.syntax["function"] = { common.color "#93DDFA" }
|
style.syntax["function"] = { common.color "#93DDFA" }
|
||||||
|
|
||||||
|
-- This can be used to override fonts per syntax group.
|
||||||
|
-- The syntax highlighter will take existing values from this table and
|
||||||
|
-- override style.code_font on a per-token basis, so you can choose to eg.
|
||||||
|
-- render comments in an italic font if you want to.
|
||||||
|
style.syntax_fonts = {}
|
||||||
|
-- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options)
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
|
|
@ -15,51 +15,72 @@ local function push_token(t, type, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function is_escaped(text, idx, esc)
|
local function push_tokens(t, syn, pattern, full_text, find_results)
|
||||||
local byte = esc:byte()
|
if #find_results > 2 then
|
||||||
local count = 0
|
-- We do some manipulation with find_results so that it's arranged
|
||||||
for i = idx - 1, 1, -1 do
|
-- like this:
|
||||||
if text:byte(i) ~= byte then break end
|
-- { start, end, i_1, i_2, i_3, …, i_last }
|
||||||
count = count + 1
|
-- Each position spans characters from i_n to ((i_n+1) - 1), to form
|
||||||
|
-- consecutive spans of text.
|
||||||
|
--
|
||||||
|
-- If i_1 is not equal to start, start is automatically inserted at
|
||||||
|
-- that index.
|
||||||
|
if find_results[3] ~= find_results[1] then
|
||||||
|
table.insert(find_results, 3, find_results[1])
|
||||||
|
end
|
||||||
|
-- Copy the ending index to the end of the table, so that an ending index
|
||||||
|
-- always follows a starting index after position 3 in the table.
|
||||||
|
table.insert(find_results, find_results[2] + 1)
|
||||||
|
-- Then, we just iterate over our modified table.
|
||||||
|
for i = 3, #find_results - 1 do
|
||||||
|
local start = find_results[i]
|
||||||
|
local fin = find_results[i + 1] - 1
|
||||||
|
local type = pattern.type[i - 2]
|
||||||
|
-- ↑ (i - 2) to convert from [3; n] to [1; n]
|
||||||
|
local text = full_text:sub(start, fin)
|
||||||
|
push_token(t, syn.symbols[text] or type, text)
|
||||||
end
|
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
|
else
|
||||||
return s, e
|
local start, fin = find_results[1], find_results[2]
|
||||||
end
|
local text = full_text:sub(start, fin)
|
||||||
|
push_token(t, syn.symbols[text] or pattern.type, text)
|
||||||
end
|
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,
|
||||||
-- does not support further highlighting.
|
-- does not support further highlighting.
|
||||||
|
|
||||||
|
-- You can think of it as a maximum 4 integer (0-255) stack. It always has
|
||||||
|
-- 1 integer in it. Calling `push_subsyntax` increases the stack depth. Calling
|
||||||
|
-- `pop_subsyntax` decreases it. The integers represent the index of a pattern
|
||||||
|
-- that we're following in the syntax. The top of the stack can be any valid
|
||||||
|
-- pattern index, any integer lower in the stack must represent a pattern that
|
||||||
|
-- specifies a subsyntax.
|
||||||
|
|
||||||
|
-- If you do not have subsyntaxes in your syntax, the three most
|
||||||
|
-- singificant numbers will always be 0, the stack will only ever be length 1
|
||||||
|
-- and the state variable will only ever range from 0-255.
|
||||||
local function retrieve_syntax_state(incoming_syntax, state)
|
local function retrieve_syntax_state(incoming_syntax, state)
|
||||||
local current_syntax, subsyntax_info, current_state, current_level =
|
local current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||||
incoming_syntax, nil, state, 0
|
incoming_syntax, nil, state, 0
|
||||||
if state > 0 and (state > 255 or current_syntax.patterns[state].syntax) then
|
if state > 0 and (state > 255 or current_syntax.patterns[state].syntax) then
|
||||||
-- If we have higher bits, then decode them one at a time, and find which
|
-- If we have higher bits, then decode them one at a time, and find which
|
||||||
-- syntax we're using. Rather than walking the bytes, and calling into
|
-- syntax we're using. Rather than walking the bytes, and calling into
|
||||||
-- `syntax` each time, we could probably cache this in a single table.
|
-- `syntax` each time, we could probably cache this in a single table.
|
||||||
for i=0,2 do
|
for i = 0, 2 do
|
||||||
local target = bit32.extract(state, i*8, 8)
|
local target = bit32.extract(state, i*8, 8)
|
||||||
if target ~= 0 then
|
if target ~= 0 then
|
||||||
if current_syntax.patterns[target].syntax then
|
if current_syntax.patterns[target].syntax then
|
||||||
subsyntax_info = current_syntax.patterns[target]
|
subsyntax_info = current_syntax.patterns[target]
|
||||||
current_syntax = type(subsyntax_info.syntax) == "table" and
|
current_syntax = type(subsyntax_info.syntax) == "table" and
|
||||||
subsyntax_info.syntax or syntax.get(subsyntax_info.syntax)
|
subsyntax_info.syntax or syntax.get(subsyntax_info.syntax)
|
||||||
current_state = 0
|
current_pattern_idx = 0
|
||||||
current_level = i+1
|
current_level = i+1
|
||||||
else
|
else
|
||||||
current_state = target
|
current_pattern_idx = target
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -67,7 +88,7 @@ local function retrieve_syntax_state(incoming_syntax, state)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return current_syntax, subsyntax_info, current_state, current_level
|
return current_syntax, subsyntax_info, current_pattern_idx, current_level
|
||||||
end
|
end
|
||||||
|
|
||||||
function tokenizer.tokenize(incoming_syntax, text, state)
|
function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
|
@ -79,35 +100,92 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
end
|
end
|
||||||
|
|
||||||
state = state or 0
|
state = state or 0
|
||||||
local current_syntax, subsyntax_info, current_state, current_level =
|
-- incoming_syntax : the parent syntax of the file.
|
||||||
|
-- state : a 32-bit number representing syntax state (see above)
|
||||||
|
|
||||||
|
-- current_syntax : the syntax we're currently in.
|
||||||
|
-- subsyntax_info : info about the delimiters of this subsyntax.
|
||||||
|
-- current_pattern_idx: the index of the pattern we're on for this syntax.
|
||||||
|
-- current_level : how many subsyntaxes deep we are.
|
||||||
|
local current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||||
retrieve_syntax_state(incoming_syntax, state)
|
retrieve_syntax_state(incoming_syntax, state)
|
||||||
|
|
||||||
|
-- Should be used to set the state variable. Don't modify it directly.
|
||||||
|
local function set_subsyntax_pattern_idx(pattern_idx)
|
||||||
|
current_pattern_idx = pattern_idx
|
||||||
|
state = bit32.replace(state, pattern_idx, current_level*8, 8)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function push_subsyntax(entering_syntax, pattern_idx)
|
||||||
|
set_subsyntax_pattern_idx(pattern_idx)
|
||||||
|
current_level = current_level + 1
|
||||||
|
subsyntax_info = entering_syntax
|
||||||
|
current_syntax = type(entering_syntax.syntax) == "table" and
|
||||||
|
entering_syntax.syntax or syntax.get(entering_syntax.syntax)
|
||||||
|
current_pattern_idx = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pop_subsyntax()
|
||||||
|
set_subsyntax_pattern_idx(0)
|
||||||
|
current_level = current_level - 1
|
||||||
|
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
|
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_state > 0 then
|
if current_pattern_idx > 0 then
|
||||||
local p = current_syntax.patterns[current_state]
|
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.
|
-- first, before the found delimeter, as ending the subsyntax takes
|
||||||
|
-- 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,
|
-- If we find that we end the subsyntax before the
|
||||||
subsyntax_info.pattern[2],
|
-- delimiter, push the token, and signal we shouldn't
|
||||||
i,
|
-- treat the bit after as a token to be normally parsed
|
||||||
subsyntax_info.pattern[3]
|
-- (as it's the syntax delimiter).
|
||||||
)
|
|
||||||
if ss and (s == nil or ss < s) then
|
if ss and (s == nil or ss < s) then
|
||||||
push_token(res, p.type, text:sub(i, ss - 1))
|
push_token(res, p.type, text:sub(i, ss - 1))
|
||||||
i = ss
|
i = ss
|
||||||
cont = false
|
cont = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- If we don't have any concerns about syntax delimiters,
|
||||||
|
-- continue on as normal.
|
||||||
if cont then
|
if cont then
|
||||||
if s then
|
if s then
|
||||||
push_token(res, p.type, text:sub(i, e))
|
push_token(res, p.type, text:sub(i, e))
|
||||||
current_state = 0
|
set_subsyntax_pattern_idx(0)
|
||||||
state = bit32.replace(state, 0, current_level*8, 8)
|
|
||||||
i = e + 1
|
i = e + 1
|
||||||
else
|
else
|
||||||
push_token(res, p.type, text:sub(i))
|
push_token(res, p.type, text:sub(i))
|
||||||
|
@ -115,21 +193,15 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- Check for end of syntax.
|
-- General end of syntax check. Applies in the case where
|
||||||
|
-- we're ending early in the middle of a delimiter, or
|
||||||
|
-- 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))
|
||||||
current_level = current_level - 1
|
-- On finding unescaped delimiter, pop it.
|
||||||
-- Zero out the state above us, as well as our new current state.
|
pop_subsyntax()
|
||||||
state = bit32.replace(state, 0, current_level*8, 16)
|
|
||||||
current_syntax, subsyntax_info, current_state, current_level =
|
|
||||||
retrieve_syntax_state(incoming_syntax, state)
|
|
||||||
i = e + 1
|
i = e + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -137,32 +209,21 @@ 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 s, e = text:find("^" .. pattern, i)
|
if find_results[1] then
|
||||||
|
-- matched pattern; make and add tokens
|
||||||
if s then
|
push_tokens(res, current_syntax, p, text, find_results)
|
||||||
-- matched pattern; make and add token
|
|
||||||
local t = text:sub(s, e)
|
|
||||||
|
|
||||||
push_token(res, current_syntax.symbols[t] or p.type, t)
|
|
||||||
-- 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
|
||||||
state = bit32.replace(state, n, current_level*8, 8)
|
-- If we have a subsyntax, push that onto the subsyntax stack.
|
||||||
-- If we've found a new subsyntax, bump our level, and set the
|
|
||||||
-- appropriate variables.
|
|
||||||
if p.syntax then
|
if p.syntax then
|
||||||
current_level = current_level + 1
|
push_subsyntax(p, n)
|
||||||
subsyntax_info = p
|
|
||||||
current_syntax = type(p.syntax) == "table" and
|
|
||||||
p.syntax or syntax.get(p.syntax)
|
|
||||||
current_state = 0
|
|
||||||
else
|
else
|
||||||
current_state = n
|
set_subsyntax_pattern_idx(n)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- move cursor past this token
|
-- move cursor past this token
|
||||||
i = e + 1
|
i = find_results[2] + 1
|
||||||
matched = true
|
matched = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,272 @@
|
||||||
|
-- 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
|
||||||
|
local items_list = { width = 0, height = 0 }
|
||||||
|
for _, items in ipairs(self.itemset) do
|
||||||
|
if items.predicate(x, y) then
|
||||||
|
items_list.width = math.max(items_list.width, items.items.width)
|
||||||
|
items_list.height = items_list.height + items.items.height
|
||||||
|
for _, subitems in ipairs(items.items) do
|
||||||
|
table.insert(items_list, subitems)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #items_list > 0 then
|
||||||
|
self.items = items_list
|
||||||
|
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 on_view_mouse_pressed = RootView.on_view_mouse_pressed
|
||||||
|
local 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
|
||||||
|
on_mouse_moved(self, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function RootView.on_view_mouse_pressed(button, x, y, clicks)
|
||||||
|
-- We give the priority to the menu to process mouse pressed events.
|
||||||
|
local handled = menu:on_mouse_pressed(button, x, y, clicks)
|
||||||
|
return handled or on_view_mouse_pressed(button, x, y, clicks)
|
||||||
|
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
|
|
@ -76,7 +76,7 @@ local function get_non_empty_lines(syntax, lines)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local auto_detect_max_lines = 200
|
local auto_detect_max_lines = 100
|
||||||
|
|
||||||
local function detect_indent_stat(doc)
|
local function detect_indent_stat(doc)
|
||||||
local stat = {}
|
local stat = {}
|
||||||
|
@ -99,22 +99,11 @@ local function detect_indent_stat(doc)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local doc_on_text_change = Doc.on_text_change
|
|
||||||
local adjust_threshold = 4
|
|
||||||
|
|
||||||
local function update_cache(doc)
|
local function update_cache(doc)
|
||||||
local type, size, score = detect_indent_stat(doc)
|
local type, size, score = detect_indent_stat(doc)
|
||||||
cache[doc] = { type = type, size = size, confirmed = (score >= adjust_threshold) }
|
local score_threshold = 4
|
||||||
|
cache[doc] = { type = type, size = size, confirmed = (score >= score_threshold) }
|
||||||
doc.indent_info = cache[doc]
|
doc.indent_info = cache[doc]
|
||||||
if score < adjust_threshold and doc_on_text_change then
|
|
||||||
Doc.on_text_change = function(self, ...)
|
|
||||||
doc_on_text_change(self, ...)
|
|
||||||
update_cache(self)
|
|
||||||
end
|
|
||||||
elseif score >= adjust_threshold and doc_on_text_change then
|
|
||||||
Doc.on_text_change = doc_on_text_change
|
|
||||||
doc_on_text_change = nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,6 +111,14 @@ local new = Doc.new
|
||||||
function Doc:new(...)
|
function Doc:new(...)
|
||||||
new(self, ...)
|
new(self, ...)
|
||||||
update_cache(self)
|
update_cache(self)
|
||||||
|
if not cache[self].confirmed then
|
||||||
|
core.add_thread(function ()
|
||||||
|
while not cache[self].confirmed do
|
||||||
|
update_cache(self)
|
||||||
|
coroutine.yield(1)
|
||||||
|
end
|
||||||
|
end, self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local clean = Doc.clean
|
local clean = Doc.clean
|
||||||
|
|
|
@ -7,15 +7,18 @@ syntax.add {
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = "//.-\n", type = "comment" },
|
{ pattern = "//.-\n", type = "comment" },
|
||||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||||
{ pattern = { "#", "[^\\]\n" }, type = "comment" },
|
|
||||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||||
{ pattern = "-?0x%x+", type = "number" },
|
{ pattern = "0x%x+", type = "number" },
|
||||||
{ pattern = "-?%d+[%d%.eE]*f?", type = "number" },
|
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
|
||||||
{ pattern = "-?%.?%d+f?", type = "number" },
|
{ pattern = "%.?%d+f?", type = "number" },
|
||||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||||
|
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||||
|
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||||
|
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
|
||||||
|
{ pattern = "#[%a_][%w_]*", type = "keyword" },
|
||||||
},
|
},
|
||||||
symbols = {
|
symbols = {
|
||||||
["if"] = "keyword",
|
["if"] = "keyword",
|
||||||
|
@ -29,8 +32,6 @@ syntax.add {
|
||||||
["continue"] = "keyword",
|
["continue"] = "keyword",
|
||||||
["return"] = "keyword",
|
["return"] = "keyword",
|
||||||
["goto"] = "keyword",
|
["goto"] = "keyword",
|
||||||
["struct"] = "keyword",
|
|
||||||
["union"] = "keyword",
|
|
||||||
["typedef"] = "keyword",
|
["typedef"] = "keyword",
|
||||||
["enum"] = "keyword",
|
["enum"] = "keyword",
|
||||||
["extern"] = "keyword",
|
["extern"] = "keyword",
|
||||||
|
@ -42,7 +43,6 @@ syntax.add {
|
||||||
["case"] = "keyword",
|
["case"] = "keyword",
|
||||||
["default"] = "keyword",
|
["default"] = "keyword",
|
||||||
["auto"] = "keyword",
|
["auto"] = "keyword",
|
||||||
["const"] = "keyword",
|
|
||||||
["void"] = "keyword",
|
["void"] = "keyword",
|
||||||
["int"] = "keyword2",
|
["int"] = "keyword2",
|
||||||
["short"] = "keyword2",
|
["short"] = "keyword2",
|
||||||
|
|
|
@ -11,14 +11,21 @@ syntax.add {
|
||||||
{ pattern = { "%[%[", "%]%]" }, type = "string" },
|
{ pattern = { "%[%[", "%]%]" }, type = "string" },
|
||||||
{ pattern = { "%-%-%[%[", "%]%]"}, type = "comment" },
|
{ pattern = { "%-%-%[%[", "%]%]"}, type = "comment" },
|
||||||
{ pattern = "%-%-.-\n", type = "comment" },
|
{ pattern = "%-%-.-\n", type = "comment" },
|
||||||
{ pattern = "-?0x%x+", type = "number" },
|
{ pattern = "0x%x+%.%x*[pP][-+]?%d+", type = "number" },
|
||||||
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
{ pattern = "0x%x+%.%x*", type = "number" },
|
||||||
{ pattern = "-?%.?%d+", type = "number" },
|
{ pattern = "0x%.%x+[pP][-+]?%d+", type = "number" },
|
||||||
|
{ pattern = "0x%.%x+", type = "number" },
|
||||||
|
{ pattern = "0x%x+[pP][-+]?%d+", type = "number" },
|
||||||
|
{ pattern = "0x%x+", type = "number" },
|
||||||
|
{ pattern = "%d%.%d*[eE][-+]?%d+", type = "number" },
|
||||||
|
{ pattern = "%d%.%d*", type = "number" },
|
||||||
|
{ pattern = "%.?%d*[eE][-+]?%d+", type = "number" },
|
||||||
|
{ pattern = "%.?%d+", type = "number" },
|
||||||
{ pattern = "<%a+>", type = "keyword2" },
|
{ pattern = "<%a+>", type = "keyword2" },
|
||||||
{ pattern = "%.%.%.?", type = "operator" },
|
{ pattern = "%.%.%.?", type = "operator" },
|
||||||
{ pattern = "[<>~=]=", type = "operator" },
|
{ pattern = "[<>~=]=", type = "operator" },
|
||||||
{ pattern = "[%+%-=/%*%^%%#<>]", type = "operator" },
|
{ pattern = "[%+%-=/%*%^%%#<>]", type = "operator" },
|
||||||
{ pattern = "[%a_][%w_]*%s*%f[(\"{]", type = "function" },
|
{ pattern = "[%a_][%w_]*()%s*%f[(\"'{]", type = {"function", "normal"} },
|
||||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||||
{ pattern = "::[%a_][%w_]*::", type = "function" },
|
{ pattern = "::[%a_][%w_]*::", type = "function" },
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,7 +30,10 @@ local function find_all_matches_in_file(t, filename, fn)
|
||||||
for line in fp:lines() do
|
for line in fp:lines() do
|
||||||
local s = fn(line)
|
local s = fn(line)
|
||||||
if s then
|
if s then
|
||||||
table.insert(t, { file = filename, text = line, line = n, col = s })
|
-- Insert maximum 256 characters. If we insert more, for compiled files, which can have very long lines
|
||||||
|
-- things tend to get sluggish. If our line is longer than 80 characters, begin to truncate the thing.
|
||||||
|
local start_index = math.max(s - 80, 1)
|
||||||
|
table.insert(t, { file = filename, text = (start_index > 1 and "..." or "") .. line:sub(start_index, 256 + start_index), line = n, col = s })
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
end
|
end
|
||||||
if n % 100 == 0 then coroutine.yield() end
|
if n % 100 == 0 then coroutine.yield() end
|
||||||
|
@ -167,12 +170,17 @@ function ResultsView:draw()
|
||||||
local ox, oy = self:get_content_offset()
|
local ox, oy = self:get_content_offset()
|
||||||
local x, y = ox + style.padding.x, oy + style.padding.y
|
local x, y = ox + style.padding.x, oy + style.padding.y
|
||||||
local files_number = core.project_files_number()
|
local files_number = core.project_files_number()
|
||||||
local per = self.last_file_idx / files_number
|
local per = files_number and self.last_file_idx / files_number or 1
|
||||||
local text
|
local text
|
||||||
if self.searching then
|
if self.searching then
|
||||||
|
if files_number then
|
||||||
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
|
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
|
||||||
per * 100, self.last_file_idx, files_number,
|
per * 100, self.last_file_idx, files_number,
|
||||||
#self.results, self.query)
|
#self.results, self.query)
|
||||||
|
else
|
||||||
|
text = string.format("Searching (%d files, %d matches) for %q...",
|
||||||
|
self.last_file_idx, #self.results, self.query)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
text = string.format("Found %d matches for %q",
|
text = string.format("Found %d matches for %q",
|
||||||
#self.results, self.query)
|
#self.results, self.query)
|
||||||
|
@ -229,9 +237,12 @@ command.add(nil, {
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["project-search:find-pattern"] = function()
|
["project-search:find-regex"] = function()
|
||||||
core.command_view:enter("Find Pattern In Project", function(text)
|
core.command_view:enter("Find Regex In Project", function(text)
|
||||||
begin_search(text, function(line_text) return line_text:find(text) end)
|
local re = regex.compile(text, "i")
|
||||||
|
begin_search(text, function(line_text)
|
||||||
|
return regex.cmatch(re, line_text)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -265,6 +276,26 @@ command.add(ResultsView, {
|
||||||
["project-search:refresh"] = function()
|
["project-search:refresh"] = function()
|
||||||
core.active_view:refresh()
|
core.active_view:refresh()
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
["project-search:move-to-previous-page"] = function()
|
||||||
|
local view = core.active_view
|
||||||
|
view.scroll.to.y = view.scroll.to.y - view.size.y
|
||||||
|
end,
|
||||||
|
|
||||||
|
["project-search:move-to-next-page"] = function()
|
||||||
|
local view = core.active_view
|
||||||
|
view.scroll.to.y = view.scroll.to.y + view.size.y
|
||||||
|
end,
|
||||||
|
|
||||||
|
["project-search:move-to-start-of-doc"] = function()
|
||||||
|
local view = core.active_view
|
||||||
|
view.scroll.to.y = 0
|
||||||
|
end,
|
||||||
|
|
||||||
|
["project-search:move-to-end-of-doc"] = function()
|
||||||
|
local view = core.active_view
|
||||||
|
view.scroll.to.y = view:get_scrollable_size()
|
||||||
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
keymap.add {
|
keymap.add {
|
||||||
|
@ -273,4 +304,10 @@ keymap.add {
|
||||||
["up"] = "project-search:select-previous",
|
["up"] = "project-search:select-previous",
|
||||||
["down"] = "project-search:select-next",
|
["down"] = "project-search:select-next",
|
||||||
["return"] = "project-search:open-selected",
|
["return"] = "project-search:open-selected",
|
||||||
|
["pageup"] = "project-search:move-to-previous-page",
|
||||||
|
["pagedown"] = "project-search:move-to-next-page",
|
||||||
|
["ctrl+home"] = "project-search:move-to-start-of-doc",
|
||||||
|
["ctrl+end"] = "project-search:move-to-end-of-doc",
|
||||||
|
["home"] = "project-search:move-to-start-of-doc",
|
||||||
|
["end"] = "project-search:move-to-end-of-doc"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
-- 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
|
||||||
|
|
||||||
|
-- we set scale_level in case this was called by user
|
||||||
|
scale_level = (scale - default_scale) / scale_steps
|
||||||
|
|
||||||
|
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 function get_scale()
|
||||||
|
return current_scale
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
["set"] = set_scale,
|
||||||
|
["get"] = get_scale,
|
||||||
|
["increase"] = inc_scale,
|
||||||
|
["decrease"] = dec_scale,
|
||||||
|
["reset"] = res_scale
|
||||||
|
}
|
|
@ -93,6 +93,13 @@ function TreeView:get_item_height()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function TreeView:invalidate_cache(dirname)
|
||||||
|
for _, v in pairs(self.cache[dirname]) do
|
||||||
|
v.skip = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:check_cache()
|
function TreeView:check_cache()
|
||||||
-- invalidate cache's skip values if project_files has changed
|
-- invalidate cache's skip values if project_files has changed
|
||||||
for i = 1, #core.project_directories do
|
for i = 1, #core.project_directories do
|
||||||
|
@ -102,9 +109,7 @@ function TreeView:check_cache()
|
||||||
self.last[dir.name] = dir.files
|
self.last[dir.name] = dir.files
|
||||||
else
|
else
|
||||||
if dir.files ~= last_files then
|
if dir.files ~= last_files then
|
||||||
for _, v in pairs(self.cache[dir.name]) do
|
self:invalidate_cache(dir.name)
|
||||||
v.skip = nil
|
|
||||||
end
|
|
||||||
self.last[dir.name] = dir.files
|
self.last[dir.name] = dir.files
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -208,17 +213,34 @@ function TreeView:on_mouse_pressed(button, x, y, clicks)
|
||||||
if caught then
|
if caught then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if not self.hovered_item then
|
local hovered_item = self.hovered_item
|
||||||
|
if not hovered_item then
|
||||||
return
|
return
|
||||||
elseif self.hovered_item.type == "dir" then
|
elseif hovered_item.type == "dir" then
|
||||||
if keymap.modkeys["ctrl"] and button == "left" then
|
if keymap.modkeys["ctrl"] and button == "left" then
|
||||||
create_directory_in(self.hovered_item)
|
create_directory_in(hovered_item)
|
||||||
else
|
else
|
||||||
self.hovered_item.expanded = not self.hovered_item.expanded
|
if core.project_files_limit and not hovered_item.expanded then
|
||||||
|
local filename, abs_filename = hovered_item.filename, hovered_item.abs_filename
|
||||||
|
local index = 0
|
||||||
|
-- The loop below is used to find the first match starting from the end
|
||||||
|
-- in case there are multiple matches.
|
||||||
|
while index and index + #filename < #abs_filename do
|
||||||
|
index = string.find(abs_filename, filename, index + 1, true)
|
||||||
|
end
|
||||||
|
-- we assume here index is not nil because the abs_filename must contain the
|
||||||
|
-- relative filename
|
||||||
|
local dirname = string.sub(abs_filename, 1, index - 2)
|
||||||
|
if core.is_project_folder(dirname) then
|
||||||
|
core.scan_project_folder(dirname, filename)
|
||||||
|
self:invalidate_cache(dirname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
hovered_item.expanded = not hovered_item.expanded
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
core.try(function()
|
core.try(function()
|
||||||
local doc_filename = common.relative_path(core.project_dir, self.hovered_item.abs_filename)
|
local doc_filename = common.relative_path(core.project_dir, hovered_item.abs_filename)
|
||||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -11,7 +12,7 @@ local function workspace_files_for(project_dir)
|
||||||
if not info_wsdir then
|
if not info_wsdir then
|
||||||
local ok, err = system.mkdir(workspace_dir)
|
local ok, err = system.mkdir(workspace_dir)
|
||||||
if not ok then
|
if not ok then
|
||||||
error("cannot create workspace directory: %s", err)
|
error("cannot create workspace directory: \"" .. err .. "\"")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return coroutine.wrap(function()
|
return coroutine.wrap(function()
|
||||||
|
@ -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 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)
|
local ok, doc = pcall(core.open_doc, t.filename)
|
||||||
if not ok then
|
if ok then
|
||||||
return DocView(core.open_doc())
|
dv = DocView(doc)
|
||||||
end
|
end
|
||||||
local dv = DocView(doc)
|
end
|
||||||
if t.text then doc:insert(1, 1, t.text) end
|
-- doc view "dv" can be nil here if the filename associated to the document
|
||||||
doc:set_selection(table.unpack(t.selection))
|
-- cannot be read.
|
||||||
dv.last_line, dv.last_col = doc:get_selection()
|
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.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
|
||||||
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
|
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
|
||||||
|
end
|
||||||
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 view then
|
||||||
if v.active then res = view end
|
if v.active then res = view end
|
||||||
node:add_view(view)
|
node:add_view(view)
|
||||||
|
if t.active_view == i then
|
||||||
|
active_view = view
|
||||||
end
|
end
|
||||||
if t.active_view then
|
end
|
||||||
node:set_active_view(node.views[t.active_view])
|
end
|
||||||
|
if active_view then
|
||||||
|
node:set_active_view(active_view)
|
||||||
end
|
end
|
||||||
return res
|
return res
|
||||||
else
|
else
|
||||||
|
@ -184,7 +202,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
local function load_workspace()
|
local function load_workspace()
|
||||||
local workspace = load_workspace_file(core.project_dir)
|
local workspace = consume_workspace_file(core.project_dir)
|
||||||
if workspace then
|
if workspace then
|
||||||
local root = get_unlocked_root(core.root_view.root_node)
|
local root = get_unlocked_root(core.root_view.root_node)
|
||||||
local active_view = load_node(root, workspace.documents)
|
local active_view = load_node(root, workspace.documents)
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
-- put user settings here
|
|
||||||
-- this module will be loaded after everything else when the application starts
|
|
||||||
-- it will be automatically reloaded when saved
|
|
||||||
|
|
||||||
local core = require "core"
|
|
||||||
local keymap = require "core.keymap"
|
|
||||||
local config = require "core.config"
|
|
||||||
local style = require "core.style"
|
|
||||||
|
|
||||||
------------------------------ Themes ----------------------------------------
|
|
||||||
|
|
||||||
-- light theme:
|
|
||||||
-- core.reload_module("colors.summer")
|
|
||||||
|
|
||||||
--------------------------- Key bindings -------------------------------------
|
|
||||||
|
|
||||||
-- key binding:
|
|
||||||
-- keymap.add { ["ctrl+escape"] = "core:quit" }
|
|
||||||
|
|
||||||
|
|
||||||
------------------------------- Fonts ----------------------------------------
|
|
||||||
|
|
||||||
-- customize fonts:
|
|
||||||
-- style.font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 13 * SCALE)
|
|
||||||
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/monospace.ttf", 12 * SCALE)
|
|
||||||
--
|
|
||||||
-- font names used by lite:
|
|
||||||
-- style.font : user interface
|
|
||||||
-- style.big_font : big text in welcome screen
|
|
||||||
-- style.icon_font : icons
|
|
||||||
-- style.icon_big_font : toolbar icons
|
|
||||||
-- style.code_font : code
|
|
||||||
--
|
|
||||||
-- the function to load the font accept a 3rd optional argument like:
|
|
||||||
--
|
|
||||||
-- {antialiasing="grayscale", hinting="full"}
|
|
||||||
--
|
|
||||||
-- possible values are:
|
|
||||||
-- antialiasing: grayscale, subpixel
|
|
||||||
-- hinting: none, slight, full
|
|
||||||
|
|
||||||
------------------------------ Plugins ----------------------------------------
|
|
||||||
|
|
||||||
-- enable or disable plugin loading setting config entries:
|
|
||||||
|
|
||||||
-- enable trimwhitespace, otherwise it is disable by default:
|
|
||||||
-- config.trimwhitespace = true
|
|
||||||
--
|
|
||||||
-- disable detectindent, otherwise it is enabled by default
|
|
||||||
-- config.detectindent = false
|
|
|
@ -15,9 +15,12 @@
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>MinimumOSVersion</key><string>10.13</string>
|
<key>MinimumOSVersion</key><string>10.13</string>
|
||||||
|
<key>NSDocumentsFolderUsageDescription</key><string>To access, edit and index your projects.</string>
|
||||||
|
<key>NSDesktopFolderUsageDescription</key><string>To access, edit and index your projects.</string>
|
||||||
|
<key>NSDownloadsFolderUsageDescription</key><string>To access, edit and index your projects.</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.16.5</string>
|
<string>1.16.10</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>© 2019-2021 rxi franko</string>
|
<string>© 2019-2021 Francesco Abbate</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
{
|
|
||||||
"name": "icons",
|
|
||||||
"css_prefix_text": "icon-",
|
|
||||||
"css_use_suffix": false,
|
|
||||||
"hinting": true,
|
|
||||||
"units_per_em": 1000,
|
|
||||||
"ascent": 850,
|
|
||||||
"glyphs": [
|
|
||||||
{
|
|
||||||
"uid": "9dd9e835aebe1060ba7190ad2b2ed951",
|
|
||||||
"css": "search-1",
|
|
||||||
"code": 76,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "c76b7947c957c9b78b11741173c8349b",
|
|
||||||
"css": "attention-1",
|
|
||||||
"code": 33,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
|
|
||||||
"css": "doc-1",
|
|
||||||
"code": 102,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "f8aa663c489bcbd6e68ec8147dca841e",
|
|
||||||
"css": "folder-1",
|
|
||||||
"code": 100,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "c95735c17a10af81448c7fed98a04546",
|
|
||||||
"css": "folder-open-1",
|
|
||||||
"code": 68,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "e99461abfef3923546da8d745372c995",
|
|
||||||
"css": "cog",
|
|
||||||
"code": 80,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "7bf14281af5633a597f85b061ef1cfb9",
|
|
||||||
"css": "angle-right",
|
|
||||||
"code": 43,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "e4dde1992f787163e2e2b534b8c8067d",
|
|
||||||
"css": "angle-down",
|
|
||||||
"code": 45,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "ea2d9a8c51ca42b38ef0d2a07f16d9a7",
|
|
||||||
"css": "chart-line",
|
|
||||||
"code": 103,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "f4445feb55521283572ee88bc304f928",
|
|
||||||
"css": "floppy",
|
|
||||||
"code": 83,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "9755f76110ae4d12ac5f9466c9152031",
|
|
||||||
"css": "book",
|
|
||||||
"code": 66,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
|
|
||||||
"css": "info-circled-1",
|
|
||||||
"code": 105,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "5211af474d3a9848f67f945e2ccaf143",
|
|
||||||
"css": "cancel-1",
|
|
||||||
"code": 67,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "04f022b8bd044d4ccfffd3887ff72088",
|
|
||||||
"css": "window-minimize",
|
|
||||||
"code": 95,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "d0e62145dbf40f30e47b3819b8b43a8f",
|
|
||||||
"css": "window-restore",
|
|
||||||
"code": 119,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "7394501fc0b17cb7bda99538f92e26d6",
|
|
||||||
"css": "window-close",
|
|
||||||
"code": 88,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "559647a6f430b3aeadbecd67194451dd",
|
|
||||||
"css": "menu-1",
|
|
||||||
"code": 77,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "07f0832c07f3d9713fffb06c8bffa027",
|
|
||||||
"css": "window-maximize",
|
|
||||||
"code": 87,
|
|
||||||
"src": "fontawesome"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "icons",
|
||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"css_use_suffix": false,
|
"css_use_suffix": false,
|
||||||
"hinting": true,
|
"hinting": true,
|
||||||
|
@ -12,234 +12,18 @@
|
||||||
"code": 76,
|
"code": 76,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"uid": "12f4ece88e46abd864e40b35e05b11cd",
|
|
||||||
"css": "ok-1",
|
|
||||||
"code": 59402,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "43ab845088317bd348dee1d975700c48",
|
|
||||||
"css": "ok-circled-1",
|
|
||||||
"code": 59403,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "ad33e708f4d2e25c5056c931da1528d6",
|
|
||||||
"css": "ok-circled2",
|
|
||||||
"code": 59405,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "1400d5103edd2fa6d2d61688fee79a5a",
|
|
||||||
"css": "ok-squared",
|
|
||||||
"code": 61770,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "5211af474d3a9848f67f945e2ccaf143",
|
|
||||||
"css": "cancel-1",
|
|
||||||
"code": 67,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "0f4cae16f34ae243a6144c18a003f2d8",
|
|
||||||
"css": "cancel-circled-1",
|
|
||||||
"code": 99,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "d7271d490b71df4311e32cdacae8b331",
|
|
||||||
"css": "home-1",
|
|
||||||
"code": 59407,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "3d4ea8a78dc34efe891f3a0f3d961274",
|
|
||||||
"css": "info",
|
|
||||||
"code": 61737,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "ce3cf091d6ebd004dd0b52d24074e6e3",
|
|
||||||
"css": "help",
|
|
||||||
"code": 61736,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "17ebadd1e3f274ff0205601eef7b9cc4",
|
|
||||||
"css": "help-circled-1",
|
|
||||||
"code": 59408,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
|
|
||||||
"css": "info-circled-1",
|
|
||||||
"code": 59409,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "c1f1975c885aa9f3dad7810c53b82074",
|
|
||||||
"css": "lock",
|
|
||||||
"code": 59410,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "657ab647f6248a6b57a5b893beaf35a9",
|
|
||||||
"css": "lock-open-1",
|
|
||||||
"code": 59411,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "05376be04a27d5a46e855a233d6e8508",
|
|
||||||
"css": "lock-open-alt-1",
|
|
||||||
"code": 61758,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "3db5347bd219f3bce6025780f5d9ef45",
|
|
||||||
"css": "tag",
|
|
||||||
"code": 59412,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "a3f89e106175a5c5c4e9738870b12e55",
|
|
||||||
"css": "tags",
|
|
||||||
"code": 59413,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "7034e4d22866af82bef811f52fb1ba46",
|
|
||||||
"css": "code",
|
|
||||||
"code": 61729,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "d35a1d35efeb784d1dc9ac18b9b6c2b6",
|
|
||||||
"css": "pencil-1",
|
|
||||||
"code": 59414,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "44fae3bfdd54754dc68ec50d37efea37",
|
|
||||||
"css": "pencil-squared",
|
|
||||||
"code": 61771,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "41087bc74d4b20b55059c60a33bf4008",
|
|
||||||
"css": "edit",
|
|
||||||
"code": 59415,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "ecb97add13804c190456025e43ec003b",
|
|
||||||
"css": "keyboard",
|
|
||||||
"code": 61724,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"uid": "c76b7947c957c9b78b11741173c8349b",
|
"uid": "c76b7947c957c9b78b11741173c8349b",
|
||||||
"css": "attention-1",
|
"css": "attention-1",
|
||||||
"code": 33,
|
"code": 33,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"uid": "00391fac5d419345ffcccd95b6f76263",
|
|
||||||
"css": "attention-alt-1",
|
|
||||||
"code": 61738,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "b035c28eba2b35c6ffe92aee8b0df507",
|
|
||||||
"css": "attention-circled",
|
|
||||||
"code": 59417,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "f48ae54adfb27d8ada53d0fd9e34ee10",
|
|
||||||
"css": "trash-empty",
|
|
||||||
"code": 59418,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
|
"uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
|
||||||
"css": "doc-1",
|
"css": "doc-1",
|
||||||
"code": 102,
|
"code": 102,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"uid": "c8585e1e5b0467f28b70bce765d5840c",
|
|
||||||
"css": "docs",
|
|
||||||
"code": 61637,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "5408be43f7c42bccee419c6be53fdef5",
|
|
||||||
"css": "doc-text",
|
|
||||||
"code": 61686,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "178053298e3e5b03551d754d4b9acd8b",
|
|
||||||
"css": "doc-inv",
|
|
||||||
"code": 61787,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "c08a1cde48d96cba21d8c05fa7d7feb1",
|
|
||||||
"css": "doc-text-inv",
|
|
||||||
"code": 61788,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "9daa1fdf0838118518a7e22715e83abc",
|
|
||||||
"css": "file-pdf",
|
|
||||||
"code": 61889,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "310ffd629da85142bc8669f010556f2d",
|
|
||||||
"css": "file-word",
|
|
||||||
"code": 61890,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "edcd4022de8d8df266ef7c42d2658ca5",
|
|
||||||
"css": "file-powerpoint",
|
|
||||||
"code": 61892,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "3c961c1a8d874815856fc6637dc5a13c",
|
|
||||||
"css": "file-image",
|
|
||||||
"code": 61893,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "e80ae555c1413a4ec18b33fb348b4049",
|
|
||||||
"css": "file-archive",
|
|
||||||
"code": 61894,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "81db033e704eb7c586a365559d7c0f36",
|
|
||||||
"css": "file-audio",
|
|
||||||
"code": 61895,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "dd69d9aa589ea7bc0a82a3fe67039f4b",
|
|
||||||
"css": "file-video",
|
|
||||||
"code": 61896,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "26613a2e6bc41593c54bead46f8c8ee3",
|
|
||||||
"css": "file-code",
|
|
||||||
"code": 61897,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"uid": "f8aa663c489bcbd6e68ec8147dca841e",
|
"uid": "f8aa663c489bcbd6e68ec8147dca841e",
|
||||||
"css": "folder-1",
|
"css": "folder-1",
|
||||||
|
@ -252,58 +36,10 @@
|
||||||
"code": 68,
|
"code": 68,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"uid": "b091a8bd0fdade174951f17d936f51e4",
|
|
||||||
"css": "folder-empty-1",
|
|
||||||
"code": 61716,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "6533bdc16ab201eb3f3b27ce989cab33",
|
|
||||||
"css": "folder-open-empty-1",
|
|
||||||
"code": 61717,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "559647a6f430b3aeadbecd67194451dd",
|
|
||||||
"css": "menu-1",
|
|
||||||
"code": 61641,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"uid": "e99461abfef3923546da8d745372c995",
|
"uid": "e99461abfef3923546da8d745372c995",
|
||||||
"css": "cog",
|
"css": "cog",
|
||||||
"code": 59422,
|
"code": 80,
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "98687378abd1faf8f6af97c254eb6cd6",
|
|
||||||
"css": "cog-alt",
|
|
||||||
"code": 59423,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "5bb103cd29de77e0e06a52638527b575",
|
|
||||||
"css": "wrench",
|
|
||||||
"code": 59424,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "0b2b66e526028a6972d51a6f10281b4b",
|
|
||||||
"css": "zoom-in",
|
|
||||||
"code": 59425,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "d25d10efa900f529ad1d275657cfd30e",
|
|
||||||
"css": "zoom-out",
|
|
||||||
"code": 59426,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "f3f90c8c89795da30f7444634476ea4f",
|
|
||||||
"css": "angle-left",
|
|
||||||
"code": 61700,
|
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -312,24 +48,12 @@
|
||||||
"code": 43,
|
"code": 43,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"uid": "5de9370846a26947e03f63142a3f1c07",
|
|
||||||
"css": "angle-up",
|
|
||||||
"code": 61701,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"uid": "e4dde1992f787163e2e2b534b8c8067d",
|
"uid": "e4dde1992f787163e2e2b534b8c8067d",
|
||||||
"css": "angle-down",
|
"css": "angle-down",
|
||||||
"code": 45,
|
"code": 45,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"uid": "bbfb51903f40597f0b70fd75bc7b5cac",
|
|
||||||
"css": "trash",
|
|
||||||
"code": 61944,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"uid": "ea2d9a8c51ca42b38ef0d2a07f16d9a7",
|
"uid": "ea2d9a8c51ca42b38ef0d2a07f16d9a7",
|
||||||
"css": "chart-line",
|
"css": "chart-line",
|
||||||
|
@ -342,106 +66,64 @@
|
||||||
"code": 83,
|
"code": 83,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"uid": "b429436ec5a518c78479d44ef18dbd60",
|
|
||||||
"css": "paste",
|
|
||||||
"code": 61674,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "8772331a9fec983cdb5d72902a6f9e0e",
|
|
||||||
"css": "scissors",
|
|
||||||
"code": 59428,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"uid": "9755f76110ae4d12ac5f9466c9152031",
|
"uid": "9755f76110ae4d12ac5f9466c9152031",
|
||||||
"css": "book",
|
"css": "book",
|
||||||
"code": 59429,
|
"code": 66,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uid": "f9cbf7508cd04145ade2800169959eef",
|
"uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
|
||||||
"css": "font",
|
"css": "info-circled-1",
|
||||||
"code": 59430,
|
"code": 105,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uid": "d3b3f17bc3eb7cd809a07bbd4d178bee",
|
"uid": "5211af474d3a9848f67f945e2ccaf143",
|
||||||
"css": "resize-vertical",
|
"css": "cancel-1",
|
||||||
"code": 59431,
|
"code": 67,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uid": "3c73d058e4589b65a8d959c0fc8f153d",
|
"uid": "04f022b8bd044d4ccfffd3887ff72088",
|
||||||
"css": "resize-horizontal",
|
"css": "window-minimize",
|
||||||
"code": 59432,
|
"code": 95,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uid": "e594fc6e5870b4ab7e49f52571d52577",
|
"uid": "d0e62145dbf40f30e47b3819b8b43a8f",
|
||||||
"css": "resize-full",
|
"css": "window-restore",
|
||||||
"code": 59433,
|
"code": 119,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uid": "5278ef7773e948d56c4d442c8c8c98cf",
|
"uid": "7394501fc0b17cb7bda99538f92e26d6",
|
||||||
"css": "lightbulb",
|
"css": "window-close",
|
||||||
"code": 61675,
|
"code": 88,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uid": "598a5f2bcf3521d1615de8e1881ccd17",
|
"uid": "559647a6f430b3aeadbecd67194451dd",
|
||||||
"css": "clock",
|
"css": "menu-1",
|
||||||
"code": 59434,
|
"code": 77,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uid": "1c4068ed75209e21af36017df8871802",
|
"uid": "07f0832c07f3d9713fffb06c8bffa027",
|
||||||
"css": "down-big",
|
"css": "window-maximize",
|
||||||
"code": 59435,
|
"code": 87,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uid": "555ef8c86832e686fef85f7af2eb7cde",
|
"uid": "d870630ff8f81e6de3958ecaeac532f2",
|
||||||
"css": "left-big",
|
"css": "left-open",
|
||||||
"code": 59436,
|
"code": 60,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uid": "ad6b3fbb5324abe71a9c0b6609cbb9f1",
|
"uid": "399ef63b1e23ab1b761dfbb5591fa4da",
|
||||||
"css": "right-big",
|
"css": "right-open",
|
||||||
"code": 59437,
|
"code": 62,
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "95376bf082bfec6ce06ea1cda7bd7ead",
|
|
||||||
"css": "up-big",
|
|
||||||
"code": 59438,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "107ce08c7231097c7447d8f4d059b55f",
|
|
||||||
"css": "ellipsis",
|
|
||||||
"code": 61761,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "750058837a91edae64b03d60fc7e81a7",
|
|
||||||
"css": "ellipsis-vert",
|
|
||||||
"code": 61762,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "8fb55fd696d9a0f58f3b27c1d8633750",
|
|
||||||
"css": "table",
|
|
||||||
"code": 61646,
|
|
||||||
"src": "fontawesome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": "53dd31a6cc6438192b2d7b09b1c1dd45",
|
|
||||||
"css": "columns",
|
|
||||||
"code": 61659,
|
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
200
doc/usage.md
200
doc/usage.md
|
@ -1,200 +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.
|
|
||||||
|
|
||||||
## 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" }
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
# Licenses
|
||||||
|
|
||||||
|
## rxi/lite
|
||||||
|
|
||||||
|
Copyright (c) 2020 rxi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
## Fira Sans
|
||||||
|
|
||||||
|
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
## Fira Code
|
||||||
|
|
||||||
|
Copyright (c) 2014, The Fira Code Project Authors (https://github.com/tonsky/FiraCode)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
## JetBrains Mono
|
||||||
|
|
||||||
|
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
|
||||||
|
# SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@ -9,6 +9,7 @@ libm = cc.find_library('m', required : false)
|
||||||
libdl = cc.find_library('dl', required : false)
|
libdl = cc.find_library('dl', required : false)
|
||||||
libx11 = dependency('x11', required : false)
|
libx11 = dependency('x11', required : false)
|
||||||
lua_dep = dependency('lua5.2', required : false)
|
lua_dep = dependency('lua5.2', required : false)
|
||||||
|
pcre2_dep = dependency('libpcre2-8')
|
||||||
|
|
||||||
if not lua_dep.found()
|
if not lua_dep.found()
|
||||||
lua_subproject = subproject('lua', default_options: ['shared=false', 'use_readline=false', 'app=false'])
|
lua_subproject = subproject('lua', default_options: ['shared=false', 'use_readline=false', 'app=false'])
|
||||||
|
@ -19,8 +20,10 @@ sdl_dep = dependency('sdl2', method: 'config-tool')
|
||||||
|
|
||||||
lite_cargs = []
|
lite_cargs = []
|
||||||
if get_option('portable')
|
if get_option('portable')
|
||||||
|
lite_docdir = 'doc'
|
||||||
lite_datadir = 'data'
|
lite_datadir = 'data'
|
||||||
else
|
else
|
||||||
|
lite_docdir = 'share/doc/lite-xl'
|
||||||
lite_datadir = 'share/lite-xl'
|
lite_datadir = 'share/lite-xl'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -29,10 +32,15 @@ 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
|
||||||
|
|
||||||
|
install_data('licenses/licenses.md', install_dir : lite_docdir)
|
||||||
|
|
||||||
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'
|
||||||
lite_link_args += ['-static-libgcc', '-static-libstdc++']
|
lite_link_args += ['-static-libgcc', '-static-libstdc++']
|
||||||
endif
|
endif
|
||||||
|
if host_machine.system() == 'darwin'
|
||||||
|
lite_link_args += ['-framework', 'CoreServices', '-framework', 'Foundation']
|
||||||
|
endif
|
||||||
|
|
||||||
lite_rc = []
|
lite_rc = []
|
||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
|
|
||||||
int luaopen_system(lua_State *L);
|
int luaopen_system(lua_State *L);
|
||||||
int luaopen_renderer(lua_State *L);
|
int luaopen_renderer(lua_State *L);
|
||||||
|
int luaopen_regex(lua_State *L);
|
||||||
|
|
||||||
|
|
||||||
static const luaL_Reg libs[] = {
|
static const luaL_Reg libs[] = {
|
||||||
{ "system", luaopen_system },
|
{ "system", luaopen_system },
|
||||||
{ "renderer", luaopen_renderer },
|
{ "renderer", luaopen_renderer },
|
||||||
|
{ "regex", luaopen_regex },
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
#include "api.h"
|
||||||
|
|
||||||
|
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <pcre2.h>
|
||||||
|
|
||||||
|
static int f_pcre_gc(lua_State* L) {
|
||||||
|
lua_rawgeti(L, -1, 1);
|
||||||
|
pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1);
|
||||||
|
if (re)
|
||||||
|
pcre2_code_free(re);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f_pcre_compile(lua_State *L) {
|
||||||
|
size_t len;
|
||||||
|
PCRE2_SIZE errorOffset;
|
||||||
|
int errorNumber;
|
||||||
|
int pattern = PCRE2_UTF;
|
||||||
|
const char* str = luaL_checklstring(L, 1, &len);
|
||||||
|
if (lua_gettop(L) > 1) {
|
||||||
|
const char* options = luaL_checkstring(L, 2);
|
||||||
|
if (strstr(options,"i"))
|
||||||
|
pattern |= PCRE2_CASELESS;
|
||||||
|
if (strstr(options,"m"))
|
||||||
|
pattern |= PCRE2_MULTILINE;
|
||||||
|
if (strstr(options,"s"))
|
||||||
|
pattern |= PCRE2_DOTALL;
|
||||||
|
}
|
||||||
|
pcre2_code* re = pcre2_compile(
|
||||||
|
(PCRE2_SPTR)str,
|
||||||
|
len,
|
||||||
|
pattern,
|
||||||
|
&errorNumber,
|
||||||
|
&errorOffset,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
if (re) {
|
||||||
|
lua_newtable(L);
|
||||||
|
lua_pushlightuserdata(L, re);
|
||||||
|
lua_rawseti(L, -2, 1);
|
||||||
|
luaL_setmetatable(L, "regex");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
PCRE2_UCHAR buffer[256];
|
||||||
|
pcre2_get_error_message(errorNumber, buffer, sizeof(buffer));
|
||||||
|
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 = 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 - 1, opts, md, NULL);
|
||||||
|
if (rc < 0) {
|
||||||
|
pcre2_match_data_free(md);
|
||||||
|
if (rc != PCRE2_ERROR_NOMATCH)
|
||||||
|
luaL_error(L, "regex matching error %d", rc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
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");
|
||||||
|
pcre2_match_data_free(md);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < rc*2; i++)
|
||||||
|
lua_pushnumber(L, ovector[i]+1);
|
||||||
|
pcre2_match_data_free(md);
|
||||||
|
return rc*2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const luaL_Reg lib[] = {
|
||||||
|
{ "compile", f_pcre_compile },
|
||||||
|
{ "cmatch", f_pcre_match },
|
||||||
|
{ "__gc", f_pcre_gc },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
int luaopen_regex(lua_State *L) {
|
||||||
|
luaL_newlib(L, lib);
|
||||||
|
lua_pushliteral(L, "regex");
|
||||||
|
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;
|
||||||
|
}
|
|
@ -38,13 +38,13 @@ static int f_get_size(lua_State *L) {
|
||||||
|
|
||||||
|
|
||||||
static int f_begin_frame(lua_State *L) {
|
static int f_begin_frame(lua_State *L) {
|
||||||
rencache_begin_frame();
|
rencache_begin_frame(L);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int f_end_frame(lua_State *L) {
|
static int f_end_frame(lua_State *L) {
|
||||||
rencache_end_frame();
|
rencache_end_frame(L);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ static int draw_text_subpixel_impl(lua_State *L, bool draw_subpixel) {
|
||||||
replace_color = (RenColor) {0};
|
replace_color = (RenColor) {0};
|
||||||
}
|
}
|
||||||
|
|
||||||
x_subpixel = rencache_draw_text(font_desc, text, x_subpixel, y, color, draw_subpixel, rep_table, replace_color);
|
x_subpixel = rencache_draw_text(L, font_desc, 1, text, x_subpixel, y, color, draw_subpixel, rep_table, replace_color);
|
||||||
lua_pushnumber(L, x_subpixel);
|
lua_pushnumber(L, x_subpixel);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#include <lua.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
|
||||||
#include "api.h"
|
#include "api.h"
|
||||||
#include "fontdesc.h"
|
#include "fontdesc.h"
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
|
@ -62,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);
|
||||||
rencache_free_font(self);
|
font_desc_clear(self);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,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 },
|
||||||
|
@ -109,6 +128,8 @@ static const luaL_Reg lib[] = {
|
||||||
{ "get_width_subpixel", f_get_width_subpixel },
|
{ "get_width_subpixel", f_get_width_subpixel },
|
||||||
{ "get_height", f_get_height },
|
{ "get_height", f_get_height },
|
||||||
{ "subpixel_scale", f_subpixel_scale },
|
{ "subpixel_scale", f_subpixel_scale },
|
||||||
|
{ "get_size", f_get_size },
|
||||||
|
{ "set_size", f_set_size },
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,14 @@ top:
|
||||||
return 4;
|
return 4;
|
||||||
|
|
||||||
case SDL_KEYDOWN:
|
case SDL_KEYDOWN:
|
||||||
|
#ifdef __APPLE__
|
||||||
|
/* on macos 11.2.3 with sdl 2.0.14 the keyup handler for cmd+w below
|
||||||
|
** was not enough. Maybe the quit event started to be triggered from the
|
||||||
|
** keydown handler? In any case, flushing the quit event here too helped. */
|
||||||
|
if ((e.key.keysym.sym == SDLK_w) && (e.key.keysym.mod & KMOD_GUI)) {
|
||||||
|
SDL_FlushEvent(SDL_QUIT);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
lua_pushstring(L, "keypressed");
|
lua_pushstring(L, "keypressed");
|
||||||
lua_pushstring(L, key_name(buf, e.key.keysym.sym));
|
lua_pushstring(L, key_name(buf, e.key.keysym.sym));
|
||||||
return 2;
|
return 2;
|
||||||
|
@ -162,8 +170,7 @@ top:
|
||||||
** we want to flush this event and let the keymapper
|
** we want to flush this event and let the keymapper
|
||||||
** handle this key combination.
|
** handle this key combination.
|
||||||
** Thanks to mathewmariani, taken from his lite-macos github repository. */
|
** Thanks to mathewmariani, taken from his lite-macos github repository. */
|
||||||
if ((e.key.keysym.sym == SDLK_w) && (e.key.keysym.mod & KMOD_GUI))
|
if ((e.key.keysym.sym == SDLK_w) && (e.key.keysym.mod & KMOD_GUI)) {
|
||||||
{
|
|
||||||
SDL_FlushEvent(SDL_QUIT);
|
SDL_FlushEvent(SDL_QUIT);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -194,6 +201,14 @@ top:
|
||||||
return 4;
|
return 4;
|
||||||
|
|
||||||
case SDL_MOUSEMOTION:
|
case SDL_MOUSEMOTION:
|
||||||
|
SDL_PumpEvents();
|
||||||
|
SDL_Event event_plus;
|
||||||
|
while (SDL_PeepEvents(&event_plus, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) {
|
||||||
|
e.motion.x = event_plus.motion.x;
|
||||||
|
e.motion.y = event_plus.motion.y;
|
||||||
|
e.motion.xrel += event_plus.motion.xrel;
|
||||||
|
e.motion.yrel += event_plus.motion.yrel;
|
||||||
|
}
|
||||||
lua_pushstring(L, "mousemoved");
|
lua_pushstring(L, "mousemoved");
|
||||||
lua_pushnumber(L, e.motion.x);
|
lua_pushnumber(L, e.motion.x);
|
||||||
lua_pushnumber(L, e.motion.y);
|
lua_pushnumber(L, e.motion.y);
|
||||||
|
|
|
@ -4,8 +4,25 @@
|
||||||
void set_macos_bundle_resources(lua_State *L)
|
void set_macos_bundle_resources(lua_State *L)
|
||||||
{ @autoreleasepool
|
{ @autoreleasepool
|
||||||
{
|
{
|
||||||
NSString* resource_path = [[NSBundle mainBundle] resourcePath];
|
/* Use resolved executablePath instead of resourcePath to allow lanching
|
||||||
lua_pushstring(L, [resource_path UTF8String]);
|
the lite-xl binary via a symlink, like typically done by Homebrew:
|
||||||
|
|
||||||
|
/usr/local/bin/lite-xl -> /Applications/lite-xl.app/Contents/MacOS/lite-xl
|
||||||
|
|
||||||
|
The resourcePath returns /usr/local in this case instead of
|
||||||
|
/Applications/lite-xl.app/Contents/Resources, which makes later
|
||||||
|
access to the resource files fail. Resolving the symlink to the
|
||||||
|
executable and then the relative path to the expected directory
|
||||||
|
Resources is a workaround for starting the application from both
|
||||||
|
the launcher directly and the command line via the symlink.
|
||||||
|
*/
|
||||||
|
NSString* executable_path = [[NSBundle mainBundle] executablePath];
|
||||||
|
char resolved_path[PATH_MAX + 16 + 1];
|
||||||
|
realpath([executable_path UTF8String], resolved_path);
|
||||||
|
strcat(resolved_path, "/../../Resources");
|
||||||
|
char resource_path[PATH_MAX + 1];
|
||||||
|
realpath(resolved_path, resource_path);
|
||||||
|
lua_pushstring(L, resource_path);
|
||||||
lua_setglobal(L, "MACOS_RESOURCES");
|
lua_setglobal(L, "MACOS_RESOURCES");
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,12 @@ void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsig
|
||||||
font_desc->cache_last_index = 0; /* Normally no need to initialize. */
|
font_desc->cache_last_index = 0; /* Normally no need to initialize. */
|
||||||
}
|
}
|
||||||
|
|
||||||
void font_desc_free(FontDesc *font_desc) {
|
void font_desc_clear(FontDesc *font_desc) {
|
||||||
for (int i = 0; i < font_desc->cache_length; i++) {
|
for (int i = 0; i < font_desc->cache_length; i++) {
|
||||||
ren_free_font(font_desc->cache[i].font);
|
ren_free_font(font_desc->cache[i].font);
|
||||||
}
|
}
|
||||||
font_desc->cache_length = 0;
|
font_desc->cache_length = 0;
|
||||||
|
font_desc->cache_last_index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size) {
|
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsig
|
||||||
int font_desc_alloc_size(const char *filename);
|
int font_desc_alloc_size(const char *filename);
|
||||||
int font_desc_get_tab_size(FontDesc *font_desc);
|
int font_desc_get_tab_size(FontDesc *font_desc);
|
||||||
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size);
|
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size);
|
||||||
void font_desc_free(FontDesc *font_desc);
|
void font_desc_clear(FontDesc *font_desc);
|
||||||
RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale);
|
RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
13
src/main.c
13
src/main.c
|
@ -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"
|
||||||
|
@ -62,8 +63,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
|
||||||
|
@ -159,11 +165,6 @@ init_lua:
|
||||||
enable_momentum_scroll();
|
enable_momentum_scroll();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* We need to clear the rencache commands because we may restarting the application
|
|
||||||
and it could be non-empty. It is important to clear the command buffer because it
|
|
||||||
stores pointers to font_desc objects and these are Lua managed. */
|
|
||||||
rencache_clear();
|
|
||||||
|
|
||||||
const char *init_lite_code = \
|
const char *init_lite_code = \
|
||||||
"local core\n"
|
"local core\n"
|
||||||
"xpcall(function()\n"
|
"xpcall(function()\n"
|
||||||
|
|
|
@ -3,6 +3,7 @@ lite_sources = [
|
||||||
'api/cp_replace.c',
|
'api/cp_replace.c',
|
||||||
'api/renderer.c',
|
'api/renderer.c',
|
||||||
'api/renderer_font.c',
|
'api/renderer_font.c',
|
||||||
|
'api/regex.c',
|
||||||
'api/system.c',
|
'api/system.c',
|
||||||
'renderer.c',
|
'renderer.c',
|
||||||
'renwindow.c',
|
'renwindow.c',
|
||||||
|
@ -18,7 +19,7 @@ endif
|
||||||
executable('lite',
|
executable('lite',
|
||||||
lite_sources + lite_rc,
|
lite_sources + lite_rc,
|
||||||
include_directories: [lite_include, font_renderer_include],
|
include_directories: [lite_include, font_renderer_include],
|
||||||
dependencies: [lua_dep, sdl_dep, libm, libdl, libx11],
|
dependencies: [lua_dep, sdl_dep, pcre2_dep, libm, libdl, libx11],
|
||||||
c_args: lite_cargs,
|
c_args: lite_cargs,
|
||||||
link_with: libfontrenderer,
|
link_with: libfontrenderer,
|
||||||
link_args: lite_link_args,
|
link_args: lite_link_args,
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <lauxlib.h>
|
||||||
#include "rencache.h"
|
#include "rencache.h"
|
||||||
|
|
||||||
/* a cache over the software renderer -- all drawing operations are stored as
|
/* a cache over the software renderer -- all drawing operations are stored as
|
||||||
|
@ -14,7 +16,7 @@
|
||||||
#define COMMAND_BUF_SIZE (1024 * 512)
|
#define COMMAND_BUF_SIZE (1024 * 512)
|
||||||
#define COMMAND_BARE_SIZE offsetof(Command, text)
|
#define COMMAND_BARE_SIZE offsetof(Command, text)
|
||||||
|
|
||||||
enum { FREE_FONT, SET_CLIP, DRAW_TEXT, DRAW_RECT, DRAW_TEXT_SUBPIXEL };
|
enum { SET_CLIP, DRAW_TEXT, DRAW_RECT, DRAW_TEXT_SUBPIXEL };
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int8_t type;
|
int8_t type;
|
||||||
|
@ -30,6 +32,15 @@ typedef struct {
|
||||||
char text[0];
|
char text[0];
|
||||||
} Command;
|
} Command;
|
||||||
|
|
||||||
|
#define FONT_REFS_MAX 12
|
||||||
|
struct FontRef {
|
||||||
|
FontDesc *font_desc;
|
||||||
|
int index;
|
||||||
|
};
|
||||||
|
typedef struct FontRef FontRef;
|
||||||
|
FontRef font_refs[FONT_REFS_MAX];
|
||||||
|
int font_refs_len = 0;
|
||||||
|
|
||||||
|
|
||||||
static unsigned cells_buf1[CELLS_X * CELLS_Y];
|
static unsigned cells_buf1[CELLS_X * CELLS_Y];
|
||||||
static unsigned cells_buf2[CELLS_X * CELLS_Y];
|
static unsigned cells_buf2[CELLS_X * CELLS_Y];
|
||||||
|
@ -45,6 +56,33 @@ static bool show_debug;
|
||||||
static inline int min(int a, int b) { return a < b ? a : b; }
|
static inline int min(int a, int b) { return a < b ? a : b; }
|
||||||
static inline int max(int a, int b) { return a > b ? a : b; }
|
static inline int max(int a, int b) { return a > b ? a : b; }
|
||||||
|
|
||||||
|
static int font_refs_add(lua_State *L, FontDesc *font_desc, int index) {
|
||||||
|
for (int i = 0; i < font_refs_len; i++) {
|
||||||
|
if (font_refs[i].font_desc == font_desc) {
|
||||||
|
return font_refs[i].index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font_refs_len >= FONT_REFS_MAX) {
|
||||||
|
fprintf(stderr, "Warning: (" __FILE__ "): exhausted font reference buffer\n");
|
||||||
|
return LUA_NOREF;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushvalue(L, index);
|
||||||
|
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
|
||||||
|
font_refs[font_refs_len++] = (FontRef) { font_desc, ref };
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void font_refs_clear(lua_State *L) {
|
||||||
|
for (int i = 0; i < font_refs_len; i++) {
|
||||||
|
luaL_unref(L, LUA_REGISTRYINDEX, font_refs[i].index);
|
||||||
|
}
|
||||||
|
font_refs_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 32bit fnv-1a hash */
|
/* 32bit fnv-1a hash */
|
||||||
#define HASH_INITIAL 2166136261
|
#define HASH_INITIAL 2166136261
|
||||||
|
|
||||||
|
@ -115,12 +153,6 @@ void rencache_show_debug(bool enable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void rencache_free_font(FontDesc *font_desc) {
|
|
||||||
Command *cmd = push_command(FREE_FONT, COMMAND_BARE_SIZE);
|
|
||||||
if (cmd) { cmd->font_desc = font_desc; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void rencache_set_clip_rect(RenRect rect) {
|
void rencache_set_clip_rect(RenRect rect) {
|
||||||
Command *cmd = push_command(SET_CLIP, COMMAND_BARE_SIZE);
|
Command *cmd = push_command(SET_CLIP, COMMAND_BARE_SIZE);
|
||||||
if (cmd) { cmd->rect = intersect_rects(rect, screen_rect); }
|
if (cmd) { cmd->rect = intersect_rects(rect, screen_rect); }
|
||||||
|
@ -136,7 +168,7 @@ void rencache_draw_rect(RenRect rect, RenColor color) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int rencache_draw_text(FontDesc *font_desc,
|
int rencache_draw_text(lua_State *L, FontDesc *font_desc, int font_index,
|
||||||
const char *text, int x, int y, RenColor color, bool draw_subpixel,
|
const char *text, int x, int y, RenColor color, bool draw_subpixel,
|
||||||
CPReplaceTable *replacements, RenColor replace_color)
|
CPReplaceTable *replacements, RenColor replace_color)
|
||||||
{
|
{
|
||||||
|
@ -148,7 +180,7 @@ int rencache_draw_text(FontDesc *font_desc,
|
||||||
rect.width = ren_font_subpixel_round(w_subpixel, subpixel_scale, 0);
|
rect.width = ren_font_subpixel_round(w_subpixel, subpixel_scale, 0);
|
||||||
rect.height = ren_get_font_height(font_desc);
|
rect.height = ren_get_font_height(font_desc);
|
||||||
|
|
||||||
if (rects_overlap(screen_rect, rect)) {
|
if (rects_overlap(screen_rect, rect) && font_refs_add(L, font_desc, font_index) >= 0) {
|
||||||
int sz = strlen(text) + 1;
|
int sz = strlen(text) + 1;
|
||||||
Command *cmd = push_command(draw_subpixel ? DRAW_TEXT_SUBPIXEL : DRAW_TEXT, COMMAND_BARE_SIZE + sz);
|
Command *cmd = push_command(draw_subpixel ? DRAW_TEXT_SUBPIXEL : DRAW_TEXT, COMMAND_BARE_SIZE + sz);
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
|
@ -173,7 +205,7 @@ void rencache_invalidate(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void rencache_begin_frame(void) {
|
void rencache_begin_frame(lua_State *L) {
|
||||||
/* reset all cells if the screen width/height has changed */
|
/* reset all cells if the screen width/height has changed */
|
||||||
int w, h;
|
int w, h;
|
||||||
ren_get_size(&w, &h);
|
ren_get_size(&w, &h);
|
||||||
|
@ -182,6 +214,7 @@ void rencache_begin_frame(void) {
|
||||||
screen_rect.height = h;
|
screen_rect.height = h;
|
||||||
rencache_invalidate();
|
rencache_invalidate();
|
||||||
}
|
}
|
||||||
|
font_refs_clear(L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -214,7 +247,7 @@ static void push_rect(RenRect r, int *count) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void rencache_end_frame(void) {
|
void rencache_end_frame(lua_State *L) {
|
||||||
/* update cells from commands */
|
/* update cells from commands */
|
||||||
Command *cmd = NULL;
|
Command *cmd = NULL;
|
||||||
RenRect cr = screen_rect;
|
RenRect cr = screen_rect;
|
||||||
|
@ -253,7 +286,6 @@ void rencache_end_frame(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* redraw updated regions */
|
/* redraw updated regions */
|
||||||
bool has_free_commands = false;
|
|
||||||
for (int i = 0; i < rect_count; i++) {
|
for (int i = 0; i < rect_count; i++) {
|
||||||
/* draw */
|
/* draw */
|
||||||
RenRect r = rect_buf[i];
|
RenRect r = rect_buf[i];
|
||||||
|
@ -262,9 +294,6 @@ void rencache_end_frame(void) {
|
||||||
cmd = NULL;
|
cmd = NULL;
|
||||||
while (next_command(&cmd)) {
|
while (next_command(&cmd)) {
|
||||||
switch (cmd->type) {
|
switch (cmd->type) {
|
||||||
case FREE_FONT:
|
|
||||||
has_free_commands = true;
|
|
||||||
break;
|
|
||||||
case SET_CLIP:
|
case SET_CLIP:
|
||||||
ren_set_clip_rect(intersect_rects(cmd->rect, r));
|
ren_set_clip_rect(intersect_rects(cmd->rect, r));
|
||||||
break;
|
break;
|
||||||
|
@ -296,16 +325,6 @@ void rencache_end_frame(void) {
|
||||||
ren_update_rects(rect_buf, rect_count);
|
ren_update_rects(rect_buf, rect_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* free fonts */
|
|
||||||
if (has_free_commands) {
|
|
||||||
cmd = NULL;
|
|
||||||
while (next_command(&cmd)) {
|
|
||||||
if (cmd->type == FREE_FONT) {
|
|
||||||
font_desc_free(cmd->font_desc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* swap cell buffer and reset */
|
/* swap cell buffer and reset */
|
||||||
unsigned *tmp = cells;
|
unsigned *tmp = cells;
|
||||||
cells = cells_prev;
|
cells = cells_prev;
|
||||||
|
@ -313,6 +332,3 @@ void rencache_end_frame(void) {
|
||||||
command_buf_idx = 0;
|
command_buf_idx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rencache_clear() {
|
|
||||||
command_buf_idx = 0;
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,17 +2,16 @@
|
||||||
#define RENCACHE_H
|
#define RENCACHE_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <lua.h>
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
|
|
||||||
void rencache_show_debug(bool enable);
|
void rencache_show_debug(bool enable);
|
||||||
void rencache_free_font(FontDesc *font_desc);
|
|
||||||
void rencache_set_clip_rect(RenRect rect);
|
void rencache_set_clip_rect(RenRect rect);
|
||||||
void rencache_draw_rect(RenRect rect, RenColor color);
|
void rencache_draw_rect(RenRect rect, RenColor color);
|
||||||
int rencache_draw_text(FontDesc *font_desc, const char *text, int x, int y, RenColor color,
|
int rencache_draw_text(lua_State *L, FontDesc *font_desc, int font_index, const char *text, int x, int y, RenColor color,
|
||||||
bool draw_subpixel, CPReplaceTable *replacements, RenColor replace_color);
|
bool draw_subpixel, CPReplaceTable *replacements, RenColor replace_color);
|
||||||
void rencache_invalidate(void);
|
void rencache_invalidate(void);
|
||||||
void rencache_begin_frame(void);
|
void rencache_begin_frame(lua_State *L);
|
||||||
void rencache_end_frame(void);
|
void rencache_end_frame(lua_State *L);
|
||||||
void rencache_clear();
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -309,7 +309,8 @@ void ren_draw_rect(RenRect rect, RenColor color) {
|
||||||
int dr = surface->w - (x2 - x1);
|
int dr = surface->w - (x2 - x1);
|
||||||
|
|
||||||
if (color.a == 0xff) {
|
if (color.a == 0xff) {
|
||||||
rect_draw_loop(color);
|
SDL_Rect rect = { x1, y1, x2 - x1, y2 - y1 };
|
||||||
|
SDL_FillRect(surface, &rect, SDL_MapRGBA(surface->format, color.r, color.g, color.b, color.a));
|
||||||
} else {
|
} else {
|
||||||
rect_draw_loop(blend_pixel(*d, color));
|
rect_draw_loop(blend_pixel(*d, color));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue