Compare commits

..

26 Commits

Author SHA1 Message Date
Francesco Abbate ed483106ec Update dmon.h with new macOS fixes 2021-10-03 00:05:33 +02:00
Francesco Abbate b83c1ade9b Fix in dmon.h for macOS path case sensitivity 2021-10-03 00:05:33 +02:00
Francesco Abbate f246aa2ee8 Fix error in dir_rescan_add_job 2021-10-03 00:05:33 +02:00
Francesco Abbate 36de78d6df Remove calls to reschedule_project_scan 2021-10-03 00:05:33 +02:00
Francesco Abbate cd0f4144e2 Fix call to missing project_files_limit 2021-10-03 00:05:33 +02:00
Francesco Abbate 595c0a5833 remove dev note 2021-10-03 00:05:33 +02:00
Francesco Abbate 14ca590dc0 Use new dmon version win watch_add/rm
Include changes in dmon not yet merged into master.
2021-10-03 00:04:30 +02:00
Francesco Abbate ba72613f60 Add missing pthread dependency 2021-10-03 00:04:30 +02:00
Francesco Abbate b7ef9a5609 Fix files limited project with dir monintoring
Changed approach to files limited project. Now we keep into the
top-level dir a list of subdirectories to be shown. When in file
limited mode we will not scan subdirectories unless they are in
the list of shown subdirectories.

With the new mechanism the function get_subdirectory_files always
recurse into subdirectories by default but is able to figure out
to stop recursing into subdirectories for files limited project.

The new mechanism is more robust of the previous one. Now the
rescan of subdirectories is compatible with files limited project.
2021-10-03 00:04:30 +02:00
Francesco Abbate db24dbc3a0 Fix a new things about project rescan
Add a flag core.redraw to force redraw when rescan is done.

Inhibit recursion when files_limit is reached.

Still doesn't work correctly for files limited directories.
2021-10-03 00:04:30 +02:00
Francesco Abbate 6c5abdd95d Smarter algorithm to patch files list
New algorithm use the fact that files list are always
sorted to optimize the table's insertions and removals.
2021-10-03 00:04:29 +02:00
Francesco Abbate 8f36b776b7 Fix error in rescan list replace 2021-10-03 00:04:29 +02:00
Francesco Abbate b992c147c0 Ensure all project files are correctly filtered 2021-10-03 00:04:29 +02:00
Francesco Abbate 0f3fb4d77d Fix a few things about dmon
Ensure that we call coroutine.yield when scanning recursively.

Do not use a weak-key based on project dir when adding the job for rescan.
Since "dir" was not unique many threads were missing.

Ensure we do not block waiting for events if there are pending rescan.
2021-10-03 00:04:29 +02:00
Francesco Abbate d36293ff60 Ensure directory is rescanned after the first read 2021-10-03 00:04:29 +02:00
Francesco Abbate 66bedbffb9 Implement project files rescan on dir changes
In theory the dmon based directory monitoring is enough to ensure that
the list of project files is always correct. In reality some events
may be missing and the project files list may get disaligned with the
real list of files.

To avoid the problem we add an additional rescan to be done later in a
thread on any project subdirectory affected by an event of directory of
file change.

In the rescan found the same files already present the thread terminates.
If a difference is found the files list is modified and a new rescan is
scheduled.
2021-10-03 00:04:29 +02:00
Francesco Abbate 83c5d963b8 More accurate path compare function 2021-10-03 00:04:29 +02:00
Francesco Abbate cb4c7d397d Update dmon from septag/dmon commit 74bbd93b
The new version includes fixes from jgmdev, github PR:

https://github.com/septag/dmon/pull/11

to solve incorrect behavior on linux not reporting directory creation.

Includes also a further revision from septag.
2021-10-03 00:04:29 +02:00
Francesco Abbate becd817ec4 Show max files warning message for initial project
If the max number of files limit is achieved when the application
is starting the StatusView is not yet configured so we cannot
show the warning.

We show the warning in the function scanning the directory only if
the StatusView is up. On the other side, when the application starts
it will check if the initial project dir hit the max files limit and
show the warning if needed.
2021-10-03 00:04:29 +02:00
Francesco Abbate 019280f2ed Fix several problem with directory update
When scanning a subdirectory on-demand ensure files aready present
are not added twice. Files or directory can be already present due
to dir monitoring create message.

Fix check for ignore files when adding a file to respond to a dir monitor
event to use each part of the file's path.

Fix C function to compare files for treeview placement.
2021-10-03 00:04:29 +02:00
Francesco Abbate 593916ada7 Fix bug with expanding directory when file limited
Introduce a new field in items generated by TreeView:each_item()
to point "dir" to the toplevel directory entry.

In this was we can simplify the code and know if the toplevel
directory is files limited.
2021-10-03 00:04:28 +02:00
Francesco Abbate 865111738a Treat watch dir errors and fix various things
Verity if dmon_watch returns an error.

Add a check if an added file for which we received a create event is
ignored based on the user's config.

Add some explanatory comments in the code.
2021-10-03 00:04:28 +02:00
Francesco Abbate 7b7dfe8c75 Remove the treeview check for modified files
In the treeview the implementation was checking the files list
to detect if it changed because of a project scan. Since we removed
the project scan we no longer need the check.

Removed the TreeView's self.last table that stores previous files
object by top-level directories.
2021-10-03 00:04:28 +02:00
Francesco Abbate 7aca4e6ba2 Remove the project scan thread
Since the directory monitoring is now basically working we remove the
project scan thread periodically scanning the project directory.

Each project's directory is scanned only once at the beginning when
calling the function `core.add_project_directory` and is updated
incrementally when directory change events are treated.

The config variable `project_scan_rate` is removed as well as the
function `core.reschedule_project_scan`.
2021-10-03 00:04:28 +02:00
Francesco Abbate c46781dbed Update dmon from septag/dmon with fix for linux
Update from https://github.com/septag/dmon, commit: 48234fc2 to
include a fix for linux.
2021-10-03 00:04:28 +02:00
Francesco Abbate fe2d0b5237 First integration of dmon for directory monitoring 2021-10-03 00:04:28 +02:00
98 changed files with 5132 additions and 5381 deletions

35
.github/labeler.yml vendored
View File

@ -1,35 +0,0 @@
"Category: CI":
- .github/workflows/*
"Category: Meta":
- ./*
- .github/*
- .github/ISSUE_TEMPLATE/*
- .github/PULL_REQUEST_TEMPLATE/*
- .gitignore
"Category: Build System":
- meson.build
- meson_options.txt
- subprojects/*
"Category: Documentation":
- docs/**/*
"Category: Resources":
- resources/**/*
"Category: Themes":
- data/colors/*
"Category: Lua Core":
- data/core/**/*
"Category: Fonts":
- data/fonts/*
"Category: Plugins":
- data/plugins/*
"Category: C Core":
- src/**/*

View File

@ -1,16 +0,0 @@
name: "Pull Request Labeler"
on:
- pull_request_target
permissions:
pull-requests: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Apply Type Label
uses: actions/labeler@v3
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: "" # works around actions/labeler#104

7
.gitignore vendored
View File

@ -3,6 +3,7 @@ build*/
lhelper/
submodules/
subprojects/lua/
subprojects/libagg/
subprojects/reproc/
/appimage*
.ccls-cache
@ -18,9 +19,3 @@ compile_commands.json
error.txt
lite-xl*
LiteXL*
lite
.config/
*.lha
release_files
*.o

View File

@ -1,82 +0,0 @@
#
# Project: Lite XL
#
# Created on: 26-12-2021
#
LiteXL_OBJ := \
src/dirmonitor.o src/main.o src/rencache.o src/renderer.o \
src/renwindow.o src/api/api.o src/api/regex.o \
src/api/renderer.o src/api/system.o src/platform/amigaos4.o
outfile := lite
compiler := gcc
cxxcompiler := g++
INCPATH := -Isrc -Ilib/dmon -I/sdk/local/newlib/include/SDL2 -I/sdk/local/common/include/freetype2
DFLAGS := -D__USE_INLINE__ -DLITE_XL_DATA_USE_EXEDIR
# -DLITE_USE_SDL_RENDERER
# -Wextra -Wall
CFLAGS := -Werror -Wwrite-strings -O3 -g -std=gnu11 -fno-strict-aliasing
# "-gstabs -finstrument-functions -fno-inline -DPROFILING"
LFLAGS := -mcrt=newlib -static-libgcc -static-libstdc++ -lauto -lpcre2 -lSDL2 -llua -lagg -lfreetype -lm -lunix -lpthread -athread=native
# " -lprofyle"
.PHONY: LiteXL clean release
default: LiteXL
clean:
@echo "Cleaning compiler objects..."
@rm -f $(LiteXL_OBJ)
LiteXL: $(LiteXL_OBJ)
@echo "Linking LiteXL"
@$(cxxcompiler) -o $(outfile) $(LiteXL_OBJ) $(LFLAGS)
.c.o:
@echo "Compiling $<"
@$(compiler) -c $< -o $*.o $(CFLAGS) $(INCPATH) $(DFLAGS)
src/dirmonitor.o: src/dirmonitor.c src/platform/amigaos4.h
src/main.o: src/main.c src/api/api.h src/rencache.h \
src/renderer.h src/platform/amigaos4.h src/dirmonitor.h
src/rencache.o: src/rencache.c
src/renderer.o: src/renderer.c
src/renwindow.o: src/renwindow.c
src/api/api.o: src/api/api.c
src/api/regex.o: src/api/regex.c
src/api/renderer.o: src/api/renderer.c
src/api/system.o: src/api/system.c
src/platform/amigaos4.o: src/platform/amigaos4.c
release:
mkdir -p release/LiteXL2
cp release_files/* release/LiteXL2/ -r
mv release/LiteXL2/LiteXL2.info release/
cp data release/LiteXL2/ -r
cp changelog.md release/LiteXL2/
cp lite release/LiteXL2/
strip release/LiteXL2/lite
cp README.md release/LiteXL2/
cp README_OS4.md release/LiteXL2/
cp LICENSE release/LiteXL2/
lha -aeqr3 a LiteXL2_OS4.lha release/

View File

@ -18,9 +18,9 @@ Lite XL has support for high DPI display on Windows and Linux and,
since 1.16.7 release, it supports **retina displays** on macOS.
Please note that Lite XL is compatible with lite for most plugins and all color themes.
We provide a separate lite-xl-plugins repository for Lite XL, because in some cases
We provide a separate lite-plugins repository for Lite XL, because in some cases
some adaptations may be needed to make them work better with Lite XL.
The repository with modified plugins is https://github.com/lite-xl/lite-xl-plugins.
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].
@ -77,61 +77,13 @@ DESTDIR="$(pwd)/Lite XL.app" meson install --skip-subprojects -C build
Please note that the package is relocatable to any prefix and the option prefix
affects only the place where the application is actually installed.
## Installing Prebuilt
Head over to [releases](https://github.com/lite-xl/lite-xl/releases) and download the version for your operating system.
### Linux
Unzip the file and `cd` into the `lite-xl` directory:
```sh
tar -xzf <file>
cd lite-xl
```
To run lite-xl without installing:
```sh
cd bin
./lite-xl
```
To install lite-xl copy files over into appropriate directories:
```sh
mkdir -p $HOME/.local/bin && cp bin/lite-xl $HOME/.local/bin
cp -r share $HOME/.local
```
If `$HOME/.local/bin` is not in PATH:
```sh
echo -e 'export PATH=$PATH:$HOME/.local/bin' >> $HOME/.bashrc
```
To get the icon to show up in app launcher:
```sh
xdg-desktop-menu forceupdate
```
You may need to logout and login again to see icon in app launcher.
To uninstall just run:
```sh
rm -f $HOME/.local/bin/lite-xl
rm -rf $HOME/.local/share/icons/hicolor/scalable/apps/lite-xl.svg \
$HOME/.local/share/applications/org.lite_xl.lite_xl.desktop \
$HOME/.local/share/metainfo/org.lite_xl.lite_xl.appdata.xml \
$HOME/.local/share/lite-xl
```
## Contributing
Any additional functionality that can be added through a plugin should be done
as a plugin, after which a pull request to the [Lite XL plugins repository] can be made.
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.
@ -147,13 +99,14 @@ See the [licenses] file for details on licenses used by the required dependencie
[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.com
[build]: https://lite-xl.com/en/documentation/build/
[Get Lite XL]: https://github.com/lite-xl/lite-xl/releases/latest
[Get plugins]: https://github.com/lite-xl/lite-xl-plugins
[Get color themes]: https://github.com/lite-xl/lite-xl-colors
[changelog]: https://github.com/lite-xl/lite-xl/blob/master/changelog.md
[Lite XL plugins repository]: https://github.com/lite-xl/lite-xl-plugins
[colors repository]: https://github.com/lite-xl/lite-xl-colors
[website]: https://lite-xl.github.io
[build]: https://lite-xl.github.io/en/documentation/build/
[Get Lite XL]: https://github.com/franko/lite-xl/releases/latest
[Get plugins]: https://github.com/franko/lite-plugins
[Get color themes]: https://github.com/rxi/lite-colors
[changelog]: https://github.com/franko/lite-xl/blob/master/changelog.md
[Lite XL plugins repository]: https://github.com/franko/lite-plugins
[plugins repository]: https://github.com/rxi/lite-plugins
[colors repository]: https://github.com/rxi/lite-colors
[LICENSE]: LICENSE
[licenses]: licenses/licenses.md

View File

@ -1,204 +0,0 @@
# Lite XL v2 for AmigaOS 4.1 FE
Lite XL is a lightweight text editor written in Lua.
The port is not perfect and might has issues here and there. For example
the filesystem notifications are not working yet. So when you make changes
at a project folder those will not be reflected in Lite XL automatically.
It might crash from time to time, if there is a path problem, but overall
it works pretty well. This is my daily editor for any kind of development.
## New features against Lite XL v1
- Faster file scrolling
- Faster switch between tabs
- Reposition tabs at the side or at the bottom of other tabs, making
multiple columns/rows of opened files
- Multiple cursor editing
- Better font manipulation and appearance
- Faster transitions
## Installation
You can extract the Lite XL archive wherever you want and run the *lite*
editor.
## Configuration folder
This editor creates a `.config` folder where the configuration is saved, as
well as plugins, themes etc.. By default this AmigaOS 4.1 FE version uses the
executable folder, but if you want to ovveride it, create an ENV variable
named `HOME` and set there your path.
You can check if there is one already set by executing the following command
in a shell
```
GetEnv HOME
```
If there is one set, then you will see the path at the output.
Otherwise, you can set your home path be executing the following command.
Change the path to the one of your preference.
```
SetEnv SAVE HOME "Sys:home/"
```
## Addons
### Colors
Colors are lua files that set the color scheme of the editor. There are
light and dark themes for you to choose.
To install and use them you have to copy the ones you would like from
`addons/colors/light` or `addons/colors/dark` into the folder
`.config/lite-xl/colors/`. Don't add light or dark folders. Just copy the
.lua files in there.
Then you have to start Lite XL and open your configuration by clicking
at the cog icon at the toolbar (bottom left sixth icon). Go at the line
that looks like below
```
-- core.reload_module("colors.summer")
```
and change the `summer` with the name of your color theme. Also, remove
the two dashes `--` at the start of the line and save the file. If you
did everything right, the color schema should change instantly.
The themes can also be found at
https://github.com/lite-xl/lite-xl-colors
### Plugins
The Lite XL that you are using on AmigaOS 4 is based on version 2.0.4
and not the latest version that is available by the development team.
This means that some of the latest plugins might not working at all
or need some modifications to work.
To make it easier for you, I gathered some of the plugins that are working
well, and I included them under `addons/plugins`. For you to install the
ones you would like to use, you have to copy the `.lua` files into the
folder `.config/lite-xl/plugins/` and restart the editor.
Please, choose wisely, because adding all the plugins might make the editor
slower on your system. I would recommend you add only those that you really
need.
The included plugins are the following:
**autoinsert**
Automatically inserts closing brackets and quotes. Also allows selected
text to be wrapped with brackets or quotes.
**autowrap**
Automatically hardwraps lines when typing
**bigclock**
Shows the current time and date in a view with large text
**bracketmatch**
Underlines matching pair for bracket under the caret
**colorpreview**
Underlays color values (eg. `#ff00ff` or `rgb(255, 0, 255)`) with their
resultant color.
**eofnewline-xl**
Make sure the file ends with one blank line.
**ephemeral_tabs**
Preview tabs. Opening a doc will replace the contents of the preview tab.
Marks tabs as non-preview on any change or tab double clicking.
**ghmarkdown**
Opens a preview of the current markdown file in a browser window
**indentguide**
Adds indent guides
**language_make**
Syntax for the Make build system language
**language_sh**
Syntax for shell scripting language
**lfautoinsert**
Automatically inserts indentation and closing bracket/text after newline
**markers**
Add markers to docs and jump between them quickly
**minimap**
Shows a minimap on the right-hand side of the docview. Please note that
this plugin will make the editor slower on file loading and scrolling.
**navigate**
Allows moving back and forward between document positions, reducing the
amount of scrolling
**rainbowparen**
Show nesting of parentheses with rainbow colours
**restoretabs**
Keep a list of recently closed tabs, and restore the tab in order on
cntrl+shift+t.
**selectionhighlight**
Highlights regions of code that match the current selection
**smallclock**
It adds a small clock at the bottom right corner.
## Tips and tricks
### Transitions
If you want to disable the transitions and make the editor faster,
open your configuration file by clicking at the cog icon at the toolbar
(bottom left, 6th icon) and add the following line at the end of the file,
and then save it. You might need to restart your editor (CTRL+SHIFT+R)
```
config.transitions = false
```
### Hide files from the file list
If you would like to hide files or whole folder from the left side bar list,
open your configuration by clicking at the cog icon at the toolbar
(bottom left sixth icon) and add the followline at the end of the file and
save it. This hides all the files that start with a dot, and all the `.info`
files. You might need to restart your editor (CTRL+SHIFT+R)
```
config.ignore_files = {"^%.", "%.info$"}
```
You can add as many rules as you want in there, to hide files or
folders, as you like.
## I would like to thank
- IconDesigner for the proper glow icons that are included in the release
- Capehill for his tireless work on SDL port
- Michael Trebilcock for his port on liblua
- Lite XL original team for being helpful and providing info
Without all the above Lite XL would not be possible
## Support
If you enjoy what I am doing and would like to keep me up during the night,
please consider to buy me a coffee at:
https://ko-fi.com/walkero
## Known issues
You can find the known issues at
https://git.walkero.gr/walkero/lite-xl/issues
# Changelog
## [2.0.3r1] - 2022-03-30
### Changed
- Applied all the necessary changes to make it run under AmigaOS 4.1 FE
- Fixes and changes
# Disclaimer
YOU MAY USE IT AT YOUR OWN RISK!
I will not be held responsible for any data loss or problem you might get
by using this software.

Binary file not shown.

View File

@ -1,31 +1,5 @@
This files document the changes done in Lite XL for each release.
### 2.0.3
Replace periodic rescan of project folder with a notification based system using the
[dmon library](https://github.com/septag/dmon). Improves performance especially for
large project folders since the application no longer needs to rescan.
The application also reports immediatly any change in the project directory even
when the application is unfocused.
Improved find-replace reverse and forward search.
Fixed a bug in incremental syntax highlighting affecting documents with multiple-lines
comments or strings.
The application now always shows the tabs in the documents' view even when a single
document is opened. Can be changed with the option `config.always_show_tabs`.
Fix problem with numeric keypad function keys not properly working.
Fix problem with pixel not correctly drawn at the window's right edge.
Treat correctly and open network paths on Windows.
Add some improvements for very slow network filesystems.
Fix problem with python syntax highliting, contributed by @dflock.
### 2.0.2
Fix problem project directory when starting the application from Launcher on macOS.

View File

@ -41,14 +41,11 @@ function command.get_all_valid()
return res
end
function command.is_valid(name, ...)
return command.map[name] and command.map[name].predicate(...)
end
local function perform(name, ...)
local function perform(name)
local cmd = command.map[name]
if cmd and cmd.predicate(...) then
cmd.perform(...)
if cmd and cmd.predicate() then
cmd.perform()
return true
end
return false

View File

@ -6,12 +6,10 @@ local LogView = require "core.logview"
local fullscreen = false
local restore_title_view = false
local function suggest_directory(text)
text = common.home_expand(text)
return common.home_encode_list((text == "" or text == common.home_expand(common.dirname(core.project_dir)))
and core.recent_projects or common.dir_path_suggest(text))
return common.home_encode_list(text == "" and core.recent_projects or common.dir_path_suggest(text))
end
command.add(nil, {
@ -29,12 +27,9 @@ command.add(nil, {
["core:toggle-fullscreen"] = function()
fullscreen = not fullscreen
if fullscreen then
restore_title_view = core.title_view.visible
end
system.set_window_mode(fullscreen and "fullscreen" or "normal")
core.show_title_bar(not fullscreen and restore_title_view)
core.title_view:configure_hit_test(not fullscreen and restore_title_view)
core.show_title_bar(not fullscreen)
core.title_view:configure_hit_test(not fullscreen)
end,
["core:reload-module"] = function()
@ -154,7 +149,7 @@ command.add(nil, {
["core:change-project-folder"] = function()
local dirname = common.dirname(core.project_dir)
if dirname then
core.command_view:set_text(common.home_encode(dirname))
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
end
core.command_view:enter("Change Project Folder", function(text, item)
text = system.absolute_path(common.home_expand(item and item.text or text))
@ -171,7 +166,7 @@ command.add(nil, {
["core:open-project-folder"] = function()
local dirname = common.dirname(core.project_dir)
if dirname then
core.command_view:set_text(common.home_encode(dirname))
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
end
core.command_view:enter("Open Project", function(text, item)
text = common.home_expand(item and item.text or text)

View File

@ -16,6 +16,14 @@ local function doc()
end
local function get_indent_string()
if config.tab_type == "hard" then
return "\t"
end
return string.rep(" ", config.indent_size)
end
local function doc_multiline_selections(sort)
local iter, state, idx, line1, col1, line2, col2 = doc():get_selections(sort)
return function()
@ -74,31 +82,6 @@ local function split_cursor(direction)
core.blink_reset()
end
local function set_cursor(x, y, snap_type)
local line, col = dv():resolve_screen_position(x, y)
doc():set_selection(line, col, line, col)
if snap_type == "word" or snap_type == "lines" then
command.perform("doc:select-" .. snap_type)
end
dv().mouse_selecting = { line, col, snap_type }
core.blink_reset()
end
local selection_commands = {
["doc:cut"] = function()
cut_or_copy(true)
end,
["doc:copy"] = function()
cut_or_copy(false)
end,
["doc:select-none"] = function()
local line, col = doc():get_selection()
doc():set_selection(line, col)
end
}
local commands = {
["doc:undo"] = function()
doc():undo()
@ -108,6 +91,14 @@ local commands = {
doc():redo()
end,
["doc:cut"] = function()
cut_or_copy(true)
end,
["doc:copy"] = function()
cut_or_copy(false)
end,
["doc:paste"] = function()
local clipboard = system.get_clipboard()
-- If the clipboard has changed since our last look, use that instead
@ -156,12 +147,11 @@ local commands = {
end,
["doc:backspace"] = function()
local _, indent_size = doc():get_indent_info()
for idx, line1, col1, line2, col2 in doc():get_selections() do
if line1 == line2 and col1 == col2 then
local text = doc():get_text(line1, 1, line1, col1)
if #text >= indent_size and text:find("^ *$") then
doc():delete_to_cursor(idx, 0, -indent_size)
if #text >= config.indent_size and text:find("^ *$") then
doc():delete_to_cursor(idx, 0, -config.indent_size)
return
end
end
@ -173,6 +163,11 @@ local commands = {
doc():set_selection(1, 1, math.huge, math.huge)
end,
["doc:select-none"] = function()
local line, col = doc():get_selection()
doc():set_selection(line, col)
end,
["doc:select-lines"] = function()
for idx, line1, _, line2 in doc():get_selections(true) do
append_line_if_last_line(line2)
@ -266,7 +261,7 @@ local commands = {
["doc:toggle-line-comments"] = function()
local comment = doc().syntax.comment
if not comment then return end
local indentation = doc():get_indent_string()
local indentation = get_indent_string()
local comment_text = comment .. " "
for idx, line1, _, line2 in doc_multiline_selections(true) do
local uncomment = true
@ -393,30 +388,6 @@ local commands = {
os.remove(filename)
core.log("Removed \"%s\"", filename)
end,
["doc:select-to-cursor"] = function(x, y, clicks)
local line1, col1 = select(3, doc():get_selection())
local line2, col2 = dv():resolve_screen_position(x, y)
dv().mouse_selecting = { line1, col1, nil }
doc():set_selection(line2, col2, line1, col1)
end,
["doc:set-cursor"] = function(x, y)
set_cursor(x, y, "set")
end,
["doc:set-cursor-word"] = function(x, y)
set_cursor(x, y, "word")
end,
["doc:set-cursor-line"] = function(x, y, clicks)
set_cursor(x, y, "lines")
end,
["doc:split-cursor"] = function(x, y, clicks)
local line, col = dv():resolve_screen_position(x, y)
doc():add_selection(line, col, line, col)
end,
["doc:create-cursor-previous-line"] = function()
split_cursor(-1)
@ -476,6 +447,3 @@ commands["doc:move-to-next-char"] = function()
end
command.add("core.docview", commands)
command.add(function()
return core.active_view:is(DocView) and core.active_view.doc:has_any_selection()
end ,selection_commands)

View File

@ -7,15 +7,15 @@ local DocView = require "core.docview"
local CommandView = require "core.commandview"
local StatusView = require "core.statusview"
local last_view, last_fn, last_text, last_sel
local max_last_finds = 50
local last_finds, last_view, last_fn, last_text, last_sel
local case_sensitive = config.find_case_sensitive or false
local find_regex = config.find_regex or false
local found_expression
local function doc()
local is_DocView = core.active_view:is(DocView) and not core.active_view:is(CommandView)
return is_DocView and core.active_view.doc or (last_view and last_view.doc)
return core.active_view:is(DocView) and core.active_view.doc or last_view.doc
end
local function get_find_tooltip()
@ -37,7 +37,7 @@ local function update_preview(sel, search_fn, text)
last_view:scroll_to_line(line2, true)
found_expression = true
else
last_view.doc:set_selection(table.unpack(sel))
last_view.doc:set_selection(unpack(sel))
found_expression = false
end
end
@ -53,8 +53,8 @@ end
local function find(label, search_fn)
last_view, last_sel = core.active_view,
{ core.active_view.doc:get_selection() }
last_view, last_sel, last_finds = core.active_view,
{ core.active_view.doc:get_selection() }, {}
local text = last_view.doc:get_text(unpack(last_sel))
found_expression = false
@ -69,8 +69,8 @@ local function find(label, search_fn)
last_fn, last_text = search_fn, text
else
core.error("Couldn't find %q", text)
last_view.doc:set_selection(table.unpack(last_sel))
last_view:scroll_to_make_visible(table.unpack(last_sel))
last_view.doc:set_selection(unpack(last_sel))
last_view:scroll_to_make_visible(unpack(last_sel))
end
end, function(text)
update_preview(last_sel, search_fn, text)
@ -79,8 +79,8 @@ local function find(label, search_fn)
end, function(explicit)
core.status_view:remove_tooltip()
if explicit then
last_view.doc:set_selection(table.unpack(last_sel))
last_view:scroll_to_make_visible(table.unpack(last_sel))
last_view.doc:set_selection(unpack(last_sel))
last_view:scroll_to_make_visible(unpack(last_sel))
end
end)
end
@ -117,7 +117,7 @@ local function has_selection()
end
local function has_unique_selection()
if not doc() then return false end
if not core.active_view:is(DocView) then return false end
local text = nil
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do
if line1 == line2 and col1 == col2 then return false end
@ -142,7 +142,7 @@ local function is_in_any_selection(line, col)
return false
end
local function select_add_next(all)
local function select_next(all)
local il1, ic1 = doc():get_selection(true)
for idx, l1, c1, l2, c2 in doc():get_selections(true, true) do
local text = doc():get_text(l1, c1, l2, c2)
@ -161,28 +161,21 @@ local function select_add_next(all)
end
end
local function select_next(reverse)
local l1, c1, l2, c2 = doc():get_selection(true)
local text = doc():get_text(l1, c1, l2, c2)
if reverse then
l1, c1, l2, c2 = search.find(doc(), l1, c1, text, { wrap = true, reverse = true })
else
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
end
if l2 then doc():set_selection(l2, c2, l1, c1) end
end
command.add(has_unique_selection, {
["find-replace:select-next"] = select_next,
["find-replace:select-previous"] = function() select_next(true) end,
["find-replace:select-add-next"] = select_add_next,
["find-replace:select-add-all"] = function() select_add_next(true) end
["find-replace:select-next"] = function()
local l1, c1, l2, c2 = doc():get_selection(true)
local text = doc():get_text(l1, c1, l2, c2)
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
if l2 then doc():set_selection(l2, c2, l1, c1) end
end,
["find-replace:select-add-next"] = function() select_next(false) end,
["find-replace:select-add-all"] = function() select_next(true) end
})
command.add("core.docview", {
["find-replace:find"] = function()
find("Find Text", function(doc, line, col, text, case_sensitive, find_regex, find_reverse)
local opt = { wrap = true, no_case = not case_sensitive, regex = find_regex, reverse = find_reverse }
find("Find Text", function(doc, line, col, text, case_sensitive, find_regex)
local opt = { wrap = true, no_case = not case_sensitive, regex = find_regex }
return search.find(doc, line, col, text, opt)
end)
end,
@ -228,29 +221,29 @@ command.add(valid_for_finding, {
core.error("No find to continue from")
else
local sl1, sc1, sl2, sc2 = doc():get_selection(true)
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc2, last_text, case_sensitive, find_regex, false)
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc2, last_text, case_sensitive, find_regex)
if line1 then
if last_view.doc ~= doc() then
last_finds = {}
end
if #last_finds >= max_last_finds then
table.remove(last_finds, 1)
end
table.insert(last_finds, { sl1, sc1, sl2, sc2 })
doc():set_selection(line2, col2, line1, col1)
last_view:scroll_to_line(line2, true)
else
core.error("Couldn't find %q", last_text)
end
end
end,
["find-replace:previous-find"] = function()
if not last_fn then
core.error("No find to continue from")
else
local sl1, sc1, sl2, sc2 = doc():get_selection(true)
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc1, last_text, case_sensitive, find_regex, true)
if line1 then
doc():set_selection(line2, col2, line1, col1)
last_view:scroll_to_line(line2, true)
else
core.error("Couldn't find %q", last_text)
end
local sel = table.remove(last_finds)
if not sel or doc() ~= last_view.doc then
core.error("No previous finds")
return
end
doc():set_selection(table.unpack(sel))
last_view:scroll_to_line(sel[3], true)
end,
})

View File

@ -3,7 +3,6 @@ local style = require "core.style"
local DocView = require "core.docview"
local command = require "core.command"
local common = require "core.common"
local config = require "core.config"
local t = {
@ -64,7 +63,7 @@ local t = {
table.insert(node.views, idx + 1, core.active_view)
end
end,
["root:shrink"] = function()
local node = core.root_view:get_active_node()
local parent = node:get_parent_node(core.root_view.root_node)
@ -77,7 +76,7 @@ local t = {
local parent = node:get_parent_node(core.root_view.root_node)
local n = (parent.a == node) and 0.1 or -0.1
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
end
end,
}
@ -113,8 +112,7 @@ for _, dir in ipairs { "left", "right", "up", "down" } do
y = node.position.y + (dir == "up" and -1 or node.size.y + style.divider_size)
end
local node = core.root_view.root_node:get_child_overlapping_point(x, y)
local sx, sy = node:get_locked_size()
if not sx and not sy then
if not node:get_locked_size() then
core.set_active_view(node.active_view)
end
end
@ -122,17 +120,5 @@ end
command.add(function()
local node = core.root_view:get_active_node()
local sx, sy = node:get_locked_size()
return not sx and not sy
return not node:get_locked_size()
end, t)
command.add(nil, {
["root:scroll"] = function(delta)
local view = (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) or core.active_view
if view and view.scrollable then
view.scroll.to.y = view.scroll.to.y + delta * -config.mouse_wheel_scroll
return true
end
return false
end
})

View File

@ -1,8 +1,8 @@
local common = {}
function common.is_utf8_cont(s, offset)
local byte = s:byte(offset or 1)
function common.is_utf8_cont(char)
local byte = char:byte()
return byte >= 0x80 and byte < 0xc0
end
@ -47,22 +47,22 @@ end
function common.color(str)
local r, g, b, a = str:match("^#(%x%x)(%x%x)(%x%x)(%x?%x?)$")
local r, g, b, a = str:match("#(%x%x)(%x%x)(%x%x)")
if r then
r = tonumber(r, 16)
g = tonumber(g, 16)
b = tonumber(b, 16)
a = tonumber(a, 16) or 0xff
a = 1
elseif str:match("rgba?%s*%([%d%s%.,]+%)") then
local f = str:gmatch("[%d.]+")
r = (f() or 0)
g = (f() or 0)
b = (f() or 0)
a = (f() or 1) * 0xff
a = f() or 1
else
error(string.format("bad color string '%s'", str))
end
return r, g, b, a
return r, g, b, a * 0xff
end
@ -280,61 +280,24 @@ local function split_on_slash(s, sep_pattern)
end
-- The filename argument given to the function is supposed to
-- come from system.absolute_path and as such should be an
-- absolute path without . or .. elements.
-- This function exists because on Windows the drive letter returned
-- by system.absolute_path is sometimes with a lower case and sometimes
-- with an upper case to we normalize to upper case.
function common.normalize_volume(filename)
if not filename then return end
if PATHSEP == '\\' then
local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
if drive then
return drive:upper() .. rem
end
end
return filename
end
function common.normalize_path(filename)
if not filename then return end
local volume
if PATHSEP == '\\' then
filename = filename:gsub('[/\\]', '\\')
local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
if drive then
volume, filename = drive:upper(), rem
else
drive, rem = filename:match('^(\\\\[^\\]+\\[^\\]+\\)(.*)')
if drive then
volume, filename = drive, rem
end
end
else
local relpath = filename:match('^/(.+)')
if relpath then
volume, filename = "/", relpath
end
local drive, rem = filename:match('^([a-zA-Z])(:.*)')
filename = drive and drive:upper() .. rem or filename
end
local parts = split_on_slash(filename, PATHSEP)
local accu = {}
for _, part in ipairs(parts) do
if part == '..' then
if #accu > 0 and accu[#accu] ~= ".." then
table.remove(accu)
elseif volume then
error("invalid path " .. volume .. filename)
else
table.insert(accu, part)
end
if part == '..' and #accu > 0 and accu[#accu] ~= ".." then
table.remove(accu)
elseif part ~= '.' then
table.insert(accu, part)
end
end
local npath = table.concat(accu, PATHSEP)
return (volume or "") .. (npath == "" and PATHSEP or npath)
return npath == "" and PATHSEP or npath
end
@ -349,7 +312,7 @@ function common.relative_path(ref_dir, dir)
if drive and ref_drive and drive ~= ref_drive then
-- Windows, different drives, system.absolute_path fails for C:\..\D:\
return dir
end
end
local ref_ls = split_on_slash(ref_dir)
local dir_ls = split_on_slash(dir)
local i = 1

View File

@ -27,7 +27,6 @@ config.disable_blink = false
config.draw_whitespace = false
config.borderless = false
config.tab_close_button = true
config.max_clicks = 3
-- Disable plugin loading setting to false the config entry
-- of the same name.
@ -35,6 +34,5 @@ config.plugins = {}
config.plugins.trimwhitespace = false
config.plugins.lineguide = false
config.plugins.drawwhitespace = false
return config

View File

@ -49,7 +49,7 @@ function ContextMenu:register(predicate, items)
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.get_binding(item.command)
item.info = keymap.reverse_map[item.command]
end
local lw, lh = get_item_size(item)
width = math.max(width, lw)
@ -66,13 +66,9 @@ function ContextMenu:show(x, y)
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_list.height = items_list.height + items.items.height
for _, subitems in ipairs(items.items) do
if not subitems.command or command.is_valid(subitems.command) then
local lw, lh = get_item_size(subitems)
items_list.height = items_list.height + lh
table.insert(items_list, subitems)
end
table.insert(items_list, subitems)
end
end
end

View File

@ -1,5 +1,4 @@
local core = require "core"
local common = require "core.common"
local config = require "core.config"
local tokenizer = require "core.tokenizer"
local Object = require "core.object"
@ -25,7 +24,7 @@ function Highlighter:new(doc)
for i = self.first_invalid_line, max do
local state = (i > 1) and self.lines[i - 1].state
local line = self.lines[i]
if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
if not (line and line.init_state == state) then
self.lines[i] = self:tokenize_line(i, state)
end
end
@ -41,36 +40,16 @@ end
function Highlighter:reset()
self.lines = {}
self:soft_reset()
end
function Highlighter:soft_reset()
for i=1,#self.lines do
self.lines[i] = false
end
self.first_invalid_line = 1
self.max_wanted_line = 0
end
function Highlighter:invalidate(idx)
self.first_invalid_line = math.min(self.first_invalid_line, idx)
self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines)
end
function Highlighter:insert_notify(line, n)
self:invalidate(line)
local blanks = { }
for i = 1, n do
blanks[i] = false
end
common.splice(self.lines, line, 0, blanks)
end
function Highlighter:remove_notify(line, n)
self:invalidate(line)
common.splice(self.lines, line, n)
end
function Highlighter:tokenize_line(idx, state)
local res = {}

View File

@ -47,7 +47,7 @@ function Doc:reset_syntax()
local syn = syntax.get(self.filename or "", header)
if self.syntax ~= syn then
self.syntax = syn
self.highlighter:soft_reset()
self.highlighter:reset()
end
end
@ -62,15 +62,12 @@ function Doc:load(filename)
local fp = assert( io.open(filename, "rb") )
self:reset()
self.lines = {}
local i = 1
for line in fp:lines() do
if line:byte(-1) == 13 then
line = line:sub(1, -2)
self.crlf = true
end
table.insert(self.lines, line .. "\n")
self.highlighter.lines[i] = false
i = i + 1
end
if #self.lines == 0 then
table.insert(self.lines, "\n")
@ -105,11 +102,7 @@ end
function Doc:is_dirty()
if self.new_file then
return #self.lines > 1 or #self.lines[1] > 1
else
return self.clean_change_id ~= self:get_change_id()
end
return self.clean_change_id ~= self:get_change_id() or self.new_file
end
@ -118,14 +111,6 @@ function Doc:clean()
end
function Doc:get_indent_info()
if not self.indent_info then return config.tab_type, config.indent_size, false end
return self.indent_info.type or config.tab_type,
self.indent_info.size or config.indent_size,
self.indent_info.confirmed
end
function Doc:get_change_id()
return self.undo_stack.idx
end
@ -157,13 +142,6 @@ function Doc:has_selection()
return line1 ~= line2 or col1 ~= col2
end
function Doc:has_any_selection()
for idx, line1, col1, line2, col2 in self:get_selections() do
if line1 ~= line2 or col1 ~= col2 then return true end
end
return false
end
function Doc:sanitize_selection()
for idx, line1, col1, line2, col2 in self:get_selections() do
self:set_selections(idx, line1, col1, line2, col2)
@ -220,9 +198,9 @@ local function selection_iterator(invariant, idx)
local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1)
if target > #invariant[1] or target <= 0 or (type(invariant[3]) == "number" and invariant[3] ~= idx - 1) then return end
if invariant[2] then
return idx+(invariant[3] and -1 or 1), sort_positions(table.unpack(invariant[1], target, target+4))
return idx+(invariant[3] and -1 or 1), sort_positions(unpack(invariant[1], target, target+4))
else
return idx+(invariant[3] and -1 or 1), table.unpack(invariant[1], target, target+4)
return idx+(invariant[3] and -1 or 1), unpack(invariant[1], target, target+4)
end
end
@ -323,8 +301,7 @@ local function pop_undo(self, undo_stack, redo_stack, modified)
local line1, col1, line2, col2 = table.unpack(cmd)
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
elseif cmd.type == "selection" then
self.selections = { table.unpack(cmd) }
self:sanitize_selection()
self.selections = { unpack(cmd) }
end
modified = modified or (cmd.type ~= "selection")
@ -367,11 +344,11 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
-- push undo
local line2, col2 = self:position_offset(line, col, #text)
push_undo(undo_stack, time, "selection", table.unpack(self.selections))
push_undo(undo_stack, time, "selection", unpack(self.selections))
push_undo(undo_stack, time, "remove", line, col, line2, col2)
-- update highlighter and assure selection is in bounds
self.highlighter:insert_notify(line, #lines - 1)
self.highlighter:invalidate(line)
self:sanitize_selection()
end
@ -379,7 +356,7 @@ end
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
-- push undo
local text = self:get_text(line1, col1, line2, col2)
push_undo(undo_stack, time, "selection", table.unpack(self.selections))
push_undo(undo_stack, time, "selection", unpack(self.selections))
push_undo(undo_stack, time, "insert", line1, col1, text)
-- get line content before/after removed text
@ -398,7 +375,7 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
end
-- update highlighter and assure selection is in bounds
self.highlighter:remove_notify(line1, line2 - line1)
self.highlighter:invalidate(line1)
self:sanitize_selection()
end
@ -505,21 +482,19 @@ end
function Doc:select_to(...) return self:select_to_cursor(nil, ...) end
function Doc:get_indent_string()
local indent_type, indent_size = self:get_indent_info()
if indent_type == "hard" then
local function get_indent_string()
if config.tab_type == "hard" then
return "\t"
end
return string.rep(" ", indent_size)
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
function Doc:get_line_indent(line, rnd_up)
local function get_line_indent(line, rnd_up)
local _, e = line:find("^[ \t]+")
local indent_type, indent_size = self:get_indent_info()
local soft_tab = string.rep(" ", indent_size)
if indent_type == "hard" then
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
@ -541,14 +516,14 @@ end
-- * 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)
local text = self:get_indent_string()
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 = self:get_line_indent(self.lines[line], unindent)
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)

View File

@ -22,62 +22,37 @@ local function init_args(doc, line, col, text, opt)
return doc, line, col, text, opt
end
-- This function is needed to uniform the behavior of
-- `regex:cmatch` and `string.find`.
local function regex_func(text, re, index, _)
local s, e = re:cmatch(text, index)
return s, e and e - 1
end
local function rfind(func, text, pattern, index, plain)
local s, e = func(text, pattern, 1, plain)
local last_s, last_e
if index < 0 then index = #text - index + 1 end
while e and e <= index do
last_s, last_e = s, e
s, e = func(text, pattern, s + 1, plain)
end
return last_s, last_e
end
function search.find(doc, line, col, text, opt)
doc, line, col, text, opt = init_args(doc, line, col, text, opt)
local plain = not opt.pattern
local pattern = text
local search_func = string.find
local re
if opt.regex then
pattern = regex.compile(text, opt.no_case and "i" or "")
search_func = regex_func
re = regex.compile(text, opt.no_case and "i" or "")
end
local start, finish, step = line, #doc.lines, 1
if opt.reverse then
start, finish, step = line, 1, -1
end
for line = start, finish, step do
for line = line, #doc.lines do
local line_text = doc.lines[line]
if opt.no_case and not opt.regex then
line_text = line_text:lower()
end
local s, e
if opt.reverse then
s, e = rfind(search_func, line_text, pattern, col - 1, plain)
if opt.regex then
local s, e = re:cmatch(line_text, col)
if s then
return line, s, line, e
end
col = 1
else
s, e = search_func(line_text, pattern, col, plain)
if opt.no_case then
line_text = line_text:lower()
end
local s, e = line_text:find(text, col, true)
if s then
return line, s, line, e + 1
end
col = 1
end
if s then
return line, s, line, e + 1
end
col = opt.reverse and -1 or 1
end
if opt.wrap then
opt = { no_case = opt.no_case, regex = opt.regex, reverse = opt.reverse }
if opt.reverse then
return search.find(doc, #doc.lines, #doc.lines[#doc.lines], text, opt)
else
return search.find(doc, 1, 1, text, opt)
end
opt = { no_case = opt.no_case, regex = opt.regex }
return search.find(doc, 1, 1, text, opt)
end
end

View File

@ -153,14 +153,14 @@ function DocView:get_col_x_offset(line, col)
local font = style.syntax_fonts[type] or default_font
for char in common.utf8_chars(text) do
if column == col then
return xoffset
return xoffset / font:subpixel_scale()
end
xoffset = xoffset + font:get_width(char)
xoffset = xoffset + font:get_width_subpixel(char)
column = column + #char
end
end
return xoffset
return xoffset / default_font:subpixel_scale()
end
@ -169,12 +169,14 @@ function DocView:get_x_offset_col(line, x)
local xoffset, last_i, i = 0, 1, 1
local default_font = self:get_font()
local subpixel_scale = default_font:subpixel_scale()
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
local w = font:get_width(char)
if xoffset >= x then
return (xoffset - x > w / 2) and last_i or i
local w = font:get_width_subpixel(char)
if xoffset >= subpixel_scale * x then
return (xoffset - x_subpixel > w / 2) and last_i or i
end
xoffset = xoffset + w
last_i = i
@ -225,6 +227,51 @@ function DocView:scroll_to_make_visible(line, col)
end
local function mouse_selection(doc, clicks, line1, col1, line2, col2)
local swap = line2 < line1 or line2 == line1 and col2 <= col1
if swap then
line1, col1, line2, col2 = line2, col2, line1, col1
end
if clicks % 4 == 2 then
line1, col1 = translate.start_of_word(doc, line1, col1)
line2, col2 = translate.end_of_word(doc, line2, col2)
elseif clicks % 4 == 3 then
if line2 == #doc.lines and doc.lines[#doc.lines] ~= "\n" then
doc:insert(math.huge, math.huge, "\n")
end
line1, col1, line2, col2 = line1, 1, line2 + 1, 1
end
if swap then
return line2, col2, line1, col1
end
return line1, col1, line2, col2
end
function DocView:on_mouse_pressed(button, x, y, clicks)
local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks)
if caught then
return
end
if keymap.modkeys["shift"] then
if clicks % 2 == 1 then
local line1, col1 = select(3, self.doc:get_selection())
local line2, col2 = self:resolve_screen_position(x, y)
self.doc:set_selection(line2, col2, line1, col1)
end
else
local line, col = self:resolve_screen_position(x, y)
if keymap.modkeys["ctrl"] then
self.doc:add_selection(mouse_selection(self.doc, clicks, line, col, line, col))
else
self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col))
end
self.mouse_selecting = { line, col, clicks = clicks }
end
core.blink_reset()
end
function DocView:on_mouse_moved(x, y, ...)
DocView.super.on_mouse_moved(self, x, y, ...)
@ -236,7 +283,8 @@ function DocView:on_mouse_moved(x, y, ...)
if self.mouse_selecting then
local l1, c1 = self:resolve_screen_position(x, y)
local l2, c2, snap_type = table.unpack(self.mouse_selecting)
local l2, c2 = table.unpack(self.mouse_selecting)
local clicks = self.mouse_selecting.clicks
if keymap.modkeys["ctrl"] then
if l1 > l2 then l1, l2 = l2, l1 end
self.doc.selections = { }
@ -244,33 +292,12 @@ function DocView:on_mouse_moved(x, y, ...)
self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c2, #self.doc.lines[i]))
end
else
if snap_type then
l1, c1, l2, c2 = self:mouse_selection(self.doc, snap_type, l1, c1, l2, c2)
end
self.doc:set_selection(l1, c1, l2, c2)
self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2))
end
end
end
function DocView:mouse_selection(doc, snap_type, line1, col1, line2, col2)
local swap = line2 < line1 or line2 == line1 and col2 <= col1
if swap then
line1, col1, line2, col2 = line2, col2, line1, col1
end
if snap_type == "word" then
line1, col1 = translate.start_of_word(doc, line1, col1)
line2, col2 = translate.end_of_word(doc, line2, col2)
elseif snap_type == "lines" then
col1, col2 = 1, math.huge
end
if swap then
return line2, col2, line1, col1
end
return line1, col1, line2, col2
end
function DocView:on_mouse_released(button)
DocView.super.on_mouse_released(self, button)
self.mouse_selecting = nil
@ -315,11 +342,16 @@ end
function DocView:draw_line_text(idx, x, y)
local default_font = self:get_font()
local tx, ty = x, y + self:get_line_text_y_offset()
local subpixel_scale = default_font:subpixel_scale()
local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset()
for _, type, text in self.doc.highlighter:each_token(idx) do
local color = style.syntax[type]
local font = style.syntax_fonts[type] or default_font
tx = renderer.draw_text(font, text, tx, ty, color)
if config.draw_whitespace then
tx = renderer.draw_text_subpixel(font, text, tx, ty, color, core.replacements, style.syntax.comment)
else
tx = renderer.draw_text_subpixel(font, text, tx, ty, color)
end
end
end
@ -329,18 +361,6 @@ function DocView:draw_caret(x, y)
end
function DocView:draw_line_body(idx, x, y)
-- draw highlight if any selection ends on this line
local draw_highlight = false
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
if line1 == idx then
draw_highlight = true
break
end
end
if draw_highlight and config.highlight_current_line and core.active_view == self then
self:draw_line_highlight(x + self.scroll.x, y)
end
-- draw selection if it overlaps this line
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
if idx >= line1 and idx <= line2 then
@ -355,6 +375,15 @@ function DocView:draw_line_body(idx, x, y)
end
end
end
local draw_highlight = nil
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
-- draw line highlight if caret is on this line
if draw_highlight ~= false and config.highlight_current_line
and line1 == idx and core.active_view == self then
draw_highlight = (line1 == line2 and col1 == col2)
end
end
if draw_highlight then self:draw_line_highlight(x + self.scroll.x, y) end
-- draw line's text
self:draw_line_text(idx, x, y)
@ -395,8 +424,8 @@ end
function DocView:draw()
self:draw_background(style.background)
local _, indent_size = self.doc:get_indent_info()
self:get_font():set_tab_size(indent_size)
self:get_font():set_tab_size(config.indent_size)
local minline, maxline = self:get_visible_line_range()
local lh = self:get_line_height()
@ -410,9 +439,7 @@ function DocView:draw()
local pos = self.position
x, y = self:get_line_screen_position(minline)
-- the clip below ensure we don't write on the gutter region. On the
-- right side it is redundant with the Node's clip.
core.push_clip_rect(pos.x + gw, pos.y, self.size.x - gw, self.size.y)
core.push_clip_rect(pos.x + gw, pos.y, self.size.x, self.size.y)
for i = minline, maxline do
self:draw_line_body(i, x, y)
y = y + lh

View File

@ -1,40 +0,0 @@
local style = require "core.style"
local keymap = require "core.keymap"
local View = require "core.view"
local EmptyView = View:extend()
local function draw_text(x, y, color)
local th = style.big_font:get_height()
local dh = 2 * th + style.padding.y * 2
local x1, y1 = x, y + (dh - th) / 2
x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color)
renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color)
x = x + style.padding.x
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
local lines = {
{ fmt = "%s to run a command", cmd = "core:find-command" },
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
}
th = style.font:get_height()
y = y + (dh - (th + style.padding.y) * #lines) / 2
local w = 0
for _, line in ipairs(lines) do
local text = string.format(line.fmt, keymap.get_binding(line.cmd))
w = math.max(w, renderer.draw_text(style.font, text, x + style.padding.x, y, color))
y = y + th + style.padding.y
end
return w, dh
end
function EmptyView:draw()
self:draw_background(style.background)
local w, h = draw_text(0, 0, { 0, 0, 0, 0 })
local x = self.position.x + math.max(style.padding.x, (self.size.x - w) / 2)
local y = self.position.y + (self.size.y - h) / 2
draw_text(x, y, style.dim)
end
return EmptyView

View File

@ -36,7 +36,7 @@ end
local function update_recents_project(action, dir_path_abs)
local dirname = common.normalize_volume(dir_path_abs)
local dirname = common.normalize_path(dir_path_abs)
if not dirname then return end
local recents = core.recent_projects
local n = #recents
@ -56,7 +56,7 @@ function core.set_project_dir(new_dir, change_project_fn)
local chdir_ok = pcall(system.chdir, new_dir)
if chdir_ok then
if change_project_fn then change_project_fn() end
core.project_dir = common.normalize_volume(new_dir)
core.project_dir = common.normalize_path(new_dir)
core.project_directories = {}
core.add_project_directory(new_dir)
return true
@ -100,21 +100,12 @@ local function get_project_file_info(root, file)
if info then
info.filename = strip_leading_path(file)
return (info.size < config.file_size_limit * 1e6 and
not common.match_pattern(common.basename(info.filename), config.ignore_files)
not common.match_pattern(info.filename, config.ignore_files)
and info)
end
end
-- Predicate function to inhibit directory recursion in get_directory_files
-- based on a time limit and the number of files.
local function timed_max_files_pred(dir, filename, entries_count, t_elapsed)
local n_limit = entries_count <= config.max_project_files
local t_limit = t_elapsed < 20 / config.fps
return n_limit and t_limit and core.project_subdir_is_shown(dir, filename)
end
-- "root" will by an absolute path without trailing '/'
-- "path" will be a path starting with '/' and without trailing '/'
-- or the empty string.
@ -123,13 +114,12 @@ end
-- 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
-- complete file path relative to "root" *without* the trailing '/'.
local function get_directory_files(dir, root, path, t, entries_count, recurse_pred, begin_hook)
local function get_directory_files(dir, root, path, t, begin_hook, max_files)
if begin_hook then begin_hook() end
local t0 = system.get_time()
local all = system.list_dir(root .. path) or {}
local t_elapsed = system.get_time() - t0
local dirs, files = {}, {}
local entries_count = 0
for _, file in ipairs(all) do
local info = get_project_file_info(root, path .. PATHSEP .. file)
if info then
@ -138,16 +128,13 @@ local function get_directory_files(dir, root, path, t, entries_count, recurse_pr
end
end
local recurse_complete = true
table.sort(dirs, compare_file)
for _, f in ipairs(dirs) do
table.insert(t, f)
if recurse_pred(dir, f.filename, entries_count, t_elapsed) then
local _, complete, n = get_directory_files(dir, root, PATHSEP .. f.filename, t, entries_count, recurse_pred, begin_hook)
recurse_complete = recurse_complete and complete
entries_count = n
else
recurse_complete = false
if (not max_files or entries_count <= max_files) and core.project_subdir_is_shown(dir, f.filename) then
local sub_limit = max_files and max_files - entries_count
local _, n = get_directory_files(dir, root, PATHSEP .. f.filename, t, begin_hook, sub_limit)
entries_count = entries_count + n
end
end
@ -156,7 +143,7 @@ local function get_directory_files(dir, root, path, t, entries_count, recurse_pr
table.insert(t, f)
end
return t, recurse_complete, entries_count
return t, entries_count
end
@ -178,13 +165,53 @@ function core.project_subdir_is_shown(dir, filename)
end
local function show_max_files_warning(dir)
local message = dir.slow_filesystem and
"Filesystem is too slow: project files will not be indexed." or
local function show_max_files_warning()
core.status_view:show_message("!", style.accent,
"Too many files in project directory: stopped reading at "..
config.max_project_files.." files. For more information see "..
"usage.md at github.com/lite-xl/lite-xl."
core.status_view:show_message("!", style.accent, message)
"usage.md at github.com/franko/lite-xl."
)
end
-- Populate a project folder top directory by scanning the filesystem.
local function scan_project_folder(index)
local dir = core.project_directories[index]
local t, entries_count = get_directory_files(dir, dir.name, "", {}, nil, config.max_project_files)
if entries_count > config.max_project_files then
dir.files_limit = true
-- Watch non-recursively on Linux only.
-- The reason is recursively watching with dmon on linux
-- doesn't work on very large directories.
dir.watch_id = system.watch_dir(dir.name, PLATFORM ~= "Linux")
if core.status_view then -- May be not yet initialized.
show_max_files_warning()
end
else
dir.watch_id = system.watch_dir(dir.name, true)
end
dir.files = t
core.dir_rescan_add_job(dir, ".")
end
function core.add_project_directory(path)
-- top directories has a file-like "item" but the item.filename
-- will be simply the name of the directory, without its path.
-- The field item.topdir will identify it as a top level directory.
path = common.normalize_path(path)
local dir = {
name = path,
item = {filename = common.basename(path), type = "dir", topdir = true},
files_limit = false,
is_dirty = true,
shown_subdir = {},
}
table.insert(core.project_directories, dir)
scan_project_folder(#core.project_directories)
if path == core.project_dir then
core.project_files = dir.files
end
core.redraw = true
end
@ -199,12 +226,12 @@ local function file_search(files, info)
inf = curr
end
end
while inf <= sup and not system.path_compare(filename, type, files[inf].filename, files[inf].type) do
repeat
if files[inf].filename == filename then
return inf, true
end
inf = inf + 1
end
until inf > sup or system.path_compare(filename, type, files[inf].filename, files[inf].type)
return inf, false
end
@ -271,7 +298,7 @@ local function project_subdir_bounds(dir, filename)
end
local function rescan_project_subdir(dir, filename_rooted)
local new_files = get_directory_files(dir, dir.name, filename_rooted, {}, 0, core.project_subdir_is_shown, coroutine.yield)
local new_files = get_directory_files(dir, dir.name, filename_rooted, {}, coroutine.yield)
local index, n = 0, #dir.files
if filename_rooted ~= "" then
local filename = strip_leading_path(filename_rooted)
@ -286,77 +313,10 @@ local function rescan_project_subdir(dir, filename_rooted)
end
local function add_dir_scan_thread(dir)
core.add_thread(function()
while true do
local has_changes = rescan_project_subdir(dir, "")
if has_changes then
core.redraw = true -- we run without an event, from a thread
end
coroutine.yield(5)
end
end)
end
-- Populate a project folder top directory by scanning the filesystem.
local function scan_project_folder(index)
local dir = core.project_directories[index]
if PLATFORM == "Linux" then
local fstype = system.get_fs_type(dir.name)
dir.force_rescan = (fstype == "nfs" or fstype == "fuse")
end
local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, 0, timed_max_files_pred)
if not complete then
dir.slow_filesystem = not complete and (entries_count <= config.max_project_files)
dir.files_limit = true
if not dir.force_rescan then
-- Watch non-recursively on Linux only.
-- The reason is recursively watching with dmon on linux
-- doesn't work on very large directories.
dir.watch_id = system.watch_dir(dir.name, PLATFORM ~= "Linux")
end
if core.status_view then -- May be not yet initialized.
show_max_files_warning(dir)
end
else
if not dir.force_rescan then
dir.watch_id = system.watch_dir(dir.name, true)
end
end
dir.files = t
if dir.force_rescan then
add_dir_scan_thread(dir)
else
core.dir_rescan_add_job(dir, ".")
end
end
function core.add_project_directory(path)
-- top directories has a file-like "item" but the item.filename
-- will be simply the name of the directory, without its path.
-- The field item.topdir will identify it as a top level directory.
path = common.normalize_volume(path)
local dir = {
name = path,
item = {filename = common.basename(path), type = "dir", topdir = true},
files_limit = false,
is_dirty = true,
shown_subdir = {},
}
table.insert(core.project_directories, dir)
scan_project_folder(#core.project_directories)
if path == core.project_dir then
core.project_files = dir.files
end
core.redraw = true
end
function core.update_project_subdir(dir, filename, expanded)
local index, n, file = project_subdir_bounds(dir, filename)
if index then
local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}, 0, core.project_subdir_is_shown) or {}
local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}) or {}
files_list_replace(dir.files, index, n, new_files)
dir.is_dirty = true
return true
@ -577,6 +537,15 @@ function core.remove_project_directory(path)
return false
end
local function whitespace_replacements()
local r = renderer.replacements.new()
r:add(" ", "·")
r:add("\t", "»")
return r
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
@ -603,9 +572,9 @@ function core.init()
Doc = require "core.doc"
if PATHSEP == '\\' then
USERDIR = common.normalize_volume(USERDIR)
DATADIR = common.normalize_volume(DATADIR)
EXEDIR = common.normalize_volume(EXEDIR)
USERDIR = common.normalize_path(USERDIR)
DATADIR = common.normalize_path(DATADIR)
EXEDIR = common.normalize_path(EXEDIR)
end
do
@ -674,6 +643,7 @@ function core.init()
core.visited_files = {}
core.restart_request = false
core.quit_request = false
core.replacements = whitespace_replacements()
core.root_view = RootView()
core.command_view = CommandView()
@ -695,7 +665,7 @@ function core.init()
local plugins_success, plugins_refuse_list = core.load_plugins()
do
local pdir, pname = project_dir_abs:match("(.*)[:/\\\\](.*)")
local pdir, pname = project_dir_abs:match("(.*)[/\\\\](.*)")
core.log("Opening project %q from directory %s", pname, pdir)
end
local got_project_error = not core.load_project_module()
@ -703,7 +673,7 @@ function core.init()
-- We assume we have just a single project directory here. Now that StatusView
-- is there show max files warning if needed.
if core.project_directories[1].files_limit then
show_max_files_warning(core.project_directories[1])
show_max_files_warning()
end
for _, filename in ipairs(files) do
@ -737,7 +707,7 @@ function core.init()
"Refused Plugins",
string.format(
"Some plugins are not loaded due to version mismatch.\n\n%s.\n\n" ..
"Please download a recent version from https://github.com/lite-xl/lite-xl-plugins.",
"Please download a recent version from https://github.com/franko/lite-plugins.",
table.concat(msg, ".\n\n")),
opt, function(item)
if item.text == "Exit" then os.exit(1) end
@ -861,18 +831,16 @@ function core.load_plugins()
userdir = {dir = USERDIR, plugins = {}},
datadir = {dir = DATADIR, plugins = {}},
}
local files, ordered = {}, {}
local files = {}
for _, root_dir in ipairs {DATADIR, USERDIR} do
local plugin_dir = root_dir .. "/plugins"
for _, filename in ipairs(system.list_dir(plugin_dir) or {}) do
if not files[filename] then table.insert(ordered, filename) end
files[filename] = plugin_dir -- user plugins will always replace system plugins
end
end
table.sort(ordered)
for _, filename in ipairs(ordered) do
local plugin_dir, basename = files[filename], filename:match("(.-)%.lua$") or filename
for filename, plugin_dir in pairs(files) do
local basename = filename:match("(.-)%.lua$") or filename
local is_lua_file, version_match = check_plugin_version(plugin_dir .. '/' .. filename)
if is_lua_file then
if not version_match then
@ -1155,11 +1123,6 @@ function core.dir_rescan_add_job(dir, filepath)
end
-- no-op but can be overrided by plugins
function core.on_dirmonitor_modify(dir, filepath)
end
function core.on_dir_change(watch_id, action, filepath)
local dir = project_dir_by_watch_id(watch_id)
if not dir then return end
@ -1168,9 +1131,6 @@ function core.on_dir_change(watch_id, action, filepath)
project_scan_remove_file(dir, filepath)
elseif action == "create" then
project_scan_add_file(dir, filepath)
core.on_dirmonitor_modify(dir, filepath);
elseif action == "modify" then
core.on_dirmonitor_modify(dir, filepath);
end
end
@ -1186,15 +1146,11 @@ function core.on_event(type, ...)
elseif type == "mousemoved" then
core.root_view:on_mouse_moved(...)
elseif type == "mousepressed" then
if not core.root_view:on_mouse_pressed(...) then
did_keymap = keymap.on_mouse_pressed(...)
end
core.root_view:on_mouse_pressed(...)
elseif type == "mousereleased" then
core.root_view:on_mouse_released(...)
elseif type == "mousewheel" then
if not core.root_view:on_mouse_wheel(...) then
did_keymap = keymap.on_mouse_wheel(...)
end
core.root_view:on_mouse_wheel(...)
elseif type == "resized" then
core.window_mode = system.get_window_mode()
elseif type == "minimized" or type == "maximized" or type == "restored" then
@ -1374,4 +1330,3 @@ end
return core

View File

@ -6,7 +6,6 @@ local function keymap_macos(keymap)
["cmd+n"] = "core:new-doc",
["cmd+shift+c"] = "core:change-project-folder",
["cmd+shift+o"] = "core:open-project-folder",
["cmd+shift+r"] = "core:restart",
["cmd+ctrl+return"] = "core:toggle-fullscreen",
["cmd+ctrl+shift+j"] = "root:split-left",
@ -33,8 +32,6 @@ local function keymap_macos(keymap)
["cmd+7"] = "root:switch-to-tab-7",
["cmd+8"] = "root:switch-to-tab-8",
["cmd+9"] = "root:switch-to-tab-9",
["wheel"] = "root:scroll",
["cmd+f"] = "find-replace:find",
["cmd+r"] = "find-replace:replace",
["f3"] = "find-replace:repeat-find",
@ -96,11 +93,6 @@ local function keymap_macos(keymap)
["pageup"] = "doc:move-to-previous-page",
["pagedown"] = "doc:move-to-next-page",
["shift+1lclick"] = "doc:select-to-cursor",
["ctrl+1lclick"] = "doc:split-cursor",
["1lclick"] = "doc:set-cursor",
["2lclick"] = "doc:set-cursor-word",
["3lclick"] = "doc:set-cursor-line",
["shift+left"] = "doc:select-to-previous-char",
["shift+right"] = "doc:select-to-next-char",
["shift+up"] = "doc:select-to-previous-line",

View File

@ -1,5 +1,4 @@
local command = require "core.command"
local config = require "core.config"
local keymap = {}
keymap.modkeys = {}
@ -7,10 +6,9 @@ keymap.map = {}
keymap.reverse_map = {}
local macos = PLATFORM == "Mac OS X"
local os4 = PLATFORM == "AmigaOS 4"
-- Thanks to mathewmariani, taken from his lite-macos github repository.
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or os4 and "os4" or "generic"))
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or "generic"))
local modkey_map = modkeys_os.map
local modkeys = modkeys_os.keys
@ -32,8 +30,7 @@ function keymap.add_direct(map)
end
keymap.map[stroke] = commands
for _, cmd in ipairs(commands) do
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
table.insert(keymap.reverse_map[cmd], stroke)
keymap.reverse_map[cmd] = stroke
end
end
end
@ -55,43 +52,18 @@ function keymap.add(map, overwrite)
end
end
for _, cmd in ipairs(commands) do
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
table.insert(keymap.reverse_map[cmd], stroke)
keymap.reverse_map[cmd] = stroke
end
end
end
local function remove_only(tbl, k, v)
for key, values in pairs(tbl) do
if key == k then
if v then
for i, value in ipairs(values) do
if value == v then
table.remove(values, i)
end
end
else
tbl[key] = nil
end
break
end
end
end
function keymap.unbind(key, cmd)
remove_only(keymap.map, key, cmd)
remove_only(keymap.reverse_map, cmd, key)
end
function keymap.get_binding(cmd)
return table.unpack(keymap.reverse_map[cmd] or {})
return keymap.reverse_map[cmd]
end
function keymap.on_key_pressed(k, ...)
function keymap.on_key_pressed(k)
local mk = modkey_map[k]
if mk then
keymap.modkeys[mk] = true
@ -101,30 +73,18 @@ function keymap.on_key_pressed(k, ...)
end
else
local stroke = key_to_stroke(k)
local commands, performed = keymap.map[stroke]
local commands = keymap.map[stroke]
if commands then
for _, cmd in ipairs(commands) do
performed = command.perform(cmd, ...)
local performed = command.perform(cmd)
if performed then break end
end
return performed
return true
end
end
return false
end
function keymap.on_mouse_wheel(delta, ...)
return not (keymap.on_key_pressed("wheel" .. (delta > 0 and "up" or "down"), delta, ...)
or keymap.on_key_pressed("wheel", delta, ...))
end
function keymap.on_mouse_pressed(button, x, y, clicks)
local click_number = (((clicks - 1) % config.max_clicks) + 1)
return not (keymap.on_key_pressed(click_number .. button:sub(1,1) .. "click", x, y, clicks) or
keymap.on_key_pressed(button:sub(1,1) .. "click", x, y, clicks) or
keymap.on_key_pressed(click_number .. "click", x, y, clicks) or
keymap.on_key_pressed("click", x, y, clicks))
end
function keymap.on_key_released(k)
local mk = modkey_map[k]
@ -147,7 +107,6 @@ keymap.add_direct {
["ctrl+n"] = "core:new-doc",
["ctrl+shift+c"] = "core:change-project-folder",
["ctrl+shift+o"] = "core:open-project-folder",
["ctrl+shift+r"] = "core:restart",
["alt+return"] = "core:toggle-fullscreen",
["f11"] = "core:toggle-fullscreen",
@ -174,7 +133,6 @@ keymap.add_direct {
["alt+7"] = "root:switch-to-tab-7",
["alt+8"] = "root:switch-to-tab-8",
["alt+9"] = "root:switch-to-tab-9",
["wheel"] = "root:scroll",
["ctrl+f"] = "find-replace:find",
["ctrl+r"] = "find-replace:replace",
@ -212,7 +170,6 @@ keymap.add_direct {
["ctrl+a"] = "doc:select-all",
["ctrl+d"] = { "find-replace:select-add-next", "doc:select-word" },
["ctrl+f3"] = "find-replace:select-next",
["ctrl+shift+f3"] = "find-replace:select-previous",
["ctrl+l"] = "doc:select-lines",
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
["ctrl+/"] = "doc:toggle-line-comments",
@ -236,11 +193,6 @@ keymap.add_direct {
["pageup"] = "doc:move-to-previous-page",
["pagedown"] = "doc:move-to-next-page",
["shift+1lclick"] = "doc:select-to-cursor",
["ctrl+1lclick"] = "doc:split-cursor",
["1lclick"] = "doc:set-cursor",
["2lclick"] = "doc:set-cursor-word",
["3lclick"] = "doc:set-cursor-line",
["shift+left"] = "doc:select-to-previous-char",
["shift+right"] = "doc:select-to-next-char",
["shift+up"] = "doc:select-to-previous-line",
@ -260,4 +212,3 @@ keymap.add_direct {
}
return keymap

View File

@ -1,15 +0,0 @@
local modkeys = {}
modkeys.map = {
["left amiga"] = "cmd",
["right amiga"] = "cmd",
["control"] = "ctrl",
["left shift"] = "shift",
["right shift"] = "shift",
["left alt"] = "alt",
["right alt"] = "altgr",
}
modkeys.keys = { "cmd", "ctrl", "alt", "altgr", "shift" }
return modkeys

View File

@ -1,737 +0,0 @@
local core = require "core"
local common = require "core.common"
local config = require "core.config"
local style = require "core.style"
local Object = require "core.object"
local EmptyView = require "core.emptyview"
local View = require "core.view"
local Node = Object:extend()
function Node:new(type)
self.type = type or "leaf"
self.position = { x = 0, y = 0 }
self.size = { x = 0, y = 0 }
self.views = {}
self.divider = 0.5
if self.type == "leaf" then
self:add_view(EmptyView())
end
self.hovered = {x = -1, y = -1 }
self.hovered_close = 0
self.tab_shift = 0
self.tab_offset = 1
self.tab_width = style.tab_width
self.move_towards = View.move_towards
end
function Node:propagate(fn, ...)
self.a[fn](self.a, ...)
self.b[fn](self.b, ...)
end
function Node:on_mouse_moved(x, y, ...)
if self.type == "leaf" then
self.hovered.x, self.hovered.y = x, y
self.active_view:on_mouse_moved(x, y, ...)
else
self:propagate("on_mouse_moved", x, y, ...)
end
end
function Node:on_mouse_released(...)
if self.type == "leaf" then
self.active_view:on_mouse_released(...)
else
self:propagate("on_mouse_released", ...)
end
end
function Node:consume(node)
for k, _ in pairs(self) do self[k] = nil end
for k, v in pairs(node) do self[k] = v end
end
local type_map = { up="vsplit", down="vsplit", left="hsplit", right="hsplit" }
-- The "locked" argument below should be in the form {x = <boolean>, y = <boolean>}
-- and it indicates if the node want to have a fixed size along the axis where the
-- boolean is true. If not it will be expanded to take all the available space.
-- The "resizable" flag indicates if, along the "locked" axis the node can be resized
-- by the user. If the node is marked as resizable their view should provide a
-- set_target_size method.
function Node:split(dir, view, locked, resizable)
assert(self.type == "leaf", "Tried to split non-leaf node")
local node_type = assert(type_map[dir], "Invalid direction")
local last_active = core.active_view
local child = Node()
child:consume(self)
self:consume(Node(node_type))
self.a = child
self.b = Node()
if view then self.b:add_view(view) end
if locked then
assert(type(locked) == 'table')
self.b.locked = locked
self.b.resizable = resizable or false
core.set_active_view(last_active)
end
if dir == "up" or dir == "left" then
self.a, self.b = self.b, self.a
return self.a
end
return self.b
end
function Node:remove_view(root, view)
if #self.views > 1 then
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)
if self.active_view == view then
self:set_active_view(self.views[idx] or self.views[#self.views])
end
else
local parent = self:get_parent_node(root)
local is_a = (parent.a == self)
local other = parent[is_a and "b" or "a"]
local locked_size_x, locked_size_y = other:get_locked_size()
local locked_size
if parent.type == "hsplit" then
locked_size = locked_size_x
else
locked_size = locked_size_y
end
local next_primary
if self.is_primary_node then
next_primary = core.root_view:select_next_primary_node()
end
if locked_size or (self.is_primary_node and not next_primary) then
self.views = {}
self:add_view(EmptyView())
else
if other == next_primary then
next_primary = parent
end
parent:consume(other)
local p = parent
while p.type ~= "leaf" do
p = p[is_a and "a" or "b"]
end
p:set_active_view(p.active_view)
if self.is_primary_node then
next_primary.is_primary_node = true
end
end
end
core.last_active_view = nil
end
function Node:close_view(root, view)
local do_close = function()
self:remove_view(root, view)
end
view:try_close(do_close)
end
function Node:close_active_view(root)
self:close_view(root, self.active_view)
end
function Node:add_view(view, idx)
assert(self.type == "leaf", "Tried to add view to non-leaf node")
assert(not self.locked, "Tried to add view to locked node")
if self.views[1] and self.views[1]:is(EmptyView) then
table.remove(self.views)
end
table.insert(self.views, idx or (#self.views + 1), view)
self:set_active_view(view)
end
function Node:set_active_view(view)
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
self.active_view = view
core.set_active_view(view)
end
function Node:get_view_idx(view)
for i, v in ipairs(self.views) do
if v == view then return i end
end
end
function Node:get_node_for_view(view)
for _, v in ipairs(self.views) do
if v == view then return self end
end
if self.type ~= "leaf" then
return self.a:get_node_for_view(view) or self.b:get_node_for_view(view)
end
end
function Node:get_parent_node(root)
if root.a == self or root.b == self then
return root
elseif root.type ~= "leaf" then
return self:get_parent_node(root.a) or self:get_parent_node(root.b)
end
end
function Node:get_children(t)
t = t or {}
for _, view in ipairs(self.views) do
table.insert(t, view)
end
if self.a then self.a:get_children(t) end
if self.b then self.b:get_children(t) end
return t
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)
if self.type ~= "leaf" then
local axis = self.type == "hsplit" and "x" or "y"
if self.a:is_resizable(axis) and self.b:is_resizable(axis) then
local p = 6
local x, y, w, h = self:get_divider_rect()
x, y = x - p, y - p
w, h = w + p * 2, h + p * 2
if px > x and py > y and px < x + w and py < y + h then
return self
end
end
return self.a:get_divider_overlapping_point(px, py)
or self.b: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)
if not self:should_show_tabs() then return nil end
local tabs_number = self:get_visible_tabs_number()
local x1, y1, w, h = self:get_tab_rect(self.tab_offset)
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
function Node:should_show_tabs()
if self.locked then return false end
local dn = core.root_view.dragged_node
if #self.views > 1
or (dn and dn.dragging) then -- show tabs while dragging
return true
elseif config.always_show_tabs then
return not self.views[1]:is(EmptyView)
end
return false
end
local function close_button_location(x, w)
local cw = style.icon_font:get_width("C")
local pad = style.padding.y
return x + w - pad - cw, cw, pad
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)
local tab_index = self:get_tab_overlapping_point(px, py)
self.hovered_tab = tab_index
self.hovered_close = 0
self.hovered_scroll_button = 0
if tab_index then
local x, y, w, h = self:get_tab_rect(tab_index)
local cx, cw = close_button_location(x, w)
if px >= cx and px < cx + cw and py >= y and py < y + h and config.tab_close_button then
self.hovered_close = tab_index
end
else
self.hovered_scroll_button = self:get_scroll_button_index(px, py) or 0
end
end
function Node:get_child_overlapping_point(x, y)
local child
if self.type == "leaf" then
return self
elseif self.type == "hsplit" then
child = (x < self.b.position.x) and self.a or self.b
elseif self.type == "vsplit" then
child = (y < self.b.position.y) and self.a or self.b
end
return child:get_child_overlapping_point(x, y)
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)
local sbw = get_scroll_button_width()
local maxw = self.size.x - 2 * sbw
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
return x1, self.position.y, x2 - x1, h
end
function Node:get_divider_rect()
local x, y = self.position.x, self.position.y
if self.type == "hsplit" then
return x + self.a.size.x, y, style.divider_size, self.size.y
elseif self.type == "vsplit" then
return x, y + self.a.size.y, self.size.x, style.divider_size
end
end
-- Return two values for x and y axis and each of them is either falsy or a number.
-- A falsy value indicate no fixed size along the corresponding direction.
function Node:get_locked_size()
if self.type == "leaf" then
if self.locked then
local size = self.active_view.size
-- The values below should be either a falsy value or a number
local sx = (self.locked and self.locked.x) and size.x
local sy = (self.locked and self.locked.y) and size.y
return sx, sy
end
else
local x1, y1 = self.a:get_locked_size()
local x2, y2 = self.b:get_locked_size()
-- The values below should be either a falsy value or a number
local sx, sy
if self.type == 'hsplit' then
if x1 and x2 then
local dsx = (x1 < 1 or x2 < 1) and 0 or style.divider_size
sx = x1 + x2 + dsx
end
sy = y1 or y2
else
if y1 and y2 then
local dsy = (y1 < 1 or y2 < 1) and 0 or style.divider_size
sy = y1 + y2 + dsy
end
sx = x1 or x2
end
return sx, sy
end
end
function Node.copy_position_and_size(dst, src)
dst.position.x, dst.position.y = src.position.x, src.position.y
dst.size.x, dst.size.y = src.size.x, src.size.y
end
-- calculating the sizes is the same for hsplits and vsplits, except the x/y
-- axis are swapped; this function lets us use the same code for both
local function calc_split_sizes(self, x, y, x1, x2, y1, y2)
local ds = ((x1 and x1 < 1) or (x2 and x2 < 1)) and 0 or style.divider_size
local n = x1 and x1 + ds or (x2 and self.size[x] - x2 or math.floor(self.size[x] * self.divider))
self.a.position[x] = self.position[x]
self.a.position[y] = self.position[y]
self.a.size[x] = n - ds
self.a.size[y] = self.size[y]
self.b.position[x] = self.position[x] + n
self.b.position[y] = self.position[y]
self.b.size[x] = self.size[x] - n
self.b.size[y] = self.size[y]
end
function Node:update_layout()
if self.type == "leaf" then
local av = self.active_view
if self:should_show_tabs() then
local _, _, _, th = self:get_tab_rect(1)
av.position.x, av.position.y = self.position.x, self.position.y + th
av.size.x, av.size.y = self.size.x, self.size.y - th
else
Node.copy_position_and_size(av, self)
end
else
local x1, y1 = self.a:get_locked_size()
local x2, y2 = self.b:get_locked_size()
if self.type == "hsplit" then
calc_split_sizes(self, "x", "y", x1, x2)
elseif self.type == "vsplit" then
calc_split_sizes(self, "y", "x", y1, y2)
end
self.a:update_layout()
self.b: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()
if self.type == "leaf" then
self:scroll_tabs_to_visible()
for _, view in ipairs(self.views) do
view:update()
end
self:tab_hovered_update(self.hovered.x, self.hovered.y)
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
self.a:update()
self.b:update()
end
end
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
local ds = style.divider_size
local dots_width = style.font:get_width("")
local color = style.dim
local padding_y = style.padding.y
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
if standalone then
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
end
if is_active then
color = style.text
renderer.draw_rect(x, y, w, h, style.background)
renderer.draw_rect(x + w, y, ds, h, style.divider)
renderer.draw_rect(x - ds, y, ds, h, style.divider)
end
local cx, cw, cspace = close_button_location(x, w)
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
if show_close_button then
local close_style = is_close_hovered and style.text or style.dim
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
end
if is_hovered then
color = style.text
end
local padx = style.padding.x
-- Normally we should substract "cspace" from text_avail_width and from the
-- clipping width. It is the padding space we give to the left and right of the
-- close button. However, since we are using dots to terminate filenames, we
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
-- close button.
local text_avail_width = cx - x - padx
core.push_clip_rect(x, y, cx - x, h)
x, w = x + padx, w - padx * 2
local align = "center"
if style.font:get_width(text) > text_avail_width then
align = "left"
for i = 1, #text do
local reduced_text = text:sub(1, #text - i)
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
text = reduced_text .. ""
break
end
end
end
common.draw_text(style.font, color, text, align, x, y, w, h)
core.pop_clip_rect()
end
function Node:draw_tabs()
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
local ds = style.divider_size
local dots_width = style.font:get_width("")
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 + h - ds, self.size.x, ds, style.divider)
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)
self:draw_tab(view:get_name(), view == self.active_view,
i == self.hovered_tab, i == self.hovered_close,
x, y, w, h)
end
core.pop_clip_rect()
end
function Node:draw()
if self.type == "leaf" then
if self:should_show_tabs() then
self:draw_tabs()
end
local pos, size = self.active_view.position, self.active_view.size
core.push_clip_rect(pos.x, pos.y, size.x, size.y)
self.active_view:draw()
core.pop_clip_rect()
else
local x, y, w, h = self:get_divider_rect()
renderer.draw_rect(x, y, w, h, style.divider)
self:propagate("draw")
end
end
function Node:is_empty()
if self.type == "leaf" then
return #self.views == 0 or (#self.views == 1 and self.views[1]:is(EmptyView))
else
return self.a:is_empty() and self.b:is_empty()
end
end
function Node:close_all_docviews(keep_active)
local node_active_view = self.active_view
local lost_active_view = false
if self.type == "leaf" then
local i = 1
while i <= #self.views do
local view = self.views[i]
if view.context == "session" and (not keep_active or view ~= self.active_view) then
table.remove(self.views, i)
if view == node_active_view then
lost_active_view = true
end
else
i = i + 1
end
end
self.tab_offset = 1
if #self.views == 0 and self.is_primary_node then
-- if we are not the primary view and we had the active view it doesn't
-- matter to reattribute the active view because, within the close_all_docviews
-- top call, the primary node will take the active view anyway.
-- Set the empty view and takes the active view.
self:add_view(EmptyView())
elseif #self.views > 0 and lost_active_view then
-- In practice we never get there but if a view remain we need
-- to reset the Node's active view.
self:set_active_view(self.views[1])
end
else
self.a:close_all_docviews(keep_active)
self.b:close_all_docviews(keep_active)
if self.a:is_empty() and not self.a.is_primary_node then
self:consume(self.b)
elseif self.b:is_empty() and not self.b.is_primary_node then
self:consume(self.a)
end
end
end
-- Returns true for nodes that accept either "proportional" resizes (based on the
-- node.divider) or "locked" resizable nodes (along the resize axis).
function Node:is_resizable(axis)
if self.type == 'leaf' then
return not self.locked or not self.locked[axis] or self.resizable
else
local a_resizable = self.a:is_resizable(axis)
local b_resizable = self.b:is_resizable(axis)
return a_resizable and b_resizable
end
end
-- Return true iff it is a locked pane along the rezise axis and is
-- declared "resizable".
function Node:is_locked_resizable(axis)
return self.locked and self.locked[axis] and self.resizable
end
function Node:resize(axis, value)
-- the application works fine with non-integer values but to have pixel-perfect
-- placements of view elements, like the scrollbar, we round the value to be
-- an integer.
value = math.floor(value)
if self.type == 'leaf' then
-- If it is not locked we don't accept the
-- resize operation here because for proportional panes the resize is
-- done using the "divider" value of the parent node.
if self:is_locked_resizable(axis) then
return self.active_view:set_target_size(axis, value)
end
else
if self.type == (axis == "x" and "hsplit" or "vsplit") then
-- we are resizing a node that is splitted along the resize axis
if self.a:is_locked_resizable(axis) and self.b:is_locked_resizable(axis) then
local rem_value = value - self.a.size[axis]
if rem_value >= 0 then
return self.b.active_view:set_target_size(axis, rem_value)
else
self.b.active_view:set_target_size(axis, 0)
return self.a.active_view:set_target_size(axis, value)
end
end
else
-- we are resizing a node that is splitted along the axis perpendicular
-- to the resize axis
local a_resizable = self.a:is_resizable(axis)
local b_resizable = self.b:is_resizable(axis)
if a_resizable and b_resizable then
self.a:resize(axis, value)
self.b:resize(axis, value)
end
end
end
end
function Node:get_split_type(mouse_x, mouse_y)
local x, y = self.position.x, self.position.y
local w, h = self.size.x, self.size.y
local _, _, _, tab_h = self:get_scroll_button_rect(1)
y = y + tab_h
h = h - tab_h
local local_mouse_x = mouse_x - x
local local_mouse_y = mouse_y - y
if local_mouse_y < 0 then
return "tab"
else
local left_pct = local_mouse_x * 100 / w
local top_pct = local_mouse_y * 100 / h
if left_pct <= 30 then
return "left"
elseif left_pct >= 70 then
return "right"
elseif top_pct <= 30 then
return "up"
elseif top_pct >= 70 then
return "down"
end
return "middle"
end
end
function Node:get_drag_overlay_tab_position(x, y, dragged_node, dragged_index)
local tab_index = self:get_tab_overlapping_point(x, y)
if not tab_index then
local first_tab_x = self:get_tab_rect(1)
if x < first_tab_x then
-- mouse before first visible tab
tab_index = self.tab_offset or 1
else
-- mouse after last visible tab
tab_index = self:get_visible_tabs_number() + (self.tab_offset - 1 or 0)
end
end
local tab_x, tab_y, tab_w, tab_h = self:get_tab_rect(tab_index)
if x > tab_x + tab_w / 2 and tab_index <= #self.views then
-- use next tab
tab_x = tab_x + tab_w
tab_index = tab_index + 1
end
if self == dragged_node and dragged_index and tab_index > dragged_index then
-- the tab we are moving is counted in tab_index
tab_index = tab_index - 1
tab_x = tab_x - tab_w
end
return tab_index, tab_x, tab_y, tab_w, tab_h
end
return Node

View File

@ -1,3 +1,4 @@
-- 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
@ -5,8 +6,7 @@ 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)
local s, e = regex.cmatch(pattern, string, offset or 1, options or 0)
return s, e and e - 1
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

View File

@ -1,11 +1,776 @@
local core = require "core"
local common = require "core.common"
local config = require "core.config"
local style = require "core.style"
local Node = require "core.node"
local keymap = require "core.keymap"
local Object = require "core.object"
local View = require "core.view"
local NagView = require "core.nagview"
local DocView = require "core.docview"
local EmptyView = View:extend()
local function draw_text(x, y, color)
local th = style.big_font:get_height()
local dh = 2 * th + style.padding.y * 2
local x1, y1 = x, y + (dh - th) / 2
x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color)
renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color)
x = x + style.padding.x
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
local lines = {
{ fmt = "%s to run a command", cmd = "core:find-command" },
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
}
th = style.font:get_height()
y = y + (dh - (th + style.padding.y) * #lines) / 2
local w = 0
for _, line in ipairs(lines) do
local text = string.format(line.fmt, keymap.get_binding(line.cmd))
w = math.max(w, renderer.draw_text(style.font, text, x + style.padding.x, y, color))
y = y + th + style.padding.y
end
return w, dh
end
function EmptyView:draw()
self:draw_background(style.background)
local w, h = draw_text(0, 0, { 0, 0, 0, 0 })
local x = self.position.x + math.max(style.padding.x, (self.size.x - w) / 2)
local y = self.position.y + (self.size.y - h) / 2
draw_text(x, y, style.dim)
end
local Node = Object:extend()
function Node:new(type)
self.type = type or "leaf"
self.position = { x = 0, y = 0 }
self.size = { x = 0, y = 0 }
self.views = {}
self.divider = 0.5
if self.type == "leaf" then
self:add_view(EmptyView())
end
self.hovered = {x = -1, y = -1 }
self.hovered_close = 0
self.tab_shift = 0
self.tab_offset = 1
self.tab_width = style.tab_width
self.move_towards = View.move_towards
end
function Node:propagate(fn, ...)
self.a[fn](self.a, ...)
self.b[fn](self.b, ...)
end
function Node:on_mouse_moved(x, y, ...)
if self.type == "leaf" then
self.hovered.x, self.hovered.y = x, y
self.active_view:on_mouse_moved(x, y, ...)
else
self:propagate("on_mouse_moved", x, y, ...)
end
end
function Node:on_mouse_released(...)
if self.type == "leaf" then
self.active_view:on_mouse_released(...)
else
self:propagate("on_mouse_released", ...)
end
end
function Node:consume(node)
for k, _ in pairs(self) do self[k] = nil end
for k, v in pairs(node) do self[k] = v end
end
local type_map = { up="vsplit", down="vsplit", left="hsplit", right="hsplit" }
-- The "locked" argument below should be in the form {x = <boolean>, y = <boolean>}
-- and it indicates if the node want to have a fixed size along the axis where the
-- boolean is true. If not it will be expanded to take all the available space.
-- The "resizable" flag indicates if, along the "locked" axis the node can be resized
-- by the user. If the node is marked as resizable their view should provide a
-- set_target_size method.
function Node:split(dir, view, locked, resizable)
assert(self.type == "leaf", "Tried to split non-leaf node")
local node_type = assert(type_map[dir], "Invalid direction")
local last_active = core.active_view
local child = Node()
child:consume(self)
self:consume(Node(node_type))
self.a = child
self.b = Node()
if view then self.b:add_view(view) end
if locked then
assert(type(locked) == 'table')
self.b.locked = locked
self.b.resizable = resizable or false
core.set_active_view(last_active)
end
if dir == "up" or dir == "left" then
self.a, self.b = self.b, self.a
return self.a
end
return self.b
end
function Node:remove_view(root, view)
if #self.views > 1 then
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)
if self.active_view == view then
self:set_active_view(self.views[idx] or self.views[#self.views])
end
else
local parent = self:get_parent_node(root)
local is_a = (parent.a == self)
local other = parent[is_a and "b" or "a"]
local locked_size_x, locked_size_y = other:get_locked_size()
local locked_size
if parent.type == "hsplit" then
locked_size = locked_size_x
else
locked_size = locked_size_y
end
if self.is_primary_node or locked_size then
self.views = {}
self:add_view(EmptyView())
else
parent:consume(other)
local p = parent
while p.type ~= "leaf" do
p = p[is_a and "a" or "b"]
end
p:set_active_view(p.active_view)
if self.is_primary_node then
p.is_primary_node = true
end
end
end
core.last_active_view = nil
end
function Node:close_view(root, view)
local do_close = function()
self:remove_view(root, view)
end
view:try_close(do_close)
end
function Node:close_active_view(root)
self:close_view(root, self.active_view)
end
function Node:add_view(view, idx)
assert(self.type == "leaf", "Tried to add view to non-leaf node")
assert(not self.locked, "Tried to add view to locked node")
if self.views[1] and self.views[1]:is(EmptyView) then
table.remove(self.views)
end
table.insert(self.views, idx or (#self.views + 1), view)
self:set_active_view(view)
end
function Node:set_active_view(view)
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
self.active_view = view
core.set_active_view(view)
end
function Node:get_view_idx(view)
for i, v in ipairs(self.views) do
if v == view then return i end
end
end
function Node:get_node_for_view(view)
for _, v in ipairs(self.views) do
if v == view then return self end
end
if self.type ~= "leaf" then
return self.a:get_node_for_view(view) or self.b:get_node_for_view(view)
end
end
function Node:get_parent_node(root)
if root.a == self or root.b == self then
return root
elseif root.type ~= "leaf" then
return self:get_parent_node(root.a) or self:get_parent_node(root.b)
end
end
function Node:get_children(t)
t = t or {}
for _, view in ipairs(self.views) do
table.insert(t, view)
end
if self.a then self.a:get_children(t) end
if self.b then self.b:get_children(t) end
return t
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)
if self.type ~= "leaf" then
local axis = self.type == "hsplit" and "x" or "y"
if self.a:is_resizable(axis) and self.b:is_resizable(axis) then
local p = 6
local x, y, w, h = self:get_divider_rect()
x, y = x - p, y - p
w, h = w + p * 2, h + p * 2
if px > x and py > y and px < x + w and py < y + h then
return self
end
end
return self.a:get_divider_overlapping_point(px, py)
or self.b: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)
if not self:should_show_tabs() then return nil end
local tabs_number = self:get_visible_tabs_number()
local x1, y1, w, h = self:get_tab_rect(self.tab_offset)
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
function Node:should_show_tabs()
if self.locked then return false end
local dn = core.root_view.dragged_node
if #self.views > 1
or (dn and dn.dragging) then -- show tabs while dragging
return true
elseif config.always_show_tabs then
return not self.views[1]:is(EmptyView)
end
return false
end
local function close_button_location(x, w)
local cw = style.icon_font:get_width("C")
local pad = style.padding.y
return x + w - pad - cw, cw, pad
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)
local tab_index = self:get_tab_overlapping_point(px, py)
self.hovered_tab = tab_index
self.hovered_close = 0
self.hovered_scroll_button = 0
if tab_index then
local x, y, w, h = self:get_tab_rect(tab_index)
local cx, cw = close_button_location(x, w)
if px >= cx and px < cx + cw and py >= y and py < y + h and config.tab_close_button then
self.hovered_close = tab_index
end
else
self.hovered_scroll_button = self:get_scroll_button_index(px, py) or 0
end
end
function Node:get_child_overlapping_point(x, y)
local child
if self.type == "leaf" then
return self
elseif self.type == "hsplit" then
child = (x < self.b.position.x) and self.a or self.b
elseif self.type == "vsplit" then
child = (y < self.b.position.y) and self.a or self.b
end
return child:get_child_overlapping_point(x, y)
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)
local sbw = get_scroll_button_width()
local maxw = self.size.x - 2 * sbw
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
return x1, self.position.y, x2 - x1, h
end
function Node:get_divider_rect()
local x, y = self.position.x, self.position.y
if self.type == "hsplit" then
return x + self.a.size.x, y, style.divider_size, self.size.y
elseif self.type == "vsplit" then
return x, y + self.a.size.y, self.size.x, style.divider_size
end
end
-- Return two values for x and y axis and each of them is either falsy or a number.
-- A falsy value indicate no fixed size along the corresponding direction.
function Node:get_locked_size()
if self.type == "leaf" then
if self.locked then
local size = self.active_view.size
-- The values below should be either a falsy value or a number
local sx = (self.locked and self.locked.x) and size.x
local sy = (self.locked and self.locked.y) and size.y
return sx, sy
end
else
local x1, y1 = self.a:get_locked_size()
local x2, y2 = self.b:get_locked_size()
-- The values below should be either a falsy value or a number
local sx, sy
if self.type == 'hsplit' then
if x1 and x2 then
local dsx = (x1 < 1 or x2 < 1) and 0 or style.divider_size
sx = x1 + x2 + dsx
end
sy = y1 or y2
else
if y1 and y2 then
local dsy = (y1 < 1 or y2 < 1) and 0 or style.divider_size
sy = y1 + y2 + dsy
end
sx = x1 or x2
end
return sx, sy
end
end
local function copy_position_and_size(dst, src)
dst.position.x, dst.position.y = src.position.x, src.position.y
dst.size.x, dst.size.y = src.size.x, src.size.y
end
-- calculating the sizes is the same for hsplits and vsplits, except the x/y
-- axis are swapped; this function lets us use the same code for both
local function calc_split_sizes(self, x, y, x1, x2, y1, y2)
local n
local ds = ((x1 and x1 < 1) or (x2 and x2 < 1)) and 0 or style.divider_size
if x1 then
n = x1 + ds
elseif x2 then
n = self.size[x] - x2
else
n = math.floor(self.size[x] * self.divider)
end
self.a.position[x] = self.position[x]
self.a.position[y] = self.position[y]
self.a.size[x] = n - ds
self.a.size[y] = self.size[y]
self.b.position[x] = self.position[x] + n
self.b.position[y] = self.position[y]
self.b.size[x] = self.size[x] - n
self.b.size[y] = self.size[y]
end
function Node:update_layout()
if self.type == "leaf" then
local av = self.active_view
if self:should_show_tabs() then
local _, _, _, th = self:get_tab_rect(1)
av.position.x, av.position.y = self.position.x, self.position.y + th
av.size.x, av.size.y = self.size.x, self.size.y - th
else
copy_position_and_size(av, self)
end
else
local x1, y1 = self.a:get_locked_size()
local x2, y2 = self.b:get_locked_size()
if self.type == "hsplit" then
calc_split_sizes(self, "x", "y", x1, x2)
elseif self.type == "vsplit" then
calc_split_sizes(self, "y", "x", y1, y2)
end
self.a:update_layout()
self.b: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()
if self.type == "leaf" then
self:scroll_tabs_to_visible()
for _, view in ipairs(self.views) do
view:update()
end
self:tab_hovered_update(self.hovered.x, self.hovered.y)
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
self.a:update()
self.b:update()
end
end
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
local ds = style.divider_size
local dots_width = style.font:get_width("")
local color = style.dim
local padding_y = style.padding.y
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
if standalone then
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
end
if is_active then
color = style.text
renderer.draw_rect(x, y, w, h, style.background)
renderer.draw_rect(x + w, y, ds, h, style.divider)
renderer.draw_rect(x - ds, y, ds, h, style.divider)
end
local cx, cw, cspace = close_button_location(x, w)
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
if show_close_button then
local close_style = is_close_hovered and style.text or style.dim
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
end
if is_hovered then
color = style.text
end
local padx = style.padding.x
-- Normally we should substract "cspace" from text_avail_width and from the
-- clipping width. It is the padding space we give to the left and right of the
-- close button. However, since we are using dots to terminate filenames, we
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
-- close button.
local text_avail_width = cx - x - padx
core.push_clip_rect(x, y, cx - x, h)
x, w = x + padx, w - padx * 2
local align = "center"
if style.font:get_width(text) > text_avail_width then
align = "left"
for i = 1, #text do
local reduced_text = text:sub(1, #text - i)
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
text = reduced_text .. ""
break
end
end
end
common.draw_text(style.font, color, text, align, x, y, w, h)
core.pop_clip_rect()
end
function Node:draw_tabs()
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
local ds = style.divider_size
local dots_width = style.font:get_width("")
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 + h - ds, self.size.x, ds, style.divider)
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)
self:draw_tab(view:get_name(), view == self.active_view,
i == self.hovered_tab, i == self.hovered_close,
x, y, w, h)
end
core.pop_clip_rect()
end
function Node:draw()
if self.type == "leaf" then
if self:should_show_tabs() then
self:draw_tabs()
end
local pos, size = self.active_view.position, self.active_view.size
core.push_clip_rect(pos.x, pos.y, size.x + pos.x % 1, size.y + pos.y % 1)
self.active_view:draw()
core.pop_clip_rect()
else
local x, y, w, h = self:get_divider_rect()
renderer.draw_rect(x, y, w, h, style.divider)
self:propagate("draw")
end
end
function Node:is_empty()
if self.type == "leaf" then
return #self.views == 0 or (#self.views == 1 and self.views[1]:is(EmptyView))
else
return self.a:is_empty() and self.b:is_empty()
end
end
function Node:close_all_docviews(keep_active)
local node_active_view = self.active_view
local lost_active_view = false
if self.type == "leaf" then
local i = 1
while i <= #self.views do
local view = self.views[i]
if view.context == "session" and (not keep_active or view ~= self.active_view) then
table.remove(self.views, i)
if view == node_active_view then
lost_active_view = true
end
else
i = i + 1
end
end
self.tab_offset = 1
if #self.views == 0 and self.is_primary_node then
-- if we are not the primary view and we had the active view it doesn't
-- matter to reattribute the active view because, within the close_all_docviews
-- top call, the primary node will take the active view anyway.
-- Set the empty view and takes the active view.
self:add_view(EmptyView())
elseif #self.views > 0 and lost_active_view then
-- In practice we never get there but if a view remain we need
-- to reset the Node's active view.
self:set_active_view(self.views[1])
end
else
self.a:close_all_docviews(keep_active)
self.b:close_all_docviews(keep_active)
if self.a:is_empty() and not self.a.is_primary_node then
self:consume(self.b)
elseif self.b:is_empty() and not self.b.is_primary_node then
self:consume(self.a)
end
end
end
-- Returns true for nodes that accept either "proportional" resizes (based on the
-- node.divider) or "locked" resizable nodes (along the resize axis).
function Node:is_resizable(axis)
if self.type == 'leaf' then
return not self.locked or not self.locked[axis] or self.resizable
else
local a_resizable = self.a:is_resizable(axis)
local b_resizable = self.b:is_resizable(axis)
return a_resizable and b_resizable
end
end
-- Return true iff it is a locked pane along the rezise axis and is
-- declared "resizable".
function Node:is_locked_resizable(axis)
return self.locked and self.locked[axis] and self.resizable
end
function Node:resize(axis, value)
if self.type == 'leaf' then
-- If it is not locked we don't accept the
-- resize operation here because for proportional panes the resize is
-- done using the "divider" value of the parent node.
if self:is_locked_resizable(axis) then
return self.active_view:set_target_size(axis, value)
end
else
if self.type == (axis == "x" and "hsplit" or "vsplit") then
-- we are resizing a node that is splitted along the resize axis
if self.a:is_locked_resizable(axis) and self.b:is_locked_resizable(axis) then
local rem_value = value - self.a.size[axis]
if rem_value >= 0 then
return self.b.active_view:set_target_size(axis, rem_value)
else
self.b.active_view:set_target_size(axis, 0)
return self.a.active_view:set_target_size(axis, value)
end
end
else
-- we are resizing a node that is splitted along the axis perpendicular
-- to the resize axis
local a_resizable = self.a:is_resizable(axis)
local b_resizable = self.b:is_resizable(axis)
if a_resizable and b_resizable then
self.a:resize(axis, value)
self.b:resize(axis, value)
end
end
end
end
function Node:get_split_type(mouse_x, mouse_y)
local x, y = self.position.x, self.position.y
local w, h = self.size.x, self.size.y
local _, _, _, tab_h = self:get_scroll_button_rect(1)
y = y + tab_h
h = h - tab_h
local local_mouse_x = mouse_x - x
local local_mouse_y = mouse_y - y
if local_mouse_y < 0 then
return "tab"
else
local left_pct = local_mouse_x * 100 / w
local top_pct = local_mouse_y * 100 / h
if left_pct <= 30 then
return "left"
elseif left_pct >= 70 then
return "right"
elseif top_pct <= 30 then
return "up"
elseif top_pct >= 70 then
return "down"
end
return "middle"
end
end
function Node:get_drag_overlay_tab_position(x, y, dragged_node, dragged_index)
local tab_index = self:get_tab_overlapping_point(x, y)
if not tab_index then
local first_tab_x = self:get_tab_rect(1)
if x < first_tab_x then
-- mouse before first visible tab
tab_index = self.tab_offset or 1
else
-- mouse after last visible tab
tab_index = self:get_visible_tabs_number() + (self.tab_offset - 1 or 0)
end
end
local tab_x, tab_y, tab_w, tab_h = self:get_tab_rect(tab_index)
if x > tab_x + tab_w / 2 and tab_index <= #self.views then
-- use next tab
tab_x = tab_x + tab_w
tab_index = tab_index + 1
end
if self == dragged_node and dragged_index and tab_index > dragged_index then
-- the tab we are moving is counted in tab_index
tab_index = tab_index - 1
tab_x = tab_x - tab_w
end
return tab_index, tab_x, tab_y, tab_w, tab_h
end
local RootView = View:extend()
function RootView:new()
@ -61,24 +826,6 @@ function RootView:get_primary_node()
end
local function select_next_primary_node(node)
if node.is_primary_node then return end
if node.type ~= "leaf" then
return select_next_primary_node(node.a) or select_next_primary_node(node.b)
else
local lx, ly = node:get_locked_size()
if not lx and not ly then
return node
end
end
end
function RootView:select_next_primary_node()
return select_next_primary_node(self.root_node)
end
function RootView:open_doc(doc)
local node = self:get_active_node_default()
for i, view in ipairs(node.views) do
@ -108,30 +855,30 @@ end
function RootView:on_mouse_pressed(button, x, y, clicks)
local div = self.root_node:get_divider_overlapping_point(x, y)
local node = self.root_node:get_child_overlapping_point(x, y)
if div and (node and not node.active_view:scrollbar_overlaps_point(x, y)) then
if div then
self.dragged_divider = div
return true
return
end
local node = self.root_node:get_child_overlapping_point(x, y)
if node.hovered_scroll_button > 0 then
node:scroll_tabs(node.hovered_scroll_button)
return true
return
end
local idx = node:get_tab_overlapping_point(x, y)
if idx then
if button == "middle" or node.hovered_close == idx then
node:close_view(self.root_node, node.views[idx])
return true
else
if button == "left" then
self.dragged_node = { node = node, idx = idx, dragging = false, drag_start_x = x, drag_start_y = y}
end
node:set_active_view(node.views[idx])
return true
end
elseif not self.dragged_node then -- avoid sending on_mouse_pressed events when dragging tabs
core.set_active_view(node.active_view)
return self.on_view_mouse_pressed(button, x, y, clicks) or node.active_view:on_mouse_pressed(button, x, y, clicks)
if not self.on_view_mouse_pressed(button, x, y, clicks) then
node.active_view:on_mouse_pressed(button, x, y, clicks)
end
end
end
@ -253,18 +1000,17 @@ function RootView:on_mouse_moved(x, y, dx, dy)
self.root_node:on_mouse_moved(x, y, dx, dy)
self.overlapping_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 tab_index = self.overlapping_node and self.overlapping_node:get_tab_overlapping_point(x, y)
if self.overlapping_node and self.overlapping_node:get_scroll_button_index(x, y) then
local tab_index = node and node:get_tab_overlapping_point(x, y)
if node and node:get_scroll_button_index(x, y) then
core.request_cursor("arrow")
elseif div and (self.overlapping_node and not self.overlapping_node.active_view:scrollbar_overlaps_point(x, y)) then
elseif div then
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
elseif tab_index then
core.request_cursor("arrow")
elseif self.overlapping_node then
core.request_cursor(self.overlapping_node.active_view.cursor)
elseif node then
core.request_cursor(node.active_view.cursor)
end
end
@ -272,7 +1018,7 @@ end
function RootView:on_mouse_wheel(...)
local x, y = self.mouse.x, self.mouse.y
local node = self.root_node:get_child_overlapping_point(x, y)
return node.active_view:on_mouse_wheel(...)
node.active_view:on_mouse_wheel(...)
end
@ -299,7 +1045,7 @@ end
function RootView:update()
Node.copy_position_and_size(self.root_node, self)
copy_position_and_size(self.root_node, self)
self.root_node:update()
self.root_node:update_layout()
@ -354,10 +1100,10 @@ function RootView:update_drag_overlay()
if split_type == "tab" and (over ~= self.dragged_node.node or #over.views > 1) then
local tab_index, tab_x, tab_y, tab_w, tab_h = over:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y)
self:set_drag_overlay(self.drag_overlay_tab,
tab_x + (tab_index and 0 or tab_w), tab_y,
style.caret_width, tab_h,
-- avoid showing tab overlay moving between nodes
over ~= self.drag_overlay_tab.last_over)
tab_x + (tab_index and 0 or tab_w), tab_y,
style.caret_width, tab_h,
-- avoid showing tab overlay moving between nodes
over ~= self.drag_overlay_tab.last_over)
self:set_show_overlay(self.drag_overlay, false)
self.drag_overlay_tab.last_over = over
else

View File

@ -1,8 +1,8 @@
-- this file is used by lite-xl to setup the Lua environment when starting
VERSION = "2.0.3r1"
VERSION = "@PROJECT_VERSION@"
MOD_VERSION = "2"
SCALE = tonumber(os.getenv("LITE_SCALE") or os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or SCALE
SCALE = tonumber(os.getenv("LITE_SCALE")) or SCALE
PATHSEP = package.config:sub(1, 1)
EXEDIR = EXEFILE:match("^(.+)[/\\][^/\\]+$")
@ -20,15 +20,3 @@ package.path = DATADIR .. '/?/init.lua;' .. package.path
package.path = USERDIR .. '/?.lua;' .. package.path
package.path = USERDIR .. '/?/init.lua;' .. package.path
local dynamic_suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so')
package.cpath = DATADIR .. '/?.' .. dynamic_suffix .. ";" .. USERDIR .. '/?.' .. dynamic_suffix
package.native_plugins = {}
package.searchers = { package.searchers[1], package.searchers[2], function(modname)
local path = package.searchpath(modname, package.cpath)
if not path then return nil end
return system.load_native_plugin, path
end }
table.pack = table.pack or pack or function(...) return {...} end
table.unpack = table.unpack or unpack

View File

@ -6,7 +6,6 @@ local style = require "core.style"
local DocView = require "core.docview"
local LogView = require "core.logview"
local View = require "core.view"
local Object = require "core.object"
local StatusView = View:extend()
@ -30,7 +29,6 @@ function StatusView:on_mouse_pressed()
and not core.active_view:is(LogView) then
command.perform "core:open-log"
end
return true
end
@ -72,7 +70,7 @@ local function draw_items(self, items, x, y, draw_fn)
local color = style.text
for _, item in ipairs(items) do
if Object.is(item, renderer.font) then
if type(item) == "userdata" then
font = item
elseif type(item) == "table" then
color = item
@ -109,9 +107,9 @@ function StatusView:get_items()
local dv = core.active_view
local line, col = dv.doc:get_selection()
local dirty = dv.doc:is_dirty()
local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info()
local indent_label = (indent_type == "hard") and "tabs: " or "spaces: "
local indent_size_str = tostring(indent_size) .. (indent_confirmed and "" or "*") or "unknown"
local indent = dv.doc.indent_info
local indent_label = (indent and indent.type == "hard") and "tabs: " or "spaces: "
local indent_size = indent and tostring(indent.size) .. (indent.confirmed and "" or "*") or "unknown"
return {
dirty and style.accent or style.text, style.icon_font, "f",

View File

@ -21,48 +21,42 @@ style.tab_width = common.round(170 * SCALE)
--
-- 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.
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 15 * SCALE)
style.big_font = style.font:copy(46 * SCALE)
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 14 * SCALE)
style.big_font = style.font:copy(40 * SCALE)
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 16 * SCALE, {antialiasing="grayscale", hinting="full"})
style.icon_big_font = style.icon_font:copy(23 * SCALE)
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 15 * SCALE)
style.icon_big_font = style.icon_font:copy(24 * SCALE)
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 14 * SCALE)
style.background = { common.color "#2e2e32" } -- Docview
style.background2 = { common.color "#252529" } -- Treeview
style.background3 = { common.color "#252529" } -- Command view
style.background = { common.color "#2e2e32" }
style.background2 = { common.color "#252529" }
style.background3 = { common.color "#252529" }
style.text = { common.color "#97979c" }
style.caret = { common.color "#93DDFA" }
style.accent = { common.color "#e1e1e6" }
-- style.dim - text color for nonactive tabs, tabs divider, prefix in log and
-- search result, hotkeys for context menu and command view
style.dim = { common.color "#525257" }
style.divider = { common.color "#202024" } -- Line between nodes
style.divider = { common.color "#202024" }
style.selection = { common.color "#48484f" }
style.line_number = { common.color "#525259" }
style.line_number2 = { common.color "#83838f" } -- With cursor
style.line_number2 = { common.color "#83838f" }
style.line_highlight = { common.color "#343438" }
style.scrollbar = { common.color "#414146" }
style.scrollbar2 = { common.color "#4b4b52" } -- Hovered
style.scrollbar2 = { common.color "#4b4b52" }
style.nagbar = { common.color "#FF0000" }
style.nagbar_text = { common.color "#FFFFFF" }
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
style.drag_overlay = { common.color "rgba(255,255,255,0.1)" }
style.drag_overlay_tab = { common.color "#93DDFA" }
style.good = { common.color "#72b886" }
style.warn = { common.color "#FFA94D" }
style.error = { common.color "#FF3333" }
style.modified = { common.color "#1c7c9c" }
style.syntax = {}
style.syntax["normal"] = { common.color "#e1e1e6" }
style.syntax["symbol"] = { common.color "#e1e1e6" }
style.syntax["comment"] = { common.color "#676b6f" }
style.syntax["keyword"] = { common.color "#E58AC9" } -- local function end if case
style.syntax["keyword2"] = { common.color "#F77483" } -- self int float
style.syntax["keyword"] = { common.color "#E58AC9" }
style.syntax["keyword2"] = { common.color "#F77483" }
style.syntax["number"] = { common.color "#FFA94D" }
style.syntax["literal"] = { common.color "#FFA94D" } -- true false nil
style.syntax["literal"] = { common.color "#FFA94D" }
style.syntax["string"] = { common.color "#f7c95c" }
style.syntax["operator"] = { common.color "#93DDFA" } -- = + - / < >
style.syntax["operator"] = { common.color "#93DDFA" }
style.syntax["function"] = { common.color "#93DDFA" }
-- This can be used to override fonts per syntax group.

View File

@ -3,7 +3,7 @@ local common = require "core.common"
local syntax = {}
syntax.items = {}
local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} }
local plain_text_syntax = { patterns = {}, symbols = {} }
function syntax.add(t)
@ -22,7 +22,7 @@ end
function syntax.get(filename, header)
return find(filename, "files")
or (header and find(header, "headers"))
or find(header, "headers")
or plain_text_syntax
end

View File

@ -1,5 +1,4 @@
local syntax = require "core.syntax"
local common = require "core.common"
local tokenizer = {}
@ -143,13 +142,8 @@ function tokenizer.tokenize(incoming_syntax, text, state)
code = p._regex
end
repeat
local next = res[2] + 1
-- go to the start of the next utf-8 character
while text:byte(next) and common.is_utf8_cont(text, next) do
next = next + 1
end
res = p.pattern and { text:find(at_start and "^" .. code or code, next) }
or { regex.match(code, text, next, at_start and regex.ANCHORED or 0) }
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
@ -161,7 +155,7 @@ function tokenizer.tokenize(incoming_syntax, text, state)
if count % 2 == 0 then break end
end
until not res[1] or not close or not target[3]
return table.unpack(res)
return unpack(res)
end
while i <= #text do
@ -237,13 +231,8 @@ function tokenizer.tokenize(incoming_syntax, text, state)
-- consume character if we didn't match
if not matched then
local n = 0
-- reach the next character
while text:byte(i + n + 1) and common.is_utf8_cont(text, i + n + 1) do
n = n + 1
end
push_token(res, "normal", text:sub(i, i + n))
i = i + n + 1
push_token(res, "normal", text:sub(i, i))
i = i + 1
end
end

View File

@ -102,10 +102,14 @@ function View:on_text_input(text)
-- no-op
end
function View:on_mouse_wheel(y)
function View:on_mouse_wheel(y)
if self.scrollable then
self.scroll.to.y = self.scroll.to.y + y * -config.mouse_wheel_scroll
end
end
function View:get_content_bounds()
local x = self.scroll.x
local y = self.scroll.y
@ -136,7 +140,7 @@ end
function View:draw_background(color)
local x, y = self.position.x, self.position.y
local w, h = self.size.x, self.size.y
renderer.draw_rect(x, y, w, h, color)
renderer.draw_rect(x, y, w + x % 1, h + y % 1, color)
end

View File

@ -5,11 +5,14 @@ local Doc = require "core.doc"
local times = setmetatable({}, { __mode = "k" })
local autoreload_scan_rate = 5
local function update_time(doc)
local info = system.get_file_info(doc.filename)
times[doc] = info.modified
end
local function reload_doc(doc)
local fp = io.open(doc.filename, "r")
local text = fp:read("*a")
@ -25,19 +28,23 @@ local function reload_doc(doc)
core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename)
end
local on_modify = core.on_dirmonitor_modify
core.on_dirmonitor_modify = function(dir, filepath)
local abs_filename = dir.name .. PATHSEP .. filepath
for _, doc in ipairs(core.docs) do
local info = system.get_file_info(doc.filename or "")
if doc.abs_filename == abs_filename and info and times[doc] ~= info.modified then
reload_doc(doc)
break
core.add_thread(function()
while true do
-- check all doc modified times
for _, doc in ipairs(core.docs) do
local info = system.get_file_info(doc.filename or "")
if info and times[doc] ~= info.modified then
reload_doc(doc)
end
coroutine.yield()
end
-- wait for next scan
coroutine.yield(autoreload_scan_rate)
end
on_modify(dir, filepath)
end
end)
-- patch `Doc.save|load` to store modified time
local load = Doc.load

View File

@ -62,15 +62,15 @@ menu:register("core.logview", {
if require("plugins.scale") then
menu:register("core.docview", {
{ text = "Cut", command = "doc:cut" },
{ text = "Copy", command = "doc:copy" },
{ text = "Paste", command = "doc:paste" },
{ text = "Font +", command = "scale:increase" },
{ text = "Font -", command = "scale:decrease" },
{ text = "Font Reset", command = "scale:reset" },
{ text = "Font +", command = "scale:increase" },
{ text = "Font -", command = "scale:decrease" },
{ text = "Font Reset", command = "scale:reset" },
ContextMenu.DIVIDER,
{ text = "Find", command = "find-replace:find" },
{ text = "Replace", command = "find-replace:replace" }
{ text = "Find", command = "find-replace:find" },
{ text = "Replace", command = "find-replace:replace" },
ContextMenu.DIVIDER,
{ text = "Find Pattern", command = "find-replace:find-pattern" },
{ text = "Replace Pattern", command = "find-replace:replace-pattern" },
})
end

View File

@ -121,17 +121,40 @@ end
local clean = Doc.clean
function Doc:clean(...)
clean(self, ...)
local _, _, confirmed = self:get_indent_info()
if not confirmed then
if not cache[self].confirmed then
update_cache(self)
end
end
local function with_indent_override(doc, fn, ...)
local c = cache[doc]
if not c then
return fn(...)
end
local type, size = config.tab_type, config.indent_size
config.tab_type, config.indent_size = c.type, c.size or config.indent_size
local r1, r2, r3 = fn(...)
config.tab_type, config.indent_size = type, size
return r1, r2, r3
end
local perform = command.perform
function command.perform(...)
return with_indent_override(core.active_view.doc, perform, ...)
end
local draw = DocView.draw
function DocView:draw(...)
return with_indent_override(self.doc, draw, self, ...)
end
local function set_indent_type(doc, type)
local _, indent_size = doc:get_indent_info()
cache[doc] = {type = type,
size = indent_size,
size = cache[doc].value or config.indent_size,
confirmed = true}
doc.indent_info = cache[doc]
end
@ -157,8 +180,7 @@ end
local function set_indent_size(doc, size)
local indent_type = doc:get_indent_info()
cache[doc] = {type = indent_type,
cache[doc] = {type = cache[doc].type or config.tab_type,
size = size,
confirmed = true}
doc.indent_info = cache[doc]

View File

@ -1,36 +0,0 @@
-- mod-version:2 -- lite-xl 2.0
local style = require "core.style"
local DocView = require "core.docview"
local common = require "core.common"
local draw_line_text = DocView.draw_line_text
function DocView:draw_line_text(idx, x, y)
local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"])
local color = style.syntax.whitespace or style.syntax.comment
local ty = y + self:get_line_text_y_offset()
local tx
local text, offset, s, e = self.doc.lines[idx], 1
local x1, _, x2, _ = self:get_content_bounds()
local _offset = self:get_x_offset_col(idx, x1)
offset = _offset
while true do
s, e = text:find(" +", offset)
if not s then break end
tx = self:get_col_x_offset(idx, s) + x
renderer.draw_text(font, string.rep("·", e - s + 1), tx, ty, color)
if tx > x + x2 then break end
offset = e + 1
end
offset = _offset
while true do
s, e = text:find("\t", offset)
if not s then break end
tx = self:get_col_x_offset(idx, s) + x
renderer.draw_text(font, "»", tx, ty, color)
if tx > x + x2 then break end
offset = e + 1
end
draw_line_text(self, idx, x, y)
end

View File

@ -2,7 +2,6 @@
local syntax = require "core.syntax"
syntax.add {
name = "C",
files = { "%.c$", "%.h$", "%.inl$" },
comment = "//",
patterns = {
@ -44,7 +43,7 @@ syntax.add {
["case"] = "keyword",
["default"] = "keyword",
["auto"] = "keyword",
["void"] = "keyword2",
["void"] = "keyword",
["int"] = "keyword2",
["short"] = "keyword2",
["long"] = "keyword2",

View File

@ -4,7 +4,6 @@ pcall(require, "plugins.language_c")
local syntax = require "core.syntax"
syntax.add {
name = "C++",
files = {
"%.h$", "%.inl$", "%.cpp$", "%.cc$", "%.C$", "%.cxx$",
"%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$"
@ -96,7 +95,7 @@ syntax.add {
["default"] = "keyword",
["auto"] = "keyword",
["const"] = "keyword",
["void"] = "keyword2",
["void"] = "keyword",
["int"] = "keyword2",
["short"] = "keyword2",
["long"] = "keyword2",

View File

@ -2,7 +2,6 @@
local syntax = require "core.syntax"
syntax.add {
name = "CSS",
files = { "%.css$" },
patterns = {
{ pattern = "\\.", type = "normal" },

View File

@ -2,7 +2,6 @@
local syntax = require "core.syntax"
syntax.add {
name = "HTML",
files = { "%.html?$" },
patterns = {
{

View File

@ -2,18 +2,17 @@
local syntax = require "core.syntax"
syntax.add {
name = "JavaScript",
files = { "%.js$", "%.json$", "%.cson$" },
comment = "//",
patterns = {
{ pattern = "//.-\n", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" },
{ pattern = { '/[^= ]', '/', '\\' },type = "string" },
{ pattern = { '/%g', '/', '\\' }, type = "string" },
{ pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = { "`", "`", '\\' }, type = "string" },
{ pattern = "0x[%da-fA-F_]+n?", type = "number" },
{ pattern = "-?%d+[%d%.eE_n]*", type = "number" },
{ pattern = "0x[%da-fA-F]+", type = "number" },
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
{ pattern = "-?%.?%d+", type = "number" },
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },

View File

@ -2,7 +2,6 @@
local syntax = require "core.syntax"
syntax.add {
name = "Lua",
files = "%.lua$",
headers = "^#!.*[ /]lua",
comment = "--",

View File

@ -4,11 +4,11 @@ local syntax = require "core.syntax"
syntax.add {
name = "Markdown",
files = { "%.md$", "%.markdown$" },
patterns = {
{ pattern = "\\.", type = "normal" },
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
{ pattern = { "```c", "```" }, type = "string", syntax = ".c" },
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" },
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
@ -25,21 +25,6 @@ syntax.add {
{ pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" },
{ pattern = { "```d", "```" }, type = "string", syntax = ".d" },
{ pattern = { "```glsl", "```" }, type = "string", syntax = ".glsl" },
{ pattern = { "```c", "```" }, type = "string", syntax = ".c" },
{ pattern = { "```julia", "```" }, type = "string", syntax = ".jl" },
{ pattern = { "```rust", "```" }, type = "string", syntax = ".rs" },
{ pattern = { "```dart", "```" }, type = "string", syntax = ".dart" },
{ pattern = { "```v", "```" }, type = "string", syntax = ".v" },
{ pattern = { "```toml", "```" }, type = "string", syntax = ".toml" },
{ pattern = { "```yaml", "```" }, type = "string", syntax = ".yaml" },
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
{ pattern = { "```nim", "```" }, type = "string", syntax = ".nim" },
{ pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" },
{ pattern = { "```rescript", "```" }, type = "string", syntax = ".res" },
{ pattern = { "```moon", "```" }, type = "string", syntax = ".moon" },
{ pattern = { "```go", "```" }, type = "string", syntax = ".go" },
{ pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" },
{ pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" },
{ pattern = { "```", "```" }, type = "string" },
{ pattern = { "``", "``", "\\" }, type = "string" },
{ pattern = { "`", "`", "\\" }, type = "string" },

View File

@ -2,21 +2,20 @@
local syntax = require "core.syntax"
syntax.add {
name = "Python",
files = { "%.py$", "%.pyw$", "%.rpy$" },
files = { "%.py$", "%.pyw$" },
headers = "^#!.*[ /]python",
comment = "#",
patterns = {
{ pattern = { "#", "\n" }, type = "comment" },
{ pattern = { '[ruU]?"""', '"""'; '\\' }, type = "string" },
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
{ pattern = "0x[%da-fA-F]+", type = "number" },
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
{ pattern = "-?%.?%d+", type = "number" },
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
{ pattern = "[%a_][%w_]*", type = "symbol" },
{ pattern = { "#", "\n" }, type = "comment" },
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
{ pattern = { '"""', '"""' }, type = "string" },
{ pattern = "0x[%da-fA-F]+", type = "number" },
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
{ pattern = "-?%.?%d+", type = "number" },
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
{ pattern = "[%a_][%w_]*", type = "symbol" },
},
symbols = {
["class"] = "keyword",

View File

@ -2,7 +2,6 @@
local syntax = require "core.syntax"
syntax.add {
name = "XML",
files = { "%.xml$" },
headers = "<%?xml",
patterns = {

View File

@ -2,20 +2,20 @@
local config = require "core.config"
local style = require "core.style"
local DocView = require "core.docview"
local CommandView = require "core.commandview"
local draw_overlay = DocView.draw_overlay
function DocView:draw_overlay(...)
if not self:is(CommandView) then
local offset = self:get_font():get_width("n") * config.line_limit
local x = self:get_line_screen_position(1) + offset
local y = self.position.y
local w = math.ceil(SCALE * 1)
local h = self.size.y
local color = style.guide or style.selection
renderer.draw_rect(x, y, w, h, color)
end
local ns = ("n"):rep(config.line_limit)
local ss = self:get_font():subpixel_scale()
local offset = self:get_font():get_width_subpixel(ns) / ss
local x = self:get_line_screen_position(1) + offset
local y = self.position.y
local w = math.ceil(SCALE * 1)
local h = self.size.y
local color = style.guide or style.selection
renderer.draw_rect(x, y, w, h, color)
draw_overlay(self, ...)
end

View File

@ -92,7 +92,7 @@ end
function ResultsView:on_mouse_pressed(...)
local caught = ResultsView.super.on_mouse_pressed(self, ...)
if not caught then
return self:open_selected_result()
self:open_selected_result()
end
end
@ -108,7 +108,6 @@ function ResultsView:open_selected_result()
dv.doc:set_selection(res.line, res.col)
dv:scroll_to_line(res.line, false, true)
end)
return true
end

View File

@ -44,14 +44,10 @@ local function set_scale(scale)
style.tab_width = style.tab_width * s
for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do
style[name] = renderer.font.copy(style[name], s * style[name]:get_size())
renderer.font.set_size(style[name], s * style[name]:get_size())
end
else
style.code_font = renderer.font.copy(style.code_font, s * style.code_font:get_size())
end
for _, font in pairs(style.syntax_fonts) do
renderer.font.set_size(font, s * font:get_size())
renderer.font.set_size(style.code_font, s * style.code_font:get_size())
end
for _, font in pairs(style.syntax_fonts) do
@ -71,6 +67,17 @@ 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.plugins.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()
set_scale(default_scale)
end
@ -94,8 +101,6 @@ keymap.add {
["ctrl+0"] = "scale:reset",
["ctrl+-"] = "scale:decrease",
["ctrl+="] = "scale:increase",
["ctrl+wheelup"] = "scale:increase",
["ctrl+wheeldown"] = "scale:decrease"
}
return {

View File

@ -42,17 +42,6 @@ function TreeView:new()
self.target_size = default_treeview_size
self.cache = {}
self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 }
self.item_icon_width = 0
self.item_text_spacing = 0
local on_dirmonitor_modify = core.on_dirmonitor_modify
function core.on_dirmonitor_modify(dir, filepath)
if self.cache[dir.name] then
self.cache[dir.name][filepath] = nil
end
on_dirmonitor_modify(dir, filepath)
end
end
@ -219,11 +208,11 @@ end
function TreeView:on_mouse_pressed(button, x, y, clicks)
local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks)
if caught or button ~= "left" then
return true
return
end
local hovered_item = self.hovered_item
if not hovered_item then
return false
return
elseif hovered_item.type == "dir" then
if keymap.modkeys["ctrl"] and button == "left" then
create_directory_in(hovered_item)
@ -236,14 +225,10 @@ function TreeView:on_mouse_pressed(button, x, y, clicks)
end
else
core.try(function()
if core.last_active_view and core.active_view == self then
core.set_active_view(core.last_active_view)
end
local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename)
core.root_view:open_doc(core.open_doc(doc_filename))
end)
end
return true
end
@ -264,9 +249,6 @@ function TreeView:update()
self.tooltip.alpha = 0
end
self.item_icon_width = style.icon_font:get_width("D")
self.item_text_spacing = style.icon_font:get_width("f") / 2
TreeView.super.update(self)
end
@ -295,90 +277,47 @@ function TreeView:draw_tooltip()
end
function TreeView:get_item_icon(item, active, hovered)
local character = "f"
if item.type == "dir" then
character = item.expanded and "D" or "d"
end
local font = style.icon_font
local color = style.text
if active or hovered then
color = style.accent
end
return character, font, color
end
function TreeView:get_item_text(item, active, hovered)
local text = item.name
local font = style.font
local color = style.text
if active or hovered then
color = style.accent
end
return text, font, color
end
function TreeView:draw_item_text(item, active, hovered, x, y, w, h)
local item_text, item_font, item_color = self:get_item_text(item, active, hovered)
common.draw_text(item_font, item_color, item_text, nil, x, y, 0, h)
end
function TreeView:draw_item_icon(item, active, hovered, x, y, w, h)
local icon_char, icon_font, icon_color = self:get_item_icon(item, active, hovered)
common.draw_text(icon_font, icon_color, icon_char, nil, x, y, 0, h)
return self.item_icon_width + self.item_text_spacing
end
function TreeView:draw_item_body(item, active, hovered, x, y, w, h)
x = x + self:draw_item_icon(item, active, hovered, x, y, w, h)
self:draw_item_text(item, active, hovered, x, y, w, h)
end
function TreeView:draw_item_chevron(item, active, hovered, x, y, w, h)
if item.type == "dir" then
local chevron_icon = item.expanded and "-" or "+"
local chevron_color = hovered and style.accent or style.text
common.draw_text(style.icon_font, chevron_color, chevron_icon, nil, x, y, 0, h)
end
return style.padding.x
end
function TreeView:draw_item_background(item, active, hovered, x, y, w, h)
if hovered then
renderer.draw_rect(x, y, w, h, style.line_highlight)
end
end
function TreeView:draw_item(item, active, hovered, x, y, w, h)
self:draw_item_background(item, active, hovered, x, y, w, h)
x = x + item.depth * style.padding.x + style.padding.x
x = x + self:draw_item_chevron(item, active, hovered, x, y, w, h)
self:draw_item_body(item, active, hovered, x, y, w, h)
end
function TreeView:draw()
self:draw_background(style.background2)
local _y, _h = self.position.y, self.size.y
local icon_width = style.icon_font:get_width("D")
local spacing = style.icon_font:get_width("f") / 2
local doc = core.active_view.doc
local active_filename = doc and system.absolute_path(doc.filename or "")
for item, x,y,w,h in self:each_item() do
if y + h >= _y and y < _y + _h then
self:draw_item(item,
item.abs_filename == active_filename,
item == self.hovered_item,
x, y, w, h)
local color = style.text
-- highlight active_view doc
if item.abs_filename == active_filename then
color = style.accent
end
-- hovered item background
if item == self.hovered_item then
renderer.draw_rect(x, y, w, h, style.line_highlight)
color = style.accent
end
-- icons
x = x + item.depth * style.padding.x + style.padding.x
if item.type == "dir" then
local icon1 = item.expanded and "-" or "+"
local icon2 = item.expanded and "D" or "d"
common.draw_text(style.icon_font, color, icon1, nil, x, y, 0, h)
x = x + style.padding.x
common.draw_text(style.icon_font, color, icon2, nil, x, y, 0, h)
x = x + icon_width
else
x = x + style.padding.x
common.draw_text(style.icon_font, color, "f", nil, x, y, 0, h)
x = x + icon_width
end
-- text
x = x + spacing
x = common.draw_text(style.font, color, item.name, nil, x, y, 0, h)
end
self:draw_scrollbar()
@ -447,7 +386,7 @@ function RootView:draw(...)
end
local function is_project_folder(path)
return core.project_dir == path
return common.basename(core.project_dir) == path
end
menu:register(function() return view.hovered_item end, {
@ -458,7 +397,7 @@ menu:register(function() return view.hovered_item end, {
menu:register(
function()
return view.hovered_item
and not is_project_folder(view.hovered_item.abs_filename)
and not is_project_folder(view.hovered_item.filename)
end,
{
{ text = "Rename", command = "treeview:rename" },
@ -508,8 +447,9 @@ command.add(function() return view.hovered_item ~= nil end, {
end,
["treeview:new-file"] = function()
if not is_project_folder(view.hovered_item.abs_filename) then
core.command_view:set_text(view.hovered_item.filename .. "/")
local dir_name = view.hovered_item.filename
if not is_project_folder(dir_name) then
core.command_view:set_text(dir_name .. "/")
end
core.command_view:enter("Filename", function(filename)
local doc_filename = core.project_dir .. PATHSEP .. filename
@ -522,8 +462,9 @@ command.add(function() return view.hovered_item ~= nil end, {
end,
["treeview:new-folder"] = function()
if not is_project_folder(view.hovered_item.abs_filename) then
core.command_view:set_text(view.hovered_item.filename .. "/")
local dir_name = view.hovered_item.filename
if not is_project_folder(dir_name) then
core.command_view:set_text(dir_name .. "/")
end
core.command_view:enter("Folder Name", function(filename)
local dir_path = core.project_dir .. PATHSEP .. filename

View File

@ -57,26 +57,23 @@ process.WAIT_INFINITE = -1
---@type integer
process.WAIT_DEADLINE = -2
---Default behavior for redirecting streams.
---This flag is deprecated and for backwards compatibility with reproc only.
---The behavior of this flag may change in future versions of Lite XL.
---Used for the process.options stdin, stdout and stderr fields.
---@type integer
process.REDIRECT_DEFAULT = 0
---Allow Process API to read this stream via process:read functions.
---Used for the process.options stdin, stdout and stderr fields.
---@type integer
process.REDIRECT_PIPE = 1
---Redirect this stream to the parent.
---Used for the process.options stdin, stdout and stderr fields.
---@type integer
process.REDIRECT_PARENT = 2
---Discard this stream (piping it to /dev/null)
---Used for the process.options stdin, stdout and stderr fields.
---@type integer
process.REDIRECT_DISCARD = 3
---Redirect this stream to stdout.
---This flag can only be used on process.options.stderr.
---Used for the process.options stdin, stdout and stderr fields.
---@type integer
process.REDIRECT_STDOUT = 4

View File

@ -19,9 +19,6 @@ renderer.color = {}
---@class renderer.fontoptions
---@field public antialiasing "'grayscale'" | "'subpixel'"
---@field public hinting "'slight'" | "'none'" | '"full"'
-- @field public bold boolean
-- @field public italic boolean
-- @field public underline boolean
renderer.fontoptions = {}
---
@ -61,6 +58,15 @@ function renderer.font:set_tab_size(chars) end
---@return number
function renderer.font:get_width(text) end
---
---Get the width in subpixels of the given text when
---rendered with this font.
---
---@param text string
---
---@return number
function renderer.font:get_width_subpixel(text) end
---
---Get the height in pixels that occupies a single character
---when rendered with this font.
@ -68,6 +74,12 @@ function renderer.font:get_width(text) end
---@return number
function renderer.font:get_height() end
---
---Gets the font subpixel scale.
---
---@return number
function renderer.font:subpixel_scale() end
---
---Get the current size of the font.
---

View File

@ -1,162 +0,0 @@
#ifndef __DMON_EXTRA_H__
#define __DMON_EXTRA_H__
//
// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved.
// License: https://github.com/septag/dmon#license-bsd-2-clause
//
// Extra header functionality for dmon.h for the backend based on inotify
//
// Add/Remove directory functions:
// dmon_watch_add: Adds a sub-directory to already valid watch_id. sub-directories are assumed to be relative to watch root_dir
// dmon_watch_add: Removes a sub-directory from already valid watch_id. sub-directories are assumed to be relative to watch root_dir
// Reason: The inotify backend does not work well when watching in recursive mode a root directory of a large tree, it may take
// quite a while until all inotify watches are established, and events will not be received in this time. Also, since one
// inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user
// will be reached. The default maximum is 8192.
// When using inotify backend users may turn off the DMON_WATCHFLAGS_RECURSIVE flag and add/remove selectively the
// sub-directories to be watched based on application-specific logic about which sub-directory actually needs to be watched.
// The function dmon_watch_add and dmon_watch_rm are used to this purpose.
//
#ifndef __DMON_H__
#error "Include 'dmon.h' before including this file"
#endif
#ifdef __cplusplus
extern "C" {
#endif
DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir);
DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir);
#ifdef __cplusplus
}
#endif
#ifdef DMON_IMPL
#if DMON_OS_LINUX
DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
{
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
bool skip_lock = pthread_self() == _dmon.thread_handle;
if (!skip_lock)
pthread_mutex_lock(&_dmon.mutex);
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
// check if the directory exists
// if watchdir contains absolute/root-included path, try to strip the rootdir from it
// else, we assume that watchdir is correct, so save it as it is
struct stat st;
dmon__watch_subdir subdir;
if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) {
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) {
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir));
}
} else {
char fullpath[DMON_MAX_PATH];
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
dmon__strcat(fullpath, sizeof(fullpath), watchdir);
if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) {
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
}
int dirlen = (int)strlen(subdir.rootdir);
if (subdir.rootdir[dirlen - 1] != '/') {
subdir.rootdir[dirlen] = '/';
subdir.rootdir[dirlen + 1] = '\0';
}
// check that the directory is not already added
for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) {
if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) {
_DMON_LOG_ERRORF("Error watching directory '%s', because it is already added.", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
}
const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY;
char fullpath[DMON_MAX_PATH];
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir);
int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask);
if (wd == -1) {
_DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watchdir, errno);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
stb_sb_push(watch->subdirs, subdir);
stb_sb_push(watch->wds, wd);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return true;
}
DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir)
{
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
bool skip_lock = pthread_self() == _dmon.thread_handle;
if (!skip_lock)
pthread_mutex_lock(&_dmon.mutex);
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
char subdir[DMON_MAX_PATH];
dmon__strcpy(subdir, sizeof(subdir), watchdir);
if (strstr(subdir, watch->rootdir) == subdir) {
dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir));
}
int dirlen = (int)strlen(subdir);
if (subdir[dirlen - 1] != '/') {
subdir[dirlen] = '/';
subdir[dirlen + 1] = '\0';
}
int i, c = stb_sb_count(watch->subdirs);
for (i = 0; i < c; i++) {
if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) {
break;
}
}
if (i >= c) {
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
inotify_rm_watch(watch->fd, watch->wds[i]);
/* Remove entry from subdirs and wds by swapping position with the last entry */
watch->subdirs[i] = stb_sb_last(watch->subdirs);
stb_sb_pop(watch->subdirs);
watch->wds[i] = stb_sb_last(watch->wds);
stb_sb_pop(watch->wds);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return true;
}
#endif // DMON_OS_LINUX
#endif // DMON_IMPL
#endif // __DMON_EXTRA_H__

View File

@ -1 +0,0 @@
lite_includes += include_directories('.')

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,198 @@
//----------------------------------------------------------------------------
// Anti-Grain Geometry - Version 2.4
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
//
// Permission to copy, use, modify, sell and distribute this software
// is granted provided this copyright notice appears in all copies.
// This software is provided "as is" without express or implied
// warranty, and with no claim as to its suitability for any purpose.
//
//----------------------------------------------------------------------------
// Contact: mcseem@antigrain.com
// mcseemagg@yahoo.com
// http://www.antigrain.com
//----------------------------------------------------------------------------
//
// See implementation agg_font_freetype.cpp
//
//----------------------------------------------------------------------------
#ifndef AGG_FONT_FREETYPE_INCLUDED
#define AGG_FONT_FREETYPE_INCLUDED
#include <ft2build.h>
#include FT_FREETYPE_H
#include "agg_scanline_storage_aa.h"
#include "agg_scanline_storage_bin.h"
#include "agg_scanline_u.h"
#include "agg_scanline_bin.h"
#include "agg_path_storage_integer.h"
#include "agg_rasterizer_scanline_aa.h"
#include "agg_conv_curve.h"
#include "agg_font_cache_manager.h"
#include "agg_trans_affine.h"
namespace agg
{
//-----------------------------------------------font_engine_freetype_base
class font_engine_freetype_base
{
public:
//--------------------------------------------------------------------
typedef serialized_scanlines_adaptor_aa<int8u> gray8_adaptor_type;
typedef serialized_scanlines_adaptor_bin mono_adaptor_type;
typedef scanline_storage_aa8 scanlines_aa_type;
typedef scanline_storage_bin scanlines_bin_type;
//--------------------------------------------------------------------
~font_engine_freetype_base();
font_engine_freetype_base(bool flag32, unsigned max_faces = 32);
// Set font parameters
//--------------------------------------------------------------------
void resolution(unsigned dpi);
bool load_font(const char* font_name, unsigned face_index, glyph_rendering ren_type,
const char* font_mem = 0, const long font_mem_size = 0);
bool attach(const char* file_name);
bool char_map(FT_Encoding map);
bool height(double h);
bool width(double w);
void hinting(bool h);
void flip_y(bool f);
void transform(const trans_affine& affine);
// Set Gamma
//--------------------------------------------------------------------
template<class GammaF> void gamma(const GammaF& f)
{
m_rasterizer.gamma(f);
}
// Accessors
//--------------------------------------------------------------------
int last_error() const { return m_last_error; }
unsigned resolution() const { return m_resolution; }
const char* name() const { return m_name; }
unsigned num_faces() const;
FT_Encoding char_map() const { return m_char_map; }
double height() const { return double(m_height) / 64.0; }
double width() const { return double(m_width) / 64.0; }
double ascender() const;
double descender() const;
int face_height() const;
int face_units_em()const;
bool hinting() const { return m_hinting; }
bool flip_y() const { return m_flip_y; }
// Interface mandatory to implement for font_cache_manager
//--------------------------------------------------------------------
const char* font_signature() const { return m_signature; }
int change_stamp() const { return m_change_stamp; }
bool prepare_glyph(unsigned glyph_code);
unsigned glyph_index() const { return m_glyph_index; }
unsigned data_size() const { return m_data_size; }
glyph_data_type data_type() const { return m_data_type; }
const rect_i& bounds() const { return m_bounds; }
double advance_x() const { return m_advance_x; }
double advance_y() const { return m_advance_y; }
void write_glyph_to(int8u* data) const;
bool add_kerning(unsigned first, unsigned second,
double* x, double* y);
private:
font_engine_freetype_base(const font_engine_freetype_base&);
const font_engine_freetype_base& operator = (const font_engine_freetype_base&);
void update_char_size();
void update_signature();
int find_face(const char* face_name) const;
bool m_flag32;
int m_change_stamp;
int m_last_error;
char* m_name;
unsigned m_name_len;
unsigned m_face_index;
FT_Encoding m_char_map;
char* m_signature;
unsigned m_height;
unsigned m_width;
bool m_hinting;
bool m_flip_y;
bool m_library_initialized;
FT_Library m_library; // handle to library
FT_Face* m_faces; // A pool of font faces
char** m_face_names;
unsigned m_num_faces;
unsigned m_max_faces;
FT_Face m_cur_face; // handle to the current face object
int m_resolution;
glyph_rendering m_glyph_rendering;
unsigned m_glyph_index;
unsigned m_data_size;
glyph_data_type m_data_type;
rect_i m_bounds;
double m_advance_x;
double m_advance_y;
trans_affine m_affine;
path_storage_integer<int16, 6> m_path16;
path_storage_integer<int32, 6> m_path32;
conv_curve<path_storage_integer<int16, 6> > m_curves16;
conv_curve<path_storage_integer<int32, 6> > m_curves32;
scanline_u8 m_scanline_aa;
scanline_bin m_scanline_bin;
scanlines_aa_type m_scanlines_aa;
scanlines_bin_type m_scanlines_bin;
rasterizer_scanline_aa<> m_rasterizer;
};
//------------------------------------------------font_engine_freetype_int16
// This class uses values of type int16 (10.6 format) for the vector cache.
// The vector cache is compact, but when rendering glyphs of height
// more that 200 there integer overflow can occur.
//
class font_engine_freetype_int16 : public font_engine_freetype_base
{
public:
typedef serialized_integer_path_adaptor<int16, 6> path_adaptor_type;
typedef font_engine_freetype_base::gray8_adaptor_type gray8_adaptor_type;
typedef font_engine_freetype_base::mono_adaptor_type mono_adaptor_type;
typedef font_engine_freetype_base::scanlines_aa_type scanlines_aa_type;
typedef font_engine_freetype_base::scanlines_bin_type scanlines_bin_type;
font_engine_freetype_int16(unsigned max_faces = 32) :
font_engine_freetype_base(false, max_faces) {}
};
//------------------------------------------------font_engine_freetype_int32
// This class uses values of type int32 (26.6 format) for the vector cache.
// The vector cache is twice larger than in font_engine_freetype_int16,
// but it allows you to render glyphs of very large sizes.
//
class font_engine_freetype_int32 : public font_engine_freetype_base
{
public:
typedef serialized_integer_path_adaptor<int32, 6> path_adaptor_type;
typedef font_engine_freetype_base::gray8_adaptor_type gray8_adaptor_type;
typedef font_engine_freetype_base::mono_adaptor_type mono_adaptor_type;
typedef font_engine_freetype_base::scanlines_aa_type scanlines_aa_type;
typedef font_engine_freetype_base::scanlines_bin_type scanlines_bin_type;
font_engine_freetype_int32(unsigned max_faces = 32) :
font_engine_freetype_base(true, max_faces) {}
};
}
#endif

View File

@ -0,0 +1,73 @@
// Adapted by Francesco Abbate for GSL Shell
// Original code's copyright below.
//----------------------------------------------------------------------------
// Anti-Grain Geometry - Version 2.4
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
//
// Permission to copy, use, modify, sell and distribute this software
// is granted provided this copyright notice appears in all copies.
// This software is provided "as is" without express or implied
// warranty, and with no claim as to its suitability for any purpose.
//
//----------------------------------------------------------------------------
// Contact: mcseem@antigrain.com
// mcseemagg@yahoo.com
// http://www.antigrain.com
//----------------------------------------------------------------------------
#ifndef AGG_LCD_DISTRIBUTION_LUT_INCLUDED
#define AGG_LCD_DISTRIBUTION_LUT_INCLUDED
#include <cstdlib>
#include "agg_basics.h"
namespace agg
{
//=====================================================lcd_distribution_lut
class lcd_distribution_lut
{
public:
lcd_distribution_lut(double prim, double second, double tert)
{
double norm = 1.0 / (prim + second*2 + tert*2);
prim *= norm;
second *= norm;
tert *= norm;
for(unsigned i = 0; i < 256; i++)
{
unsigned b = (i << 8);
unsigned s = round(second * b);
unsigned t = round(tert * b);
unsigned p = b - (2*s + 2*t);
m_data[3*i + 1] = s; /* secondary */
m_data[3*i + 2] = t; /* tertiary */
m_data[3*i ] = p; /* primary */
}
}
unsigned convolution(const int8u* covers, int i0, int i_min, int i_max) const
{
unsigned sum = 0;
int k_min = (i0 >= i_min + 2 ? -2 : i_min - i0);
int k_max = (i0 <= i_max - 2 ? 2 : i_max - i0);
for (int k = k_min; k <= k_max; k++)
{
/* select the primary, secondary or tertiary channel */
int channel = std::abs(k) % 3;
int8u c = covers[i0 + k];
sum += m_data[3*c + channel];
}
return (sum + 128) >> 8;
}
private:
unsigned short m_data[256*3];
};
}
#endif

View File

@ -0,0 +1,93 @@
#pragma once
#include <string.h>
#include "agg_basics.h"
#include "agg_rendering_buffer.h"
namespace agg
{
// This is a special purpose color type that only has the alpha channel.
// It can be thought as a gray color but with the intensity always on.
// It is actually used to store coverage information.
struct alpha8
{
typedef int8u value_type;
typedef int32u calc_type;
typedef int32 long_type;
enum base_scale_e
{
base_shift = 8,
base_scale = 1 << base_shift,
base_mask = base_scale - 1
};
value_type a;
//--------------------------------------------------------------------
alpha8(unsigned a_=base_mask) :
a(int8u(a_)) {}
};
// Pixer format to store coverage information.
class pixfmt_alpha8
{
public:
typedef alpha8 color_type;
typedef int8u value_type;
typedef int32u calc_type;
typedef agg::rendering_buffer::row_data row_data;
//--------------------------------------------------------------------
pixfmt_alpha8(rendering_buffer& rb): m_rbuf(&rb)
{
}
//--------------------------------------------------------------------
unsigned width() const {
return m_rbuf->width();
}
unsigned height() const {
return m_rbuf->height();
}
// This method should never be called when using the scanline_u8.
// The use of scanline_p8 should be avoided because if does not works
// properly for rendering fonts because single hspan are split in many
// hline/hspan elements and pixel whitening happens.
void blend_hline(int x, int y, unsigned len,
const color_type& c, int8u cover)
{ }
void copy_hline(int x, int y, unsigned len, const color_type& c)
{
value_type* p = (value_type*) m_rbuf->row_ptr(y) + x;
do
{
*p = c.a;
p++;
}
while(--len);
}
//--------------------------------------------------------------------
void blend_solid_hspan(int x, int y,
unsigned len,
const color_type& c,
const int8u* covers)
{
value_type* p = (value_type*) m_rbuf->row_ptr(y) + x;
do
{
calc_type alpha = (calc_type(c.a) * (calc_type(*covers) + 1)) >> 8;
*p = alpha;
p++;
++covers;
}
while(--len);
}
private:
rendering_buffer* m_rbuf;
};
}

View File

@ -0,0 +1,445 @@
#include "font_renderer.h"
#include "agg_lcd_distribution_lut.h"
#include "agg_pixfmt_rgb.h"
#include "agg_pixfmt_rgba.h"
#include "font_renderer_alpha.h"
// Important: when a subpixel scale is used the width below will be the width in logical pixel.
// As each logical pixel contains 3 subpixels it means that the 'pixels' pointer
// will hold enough space for '3 * width' uint8_t values.
struct FR_Bitmap {
agg::int8u *pixels;
int width, height;
};
class FR_Renderer {
public:
// Conventional LUT values: (1./3., 2./9., 1./9.)
// The values below are fine tuned as in the Elementary Plot library.
FR_Renderer(bool hinting, bool kerning, bool subpixel, bool prescale_x) :
m_renderer(hinting, kerning, subpixel, prescale_x),
m_lcd_lut(0.448, 0.184, 0.092),
m_subpixel(subpixel)
{ }
font_renderer_alpha& renderer_alpha() { return m_renderer; }
agg::lcd_distribution_lut& lcd_distribution_lut() { return m_lcd_lut; }
int subpixel_scale() const { return (m_subpixel ? 3 : 1); }
private:
font_renderer_alpha m_renderer;
agg::lcd_distribution_lut m_lcd_lut;
int m_subpixel;
};
FR_Renderer *FR_Renderer_New(unsigned int flags) {
bool hinting = ((flags & FR_HINTING) != 0);
bool kerning = ((flags & FR_KERNING) != 0);
bool subpixel = ((flags & FR_SUBPIXEL) != 0);
bool prescale_x = ((flags & FR_PRESCALE_X) != 0);
return new FR_Renderer(hinting, kerning, subpixel, prescale_x);
}
FR_Bitmap* FR_Bitmap_New(FR_Renderer *font_renderer, int width, int height) {
const int subpixel_scale = font_renderer->subpixel_scale();
FR_Bitmap *image = (FR_Bitmap *) malloc(sizeof(FR_Bitmap) + width * height * subpixel_scale);
if (!image) { return NULL; }
image->pixels = (agg::int8u *) (image + 1);
image->width = width;
image->height = height;
return image;
}
void FR_Bitmap_Free(FR_Bitmap *image) {
free(image);
}
void FR_Renderer_Free(FR_Renderer *font_renderer) {
delete font_renderer;
}
int FR_Subpixel_Scale(FR_Renderer *font_renderer) {
return font_renderer->subpixel_scale();
}
int FR_Load_Font(FR_Renderer *font_renderer, const char *filename) {
bool success = font_renderer->renderer_alpha().load_font(filename);
return (success ? 0 : 1);
}
int FR_Get_Font_Height(FR_Renderer *font_renderer, float size) {
font_renderer_alpha& renderer_alpha = font_renderer->renderer_alpha();
double ascender, descender;
renderer_alpha.get_font_vmetrics(ascender, descender);
int face_height = renderer_alpha.get_face_height();
float scale = renderer_alpha.scale_for_em_to_pixels(size);
return int((ascender - descender) * face_height * scale + 0.5);
}
static void glyph_trim_rect(agg::rendering_buffer& ren_buf, FR_Bitmap_Glyph_Metrics& gli, int subpixel_scale) {
const int height = ren_buf.height();
int x0 = gli.x0 * subpixel_scale, x1 = gli.x1 * subpixel_scale;
int y0 = gli.y0, y1 = gli.y1;
for (int y = gli.y0; y < gli.y1; y++) {
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
unsigned int row_bitsum = 0;
for (int x = x0; x < x1; x++) {
row_bitsum |= row[x];
}
if (row_bitsum == 0) {
y0++;
} else {
break;
}
}
for (int y = gli.y1 - 1; y >= y0; y--) {
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
unsigned int row_bitsum = 0;
for (int x = x0; x < x1; x++) {
row_bitsum |= row[x];
}
if (row_bitsum == 0) {
y1--;
} else {
break;
}
}
for (int x = gli.x0 * subpixel_scale; x < gli.x1 * subpixel_scale; x += subpixel_scale) {
unsigned int xaccu = 0;
for (int y = y0; y < y1; y++) {
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
for (int i = 0; i < subpixel_scale; i++) {
xaccu |= row[x + i];
}
}
if (xaccu == 0) {
x0 += subpixel_scale;
} else {
break;
}
}
for (int x = (gli.x1 - 1) * subpixel_scale; x >= x0; x -= subpixel_scale) {
unsigned int xaccu = 0;
for (int y = y0; y < y1; y++) {
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
for (int i = 0; i < subpixel_scale; i++) {
xaccu |= row[x + i];
}
}
if (xaccu == 0) {
x1 -= subpixel_scale;
} else {
break;
}
}
gli.xoff += (x0 / subpixel_scale) - gli.x0;
gli.yoff += (y0 - gli.y0);
gli.x0 = x0 / subpixel_scale;
gli.y0 = y0;
gli.x1 = x1 / subpixel_scale;
gli.y1 = y1;
}
static void glyph_lut_convolution(agg::rendering_buffer ren_buf, agg::lcd_distribution_lut& lcd_lut, agg::int8u *covers_buf, FR_Bitmap_Glyph_Metrics& gli) {
const int subpixel = 3;
const int x0 = gli.x0, y0 = gli.y0, x1 = gli.x1, y1 = gli.y1;
const int len = (x1 - x0) * subpixel;
const int height = ren_buf.height();
for (int y = y0; y < y1; y++) {
agg::int8u *covers = ren_buf.row_ptr(height - 1 - y) + x0 * subpixel;
memcpy(covers_buf, covers, len);
for (int x = x0 - 1; x < x1 + 1; x++) {
for (int i = 0; i < subpixel; i++) {
const int cx = (x - x0) * subpixel + i;
covers[cx] = lcd_lut.convolution(covers_buf, cx, 0, len - 1);
}
}
}
gli.x0 -= 1;
gli.x1 += 1;
gli.xoff -= 1;
}
// The two functions below are needed because in C and C++ integer division
// is rounded toward zero.
// euclidean division rounded toward positive infinite
static int div_pos(int n, int p) {
return n >= 0 ? (n + p - 1) / p : (n / p);
}
// euclidean division rounded toward negative infinite
static int div_neg(int n, int p) {
return n >= 0 ? (n / p) : ((n - p + 1) / p);
}
FR_Bitmap *FR_Bake_Font_Bitmap(FR_Renderer *font_renderer, int font_height,
int first_char, int num_chars, FR_Bitmap_Glyph_Metrics *glyphs)
{
font_renderer_alpha& renderer_alpha = font_renderer->renderer_alpha();
agg::lcd_distribution_lut& lcd_lut = font_renderer->lcd_distribution_lut();
const int subpixel_scale = font_renderer->subpixel_scale();
double ascender, descender;
renderer_alpha.get_font_vmetrics(ascender, descender);
const int ascender_px = int(ascender * font_height);
const int pad_y = 1;
// When using subpixel font rendering it is needed to leave a padding pixel on the left and on the right.
// Since each pixel is composed by n subpixel we set below x_start to subpixel_scale instead than zero.
// In addition we need one more pixel on the left because of subpixel positioning so
// it adds up to 2 * subpixel_scale.
// Note about the coordinates: they are AGG-like so x is positive toward the right and
// y is positive in the upper direction.
const int x_start = 2 * subpixel_scale;
const agg::alpha8 text_color(0xff);
#ifdef FONT_RENDERER_HEIGHT_HACK
const int font_height_reduced = (font_height * 86) / 100;
#else
const int font_height_reduced = font_height;
#endif
renderer_alpha.set_font_height(font_height_reduced);
int *index = (int *) malloc(num_chars * sizeof(int));
agg::rect_i *bounds = (agg::rect_i *) malloc(num_chars * sizeof(agg::rect_i));
if (!index || !bounds) {
free(index);
free(bounds);
return NULL;
}
int x_size_sum = 0, glyph_count = 0;
for (int i = 0; i < num_chars; i++) {
int codepoint = first_char + i;
index[i] = i;
if (renderer_alpha.codepoint_bounds(codepoint, subpixel_scale, bounds[i])) {
// Invalid glyph
bounds[i].x1 = 0;
bounds[i].y1 = 0;
bounds[i].x2 = -1;
bounds[i].y2 = -1;
} else {
if (bounds[i].x2 > bounds[i].x1) {
x_size_sum += bounds[i].x2 - bounds[i].x1;
glyph_count++;
}
bounds[i].x1 = subpixel_scale * div_neg(bounds[i].x1, subpixel_scale);
bounds[i].x2 = subpixel_scale * div_pos(bounds[i].x2, subpixel_scale);
}
}
// Simple insertion sort algorithm: https://en.wikipedia.org/wiki/Insertion_sort
int i = 1;
while (i < num_chars) {
int j = i;
while (j > 0 && bounds[index[j-1]].y2 - bounds[index[j-1]].y1 > bounds[index[j]].y2 - bounds[index[j]].y1) {
int tmp = index[j];
index[j] = index[j-1];
index[j-1] = tmp;
j = j - 1;
}
i = i + 1;
}
const int glyph_avg_width = glyph_count > 0 ? x_size_sum / (glyph_count * subpixel_scale) : font_height;
const int pixels_width = glyph_avg_width > 0 ? glyph_avg_width * 28 : 28;
// dry run simulating pixel position to estimate required image's height
int x = x_start, y = 0, y_bottom = y;
for (int i = 0; i < num_chars; i++) {
const agg::rect_i& gbounds = bounds[index[i]];
if (gbounds.x2 < gbounds.x1) continue;
// 1. It is very important to ensure that the x's increment below (1) and in
// (2), (3) and (4) are perfectly the same.
// Note that x_step below is always an integer multiple of subpixel_scale.
const int x_step = gbounds.x2 + 3 * subpixel_scale;
if (x + x_step >= pixels_width * subpixel_scale) {
x = x_start;
y = y_bottom;
}
// 5. Ensure that y's increment below is exactly the same to the one used in (6)
const int glyph_y_bottom = y - 2 * pad_y - (gbounds.y2 - gbounds.y1);
y_bottom = (y_bottom > glyph_y_bottom ? glyph_y_bottom : y_bottom);
// 2. Ensure x's increment is aligned with (1)
x = x + x_step;
}
agg::int8u *cover_swap_buffer = (agg::int8u *) malloc(sizeof(agg::int8u) * (pixels_width * subpixel_scale));
if (!cover_swap_buffer) {
free(index);
free(bounds);
return NULL;
}
const int pixels_height = -y_bottom + 1;
const int pixel_size = 1;
FR_Bitmap *image = FR_Bitmap_New(font_renderer, pixels_width, pixels_height);
if (!image) {
free(index);
free(bounds);
free(cover_swap_buffer);
return NULL;
}
agg::int8u *pixels = image->pixels;
memset(pixels, 0x00, pixels_width * pixels_height * subpixel_scale * pixel_size);
agg::rendering_buffer ren_buf(pixels, pixels_width * subpixel_scale, pixels_height, -pixels_width * subpixel_scale * pixel_size);
// The variable y_bottom will be used to go down to the next row by taking into
// account the space occupied by each glyph of the current row along the y direction.
x = x_start;
// Set y to the image's height minus one to begin writing glyphs in the upper part of the image.
y = pixels_height - 1;
y_bottom = y;
for (int i = 0; i < num_chars; i++) {
// Important: the variable x in this loop should always be an integer multiple
// of subpixel_scale.
int codepoint = first_char + index[i];
const agg::rect_i& gbounds = bounds[index[i]];
if (gbounds.x2 < gbounds.x1) continue;
// 3. Ensure x's increment is aligned with (1)
// Note that x_step below is always an integer multiple of subpixel_scale.
// We need 3 * subpixel_scale because:
// . +1 pixel on the left, because of RGB color filter
// . +1 pixel on the right, because of RGB color filter
// . +1 pixel on the right, because of subpixel positioning
// and each pixel requires "subpixel_scale" sub-pixels.
const int x_step = gbounds.x2 + 3 * subpixel_scale;
if (x + x_step >= pixels_width * subpixel_scale) {
// No more space along x, begin writing the row below.
x = x_start;
y = y_bottom;
}
const int y_baseline = y - pad_y - gbounds.y2;
// 6. Ensure the y's increment below is aligned with the increment used in (5)
const int glyph_y_bottom = y - 2 * pad_y - (gbounds.y2 - gbounds.y1);
y_bottom = (y_bottom > glyph_y_bottom ? glyph_y_bottom : y_bottom);
double x_next = x, y_next = y_baseline;
renderer_alpha.render_codepoint(ren_buf, text_color, x_next, y_next, codepoint, subpixel_scale);
// The y coordinate for the glyph below is positive in the bottom direction,
// like is used by Lite's drawing system.
FR_Bitmap_Glyph_Metrics& glyph_info = glyphs[index[i]];
glyph_info.x0 = x / subpixel_scale;
glyph_info.y0 = pixels_height - 1 - (y_baseline + gbounds.y2 + pad_y);
glyph_info.x1 = div_pos(x_next + 0.5, subpixel_scale);
glyph_info.y1 = pixels_height - 1 - (y_baseline + gbounds.y1 - pad_y);
glyph_info.xoff = 0;
glyph_info.yoff = -pad_y - gbounds.y2 + ascender_px;
// Note that below the xadvance is in pixels times the subpixel_scale.
// This is meant for subpixel positioning.
glyph_info.xadvance = roundf(x_next - x);
if (subpixel_scale != 1 && glyph_info.x1 > glyph_info.x0) {
glyph_lut_convolution(ren_buf, lcd_lut, cover_swap_buffer, glyph_info);
}
glyph_trim_rect(ren_buf, glyph_info, subpixel_scale);
// When subpixel is activated we need one padding pixel on the left and on the right
// and one more because of subpixel positioning.
// 4. Ensure x's increment is aligned with (1)
x = x + x_step;
}
free(index);
free(bounds);
free(cover_swap_buffer);
return image;
}
template <typename Order>
void blend_solid_hspan(agg::rendering_buffer& rbuf, int x, int y, unsigned len,
const agg::rgba8& c, const agg::int8u* covers)
{
const int pixel_size = 4;
agg::int8u* p = rbuf.row_ptr(y) + x * pixel_size;
do
{
const unsigned alpha = *covers;
const unsigned r = p[Order::R], g = p[Order::G], b = p[Order::B];
p[Order::R] = (((unsigned(c.r) - r) * alpha) >> 8) + r;
p[Order::G] = (((unsigned(c.g) - g) * alpha) >> 8) + g;
p[Order::B] = (((unsigned(c.b) - b) * alpha) >> 8) + b;
// Leave p[3], the alpha channel value unmodified.
p += 4;
++covers;
}
while(--len);
}
template <typename Order>
void blend_solid_hspan_subpixel(agg::rendering_buffer& rbuf, agg::lcd_distribution_lut& lcd_lut,
const int x, const int y, unsigned len,
const agg::rgba8& c,
const agg::int8u* covers)
{
const int pixel_size = 4;
const unsigned rgb[3] = { c.r, c.g, c.b };
agg::int8u* p = rbuf.row_ptr(y) + x * pixel_size;
// Indexes to adress RGB colors in a BGRA32 format.
const int pixel_index[3] = {Order::R, Order::G, Order::B};
for (unsigned cx = 0; cx < len; cx += 3)
{
for (int i = 0; i < 3; i++) {
const unsigned cover_value = covers[cx + i];
const unsigned alpha = (cover_value + 1) * (c.a + 1);
const unsigned src_col = *(p + pixel_index[i]);
*(p + pixel_index[i]) = (((rgb[i] - src_col) * alpha) + (src_col << 16)) >> 16;
}
// Leave p[3], the alpha channel value unmodified.
p += 4;
}
}
// destination implicitly BGRA32. Source implictly single-byte renderer_alpha coverage with subpixel scale = 3.
// FIXME: consider using something like RenColor* instead of uint8_t * for dst.
void FR_Blend_Glyph(FR_Renderer *font_renderer, FR_Clip_Area *clip, int x_mult, int y, uint8_t *dst, int dst_width, const FR_Bitmap *glyphs_bitmap, const FR_Bitmap_Glyph_Metrics *glyph, FR_Color color) {
agg::lcd_distribution_lut& lcd_lut = font_renderer->lcd_distribution_lut();
const int subpixel_scale = font_renderer->subpixel_scale();
const int pixel_size = 4; // Pixel size for BGRA32 format.
int x = x_mult / subpixel_scale;
x += glyph->xoff;
y += glyph->yoff;
int glyph_x = glyph->x0, glyph_y = glyph->y0;
int glyph_x_subpixel = -(x_mult % subpixel_scale);
int glyph_width = glyph->x1 - glyph->x0;
int glyph_height = glyph->y1 - glyph->y0;
int n;
if ((n = clip->left - x) > 0) { glyph_width -= n; glyph_x += n; x += n; }
if ((n = clip->top - y) > 0) { glyph_height -= n; glyph_y += n; y += n; }
if ((n = x + glyph_width - clip->right ) > 0) { glyph_width -= n; }
if ((n = y + glyph_height - clip->bottom) > 0) { glyph_height -= n; }
if (glyph_width <= 0 || glyph_height <= 0) {
return;
}
dst += (x + y * dst_width) * pixel_size;
agg::rendering_buffer dst_ren_buf(dst, glyph_width, glyph_height, dst_width * pixel_size);
uint8_t *src = glyphs_bitmap->pixels + (glyph_x + glyph_y * glyphs_bitmap->width) * subpixel_scale + glyph_x_subpixel;
int src_stride = glyphs_bitmap->width * subpixel_scale;
const agg::rgba8 color_a(color.r, color.g, color.b);
for (int x = 0, y = 0; y < glyph_height; y++) {
agg::int8u *covers = src + y * src_stride;
if (subpixel_scale == 1) {
blend_solid_hspan<agg::order_bgra>(dst_ren_buf, x, y, glyph_width, color_a, covers);
} else {
blend_solid_hspan_subpixel<agg::order_bgra>(dst_ren_buf, lcd_lut, x, y, glyph_width * subpixel_scale, color_a, covers);
}
}
}

View File

@ -0,0 +1,58 @@
#ifndef FONT_RENDERER_H
#define FONT_RENDERER_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
unsigned short x0, y0, x1, y1;
float xoff, yoff, xadvance;
} FR_Bitmap_Glyph_Metrics;
typedef struct FR_Bitmap FR_Bitmap;
#ifdef __cplusplus
class FR_Renderer;
#else
struct FR_Renderer;
typedef struct FR_Renderer FR_Renderer;
#endif
enum {
FR_HINTING = 1 << 0,
FR_KERNING = 1 << 1,
FR_SUBPIXEL = 1 << 2,
FR_PRESCALE_X = 1 << 3,
};
typedef struct {
uint8_t r, g, b;
} FR_Color;
typedef struct {
int left, top, right, bottom;
} FR_Clip_Area;
FR_Renderer * FR_Renderer_New(unsigned int flags);
void FR_Renderer_Free(FR_Renderer *);
int FR_Load_Font(FR_Renderer *, const char *filename);
FR_Bitmap* FR_Bitmap_New(FR_Renderer *, int width, int height);
void FR_Bitmap_Free(FR_Bitmap *image);
int FR_Get_Font_Height(FR_Renderer *, float size);
FR_Bitmap * FR_Bake_Font_Bitmap(FR_Renderer *, int font_height,
int first_char, int num_chars, FR_Bitmap_Glyph_Metrics *glyph_info);
void FR_Blend_Glyph(FR_Renderer *font_renderer,
FR_Clip_Area *clip, int x, int y,
uint8_t *dst, int dst_width,
const FR_Bitmap *glyphs_bitmap,
const FR_Bitmap_Glyph_Metrics *glyph, FR_Color color);
int FR_Subpixel_Scale(FR_Renderer *);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,164 @@
#pragma once
#include "agg_basics.h"
#include "agg_conv_curve.h"
#include "agg_conv_transform.h"
#include "agg_gamma_lut.h"
#include "agg_font_freetype.h"
#include "agg_pixfmt_alpha8.h"
#include "agg_rasterizer_scanline_aa.h"
#include "agg_renderer_primitives.h"
#include "agg_renderer_scanline.h"
#include "agg_rendering_buffer.h"
#include "agg_scanline_u.h"
class font_renderer_alpha
{
typedef agg::pixfmt_alpha8 pixfmt_type;
typedef agg::renderer_base<pixfmt_type> base_ren_type;
typedef agg::renderer_scanline_aa_solid<base_ren_type> renderer_solid;
typedef agg::font_engine_freetype_int32 font_engine_type;
typedef agg::font_cache_manager<font_engine_type> font_manager_type;
font_engine_type m_feng;
font_manager_type m_fman;
// Font rendering options.
bool m_hinting;
bool m_kerning;
bool m_subpixel;
bool m_prescale_x;
bool m_font_loaded;
// Pipeline to process the vectors glyph paths (curves + contour)
agg::trans_affine m_mtx;
agg::conv_curve<font_manager_type::path_adaptor_type> m_curves;
agg::conv_transform<agg::conv_curve<font_manager_type::path_adaptor_type> > m_trans;
public:
typedef agg::pixfmt_alpha8::color_type color_type;
font_renderer_alpha(bool hinting, bool kerning, bool subpixel, bool prescale_x):
m_feng(),
m_fman(m_feng),
m_hinting(hinting),
m_kerning(kerning),
m_subpixel(subpixel),
m_prescale_x(prescale_x),
m_font_loaded(false),
m_curves(m_fman.path_adaptor()),
m_trans(m_curves, m_mtx)
{ }
int get_face_height() const {
return m_feng.face_height();
}
void get_font_vmetrics(double& ascender, double& descender) {
double current_height = m_feng.height();
m_feng.height(1.0);
ascender = m_feng.ascender();
descender = m_feng.descender();
m_feng.height(current_height);
}
float scale_for_em_to_pixels(float size) {
int units_per_em = m_feng.face_units_em();
if (units_per_em > 0) {
return size / units_per_em;
}
return 0.0;
}
bool load_font(const char *font_filename) {
if(m_feng.load_font(font_filename, 0, agg::glyph_ren_outline)) {
m_font_loaded = true;
m_feng.hinting(m_hinting);
}
return m_font_loaded;
}
void set_font_height(double height) {
const double scale_x = (m_prescale_x ? 100.0 : 1.0);
m_feng.height(height);
m_feng.width(height * scale_x);
}
template<class Rasterizer, class Scanline, class RenSolid>
void draw_codepoint(Rasterizer& ras, Scanline& sl,
RenSolid& ren_solid, const color_type color,
int codepoint, double& x, double& y, const int subpixel_scale)
{
const double scale_x = (m_prescale_x ? 100.0 : 1.0);
// Coefficient to scale back the glyph to the final scale.
const double cx_inv_scale = subpixel_scale / scale_x;
// Represent the delta in x scaled by scale_x.
double x_delta = 0;
double start_x = x;
const agg::glyph_cache* glyph = m_fman.glyph(codepoint);
if(glyph)
{
if(m_kerning)
{
m_fman.add_kerning(&x_delta, &y);
}
m_fman.init_embedded_adaptors(glyph, 0, 0);
if(glyph->data_type == agg::glyph_data_outline)
{
double ty = m_hinting ? floor(y + 0.5) : y;
ras.reset();
m_mtx.reset();
m_mtx *= agg::trans_affine_scaling(cx_inv_scale, 1);
m_mtx *= agg::trans_affine_translation(start_x + cx_inv_scale * x_delta, ty);
ras.add_path(m_trans);
ren_solid.color(color);
agg::render_scanlines(ras, sl, ren_solid);
}
y += glyph->advance_y;
x += cx_inv_scale * (x_delta + glyph->advance_x);
}
}
void clear(agg::rendering_buffer& ren_buf, const color_type color) {
pixfmt_type pf(ren_buf);
base_ren_type ren_base(pf);
ren_base.clear(color);
}
void render_codepoint(agg::rendering_buffer& ren_buf,
const color_type text_color,
double& x, double& y,
int codepoint, const int subpixel_scale)
{
if (!m_font_loaded) {
return;
}
agg::scanline_u8 sl;
agg::rasterizer_scanline_aa<> ras;
ras.clip_box(0, 0, ren_buf.width(), ren_buf.height());
agg::pixfmt_alpha8 pf(ren_buf);
base_ren_type ren_base(pf);
renderer_solid ren_solid(ren_base);
draw_codepoint(ras, sl, ren_solid, text_color, codepoint, x, y, subpixel_scale);
}
int codepoint_bounds(int codepoint, const int subpixel_scale, agg::rect_i& bounds)
{
if (!m_font_loaded) return 1;
const double scale_x = (m_prescale_x ? 100.0 : 1.0);
const double cx_inv_scale = subpixel_scale / scale_x;
const agg::glyph_cache* glyph = m_fman.glyph(codepoint);
if (glyph) {
bounds = glyph->bounds;
bounds.x1 *= cx_inv_scale;
bounds.x2 *= cx_inv_scale;
return 0;
}
return 1;
}
};

View File

@ -0,0 +1,19 @@
freetype_dep = dependency('freetype2')
libagg_dep = dependency('libagg', fallback: ['libagg', 'libagg_dep'])
font_renderer_sources = [
'agg_font_freetype.cpp',
'font_renderer.cpp',
]
font_renderer_cdefs = ['-DFONT_RENDERER_HEIGHT_HACK']
font_renderer_include = include_directories('.')
libfontrenderer = static_library('fontrenderer',
font_renderer_sources,
dependencies: [libagg_dep, freetype_dep],
cpp_args: font_renderer_cdefs,
)

View File

@ -0,0 +1,125 @@
```c
stbtt_InitFont
stbtt_ScaleForMappingEmToPixels x 3
stbtt_ScaleForPixelHeight
stbtt_BakeFontBitmap
stbtt_GetFontVMetrics x 2
typedef struct {
unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap
float xoff, yoff, xadvance;
} stbtt_bakedchar;
struct RenImage {
RenColor *pixels;
int width, height;
};
typedef struct {
RenImage *image;
stbtt_bakedchar glyphs[256];
} GlyphSet;
struct RenFont {
void *data;
stbtt_fontinfo stbfont;
GlyphSet *sets[MAX_GLYPHSET];
float size;
int height;
};
```
The function stbtt_BakeFontBitmap is used to write bitmap data into set->image->pixels (where set is a GlyphSet).
Note that set->image->pixels need data in RGB format. After stbtt_BakeFontBitmap call the bitmap data are converted into RGB.
With a single call many glyphs corresponding to a range of codepoints, all in a
single image.
## STB truetype font metrics
stbtt_ScaleForPixelHeight takes a float 'height' and returns height / (ascent - descent).
stbtt_ScaleForMappingEmToPixels take a float 'pixels' and returns pixels / unitsPerEm.
### Computing RenFont
When loading a font, in renderer.c, the font->height is determined as:
```c
int ascent, descent, linegap;
stbtt_GetFontVMetrics(&font->stbfont, &ascent, &descent, &linegap);
float scale = stbtt_ScaleForMappingEmToPixels(&font->stbfont, font->size);
font->height = (ascent - descent + linegap) * scale + 0.5;
```
so, mathematically
```c
font->height = (ascent - descent + linegap) * font->size / unitsPerEm + 0.5;
```
**TO DO**: find out for what font->height is actually used.
### Call to BakeFontBitmap
In the same file, renderer.c, to create the glyphset image it computes:
```c
// Using stbtt functions
float s = ScaleForMappingEmToPixels(1) / ScaleForPixelHeight(1);
```
so 's' is actually equal to (ascent - descent) / unitsPerEm.
Then BakeFontBitmap is called and `font->size * s` is used for the pixel_height argument.
So BakeFontBitmap gets
```c
pixel_height = (ascent - descent) * font->size / unitsPerEm;
```
In turns BakeFontBitmap passes pixel_height to ScaleForPixelHeight() and uses the
resulting 'scale' to scale the glyphs by passing it to MakeGlyphBitmap().
This is equal almost equal to font->height except the 0.5, the missing linegap calculation
and the fact that this latter is an integer instead of a float.
## AGG Font Engine
Calls
`FT_Init_FreeType` (initialize the library)
In `load_font()` method:
`FT_New_Face` or `FT_New_Memory_Face` (use `FT_Done_Face` when done)
`FT_Attach_File`
`FT_Select_Charmap`
In `update_char_size()` method:
`FT_Set_Char_Size` or `FT_Set_Pixel_Sizes`
In `prepare_glyph()` method:
`FT_Get_Char_Index`
`FT_Load_Glyph`
`FT_Render_Glyph` (if glyph_render_native_mono or native_gray8)
in `add_kerning()` method
`FT_Get_Kerning`
`FT_Done_FreeType` (end with library)
## Freetype2's metrics related function and structs
`FT_New_Face` return a `FT_Face` (a pointer) with font face information.
## AGG font engine's font size
The variable `m_height` is the size of the font muliplied by 64.
It will be used to set font size with:
- `FT_Set_Char_Size` if m_resolution is set (> 0)
- `FT_Set_Pixel_Sizes`, divided by 64, if m_resolution is not set (= 0)
The method height() returns m_height / 64;

View File

@ -1,9 +1,9 @@
project('lite-xl',
['c'],
['c', 'cpp'],
version : '2.0.3',
license : 'MIT',
meson_version : '>= 0.54',
default_options : ['c_std=gnu11']
default_options : ['c_std=gnu11', 'cpp_std=c++03']
)
#===============================================================================
@ -23,7 +23,6 @@ endif
cc = meson.get_compiler('c')
lite_includes = []
lite_cargs = []
# On macos we need to use the SDL renderer to support retina displays
if get_option('renderer') or host_machine.system() == 'darwin'
@ -34,7 +33,7 @@ endif
#===============================================================================
lite_link_args = []
if cc.get_id() == 'gcc' and get_option('buildtype') == 'release'
lite_link_args += ['-static-libgcc']
lite_link_args += ['-static-libgcc', '-static-libstdc++']
endif
if host_machine.system() == 'darwin'
@ -51,9 +50,21 @@ if not get_option('source-only')
default_options: ['shared=false', 'use_readline=false', 'app=false']
)
pcre2_dep = dependency('libpcre2-8')
freetype_dep = dependency('freetype2')
sdl_dep = dependency('sdl2', method: 'config-tool')
lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl, threads_dep]
reproc_dep = dependency('reproc', fallback: ['reproc', 'reproc_dep'],
default_options: [
'default_library=static', 'multithreaded=false',
'reproc-cpp=false', 'examples=false'
]
)
lite_deps = [lua_dep, sdl_dep, reproc_dep, pcre2_dep, libm, libdl, threads_dep]
if host_machine.system() == 'windows'
# Note that we need to explicitly add the windows socket DLL because
# the pkg-config file from reproc does not include it.
lite_deps += meson.get_compiler('cpp').find_library('ws2_32', required: true)
endif
endif
#===============================================================================
# Install Configuration
@ -107,8 +118,11 @@ configure_file(
install_dir : lite_datadir / 'core',
)
#===============================================================================
# Targets
#===============================================================================
if not get_option('source-only')
subdir('lib/dmon')
subdir('lib/font_renderer')
subdir('src')
subdir('scripts')
endif

View File

@ -1,768 +0,0 @@
#ifndef LITE_XL_PLUGIN_API
#define LITE_XL_PLUGIN_API
/**
The lite_xl plugin API is quite simple. Any shared library can be a plugin file, so long
as it has an entrypoint that looks like the following, where xxxxx is the plugin name:
#include "lite_xl_plugin_api.h"
int lua_open_lite_xl_xxxxx(lua_State* L, void* XL) {
lite_xl_plugin_init(XL);
...
return 1;
}
In linux, to compile this file, you'd do: 'gcc -o xxxxx.so -shared xxxxx.c'. Simple!
Due to the way the API is structured, you *should not* link or include lua libraries.
This file was automatically generated. DO NOT MODIFY DIRECTLY.
**/
#include <stdarg.h>
#include <stdio.h> // for BUFSIZ? this is kinda weird
/** luaconf.h **/
#ifndef lconfig_h
#define lconfig_h
#include <limits.h>
#include <stddef.h>
#if !defined(LUA_ANSI) && defined(__STRICT_ANSI__)
#define LUA_ANSI
#endif
#if !defined(LUA_ANSI) && defined(_WIN32) && !defined(_WIN32_WCE)
#define LUA_WIN
#endif
#if defined(LUA_WIN)
#define LUA_DL_DLL
#define LUA_USE_AFORMAT
#endif
#if defined(LUA_USE_LINUX)
#define LUA_USE_POSIX
#define LUA_USE_DLOPEN
#define LUA_USE_READLINE
#define LUA_USE_STRTODHEX
#define LUA_USE_AFORMAT
#define LUA_USE_LONGLONG
#endif
#if defined(LUA_USE_MACOSX)
#define LUA_USE_POSIX
#define LUA_USE_DLOPEN
#define LUA_USE_READLINE
#define LUA_USE_STRTODHEX
#define LUA_USE_AFORMAT
#define LUA_USE_LONGLONG
#endif
#if defined(LUA_USE_POSIX)
#define LUA_USE_MKSTEMP
#define LUA_USE_ISATTY
#define LUA_USE_POPEN
#define LUA_USE_ULONGJMP
#define LUA_USE_GMTIME_R
#endif
#if defined(_WIN32)
#define LUA_LDIR "!\\lua\\"
#define LUA_CDIR "!\\"
#define LUA_PATH_DEFAULT LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" ".\\?.lua"
#define LUA_CPATH_DEFAULT LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll;" ".\\?.dll"
#else
#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR "/"
#define LUA_ROOT "/usr/local/"
#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR
#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR
#define LUA_PATH_DEFAULT LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" "./?.lua"
#define LUA_CPATH_DEFAULT LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so"
#endif
#if defined(_WIN32)
#define LUA_DIRSEP "\\"
#else
#define LUA_DIRSEP "/"
#endif
#define LUA_ENV "_ENV"
#if defined(LUA_BUILD_AS_DLL)
#if defined(LUA_CORE) || defined(LUA_LIB)
#define LUA_API __declspec(dllexport)
#else
#define LUA_API __declspec(dllimport)
#endif
#else
#define LUA_API extern
#endif
#define LUALIB_API LUA_API
#define LUAMOD_API LUALIB_API
#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && defined(__ELF__)
#define LUAI_FUNC __attribute__((visibility("hidden"))) extern
#define LUAI_DDEC LUAI_FUNC
#define LUAI_DDEF
#else
#define LUAI_FUNC extern
#define LUAI_DDEC extern
#define LUAI_DDEF
#endif
#define LUA_QL(x) "'" x "'"
#define LUA_QS LUA_QL("%s")
#define LUA_IDSIZE 60
#if defined(LUA_LIB) || defined(lua_c)
#include <stdio.h>
#define luai_writestring(s,l) fwrite((s), sizeof(char), (l), stdout)
#define luai_writeline() (luai_writestring("\n", 1), fflush(stdout))
#endif
#define luai_writestringerror(s,p) (fprintf(stderr, (s), (p)), fflush(stderr))
#define LUAI_MAXSHORTLEN 40
#if defined(LUA_COMPAT_ALL)
#define LUA_COMPAT_UNPACK
#define LUA_COMPAT_LOADERS
#define lua_cpcall(L,f,u) (lua_pushcfunction(L, (f)), lua_pushlightuserdata(L,(u)), lua_pcall(L,1,0,0))
#define LUA_COMPAT_LOG10
#define LUA_COMPAT_LOADSTRING
#define LUA_COMPAT_MAXN
#define lua_strlen(L,i) lua_rawlen(L, (i))
#define lua_objlen(L,i) lua_rawlen(L, (i))
#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ)
#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT)
#define LUA_COMPAT_MODULE
#endif
#if INT_MAX-20 < 32760
#define LUAI_BITSINT 16
#elif INT_MAX > 2147483640L
#define LUAI_BITSINT 32
#else
#error "you must define LUA_BITSINT with number of bits in an integer"
#endif
#if LUAI_BITSINT >= 32
#define LUA_INT32 int
#define LUAI_UMEM size_t
#define LUAI_MEM ptrdiff_t
#else
#define LUA_INT32 long
#define LUAI_UMEM unsigned long
#define LUAI_MEM long
#endif
#if LUAI_BITSINT >= 32
#define LUAI_MAXSTACK 1000000
#else
#define LUAI_MAXSTACK 15000
#endif
#define LUAI_FIRSTPSEUDOIDX (-LUAI_MAXSTACK - 1000)
#define LUAL_BUFFERSIZE BUFSIZ
#define LUA_NUMBER_DOUBLE
#define LUA_NUMBER double
#define LUAI_UACNUMBER double
#define LUA_NUMBER_SCAN "%lf"
#define LUA_NUMBER_FMT "%.14g"
#define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n))
#define LUAI_MAXNUMBER2STR 32
#define l_mathop(x) (x)
#define lua_str2number(s,p) strtod((s), (p))
#if defined(LUA_USE_STRTODHEX)
#define lua_strx2number(s,p) strtod((s), (p))
#endif
#if defined(lobject_c) || defined(lvm_c)
#include <math.h>
#define luai_nummod(L,a,b) ((a) - l_mathop(floor)((a)/(b))*(b))
#define luai_numpow(L,a,b) (l_mathop(pow)(a,b))
#endif
#if defined(LUA_CORE)
#define luai_numadd(L,a,b) ((a)+(b))
#define luai_numsub(L,a,b) ((a)-(b))
#define luai_nummul(L,a,b) ((a)*(b))
#define luai_numdiv(L,a,b) ((a)/(b))
#define luai_numunm(L,a) (-(a))
#define luai_numeq(a,b) ((a)==(b))
#define luai_numlt(L,a,b) ((a)<(b))
#define luai_numle(L,a,b) ((a)<=(b))
#define luai_numisnan(L,a) (!luai_numeq((a), (a)))
#endif
#define LUA_INTEGER ptrdiff_t
#define LUA_UNSIGNED unsigned LUA_INT32
#if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI)
#if defined(LUA_WIN) && defined(_MSC_VER) && defined(_M_IX86)
#define LUA_MSASMTRICK
#define LUA_IEEEENDIAN 0
#define LUA_NANTRICK
#elif defined(__i386__) || defined(__i386) || defined(__X86__)
#define LUA_IEEE754TRICK
#define LUA_IEEELL
#define LUA_IEEEENDIAN 0
#define LUA_NANTRICK
#elif defined(__x86_64)
#define LUA_IEEE754TRICK
#define LUA_IEEEENDIAN 0
#elif defined(__POWERPC__) || defined(__ppc__)
#define LUA_IEEE754TRICK
#define LUA_IEEEENDIAN 1
#else
#define LUA_IEEE754TRICK
#endif
#endif
#endif
/** lua.h **/
typedef struct lua_State lua_State;
typedef int (*lua_CFunction) (lua_State *L);
typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz);
typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud);
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
typedef LUA_NUMBER lua_Number;
typedef LUA_INTEGER lua_Integer;
typedef LUA_UNSIGNED lua_Unsigned;
typedef struct lua_Debug lua_Debug;
typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);
struct lua_Debug {
int event;
const char *name;
const char *namewhat;
const char *what;
const char *source;
int currentline;
int linedefined;
int lastlinedefined;
unsigned char nups;
unsigned char nparams;
char isvararg;
char istailcall;
char short_src[LUA_IDSIZE];
struct CallInfo *i_ci;
};
static lua_State *(*lua_newstate) (lua_Alloc f, void *ud);
static void (*lua_close) (lua_State *L);
static lua_State *(*lua_newthread) (lua_State *L);
static lua_CFunction (*lua_atpanic) (lua_State *L, lua_CFunction panicf);
static const lua_Number *(*lua_version) (lua_State *L);
static int (*lua_absindex) (lua_State *L, int idx);
static int (*lua_gettop) (lua_State *L);
static void (*lua_settop) (lua_State *L, int idx);
static void (*lua_pushvalue) (lua_State *L, int idx);
static void (*lua_remove) (lua_State *L, int idx);
static void (*lua_insert) (lua_State *L, int idx);
static void (*lua_replace) (lua_State *L, int idx);
static void (*lua_copy) (lua_State *L, int fromidx, int toidx);
static int (*lua_checkstack) (lua_State *L, int sz);
static void (*lua_xmove) (lua_State *from, lua_State *to, int n);
static int (*lua_isnumber) (lua_State *L, int idx);
static int (*lua_isstring) (lua_State *L, int idx);
static int (*lua_iscfunction) (lua_State *L, int idx);
static int (*lua_isuserdata) (lua_State *L, int idx);
static int (*lua_type) (lua_State *L, int idx);
static const char *(*lua_typename) (lua_State *L, int tp);
static lua_Number (*lua_tonumberx) (lua_State *L, int idx, int *isnum);
static lua_Integer (*lua_tointegerx) (lua_State *L, int idx, int *isnum);
static lua_Unsigned (*lua_tounsignedx) (lua_State *L, int idx, int *isnum);
static int (*lua_toboolean) (lua_State *L, int idx);
static const char *(*lua_tolstring) (lua_State *L, int idx, size_t *len);
static size_t (*lua_rawlen) (lua_State *L, int idx);
static lua_CFunction (*lua_tocfunction) (lua_State *L, int idx);
static void *(*lua_touserdata) (lua_State *L, int idx);
static lua_State *(*lua_tothread) (lua_State *L, int idx);
static const void *(*lua_topointer) (lua_State *L, int idx);
static void (*lua_arith) (lua_State *L, int op);
static int (*lua_rawequal) (lua_State *L, int idx1, int idx2);
static int (*lua_compare) (lua_State *L, int idx1, int idx2, int op);
static void (*lua_pushnil) (lua_State *L);
static void (*lua_pushnumber) (lua_State *L, lua_Number n);
static void (*lua_pushinteger) (lua_State *L, lua_Integer n);
static void (*lua_pushunsigned) (lua_State *L, lua_Unsigned n);
static const char *(*lua_pushlstring) (lua_State *L, const char *s, size_t l);
static const char *(*lua_pushstring) (lua_State *L, const char *s);
static const char *(*lua_pushvfstring) (lua_State *L, const char *fmt, va_list argp);
static const char *(*lua_pushfstring) (lua_State *L, const char *fmt, ...);
static void (*lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
static void (*lua_pushboolean) (lua_State *L, int b);
static void (*lua_pushlightuserdata) (lua_State *L, void *p);
static int (*lua_pushthread) (lua_State *L);
static void (*lua_getglobal) (lua_State *L, const char *var);
static void (*lua_gettable) (lua_State *L, int idx);
static void (*lua_getfield) (lua_State *L, int idx, const char *k);
static void (*lua_rawget) (lua_State *L, int idx);
static void (*lua_rawgeti) (lua_State *L, int idx, int n);
static void (*lua_rawgetp) (lua_State *L, int idx, const void *p);
static void (*lua_createtable) (lua_State *L, int narr, int nrec);
static void *(*lua_newuserdata) (lua_State *L, size_t sz);
static int (*lua_getmetatable) (lua_State *L, int objindex);
static void (*lua_getuservalue) (lua_State *L, int idx);
static void (*lua_setglobal) (lua_State *L, const char *var);
static void (*lua_settable) (lua_State *L, int idx);
static void (*lua_setfield) (lua_State *L, int idx, const char *k);
static void (*lua_rawset) (lua_State *L, int idx);
static void (*lua_rawseti) (lua_State *L, int idx, int n);
static void (*lua_rawsetp) (lua_State *L, int idx, const void *p);
static int (*lua_setmetatable) (lua_State *L, int objindex);
static void (*lua_setuservalue) (lua_State *L, int idx);
static void (*lua_callk) (lua_State *L, int nargs, int nresults, int ctx, lua_CFunction k);
static int (*lua_getctx) (lua_State *L, int *ctx);
static int (*lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k);
static int (*lua_load) (lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode);
static int (*lua_dump) (lua_State *L, lua_Writer writer, void *data);
static int (*lua_yieldk) (lua_State *L, int nresults, int ctx, lua_CFunction k);
static int (*lua_resume) (lua_State *L, lua_State *from, int narg);
static int (*lua_status) (lua_State *L);
static int (*lua_gc) (lua_State *L, int what, int data);
static int (*lua_error) (lua_State *L);
static int (*lua_next) (lua_State *L, int idx);
static void (*lua_concat) (lua_State *L, int n);
static void (*lua_len) (lua_State *L, int idx);
static lua_Alloc (*lua_getallocf) (lua_State *L, void **ud);
static void (*lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
static int (*lua_getstack) (lua_State *L, int level, lua_Debug *ar);
static int (*lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar);
static const char *(*lua_getlocal) (lua_State *L, const lua_Debug *ar, int n);
static const char *(*lua_setlocal) (lua_State *L, const lua_Debug *ar, int n);
static const char *(*lua_getupvalue) (lua_State *L, int funcindex, int n);
static const char *(*lua_setupvalue) (lua_State *L, int funcindex, int n);
static void *(*lua_upvalueid) (lua_State *L, int fidx, int n);
static void (*lua_upvaluejoin) (lua_State *L, int fidx1, int n1, int fidx2, int n2);
static int (*lua_sethook) (lua_State *L, lua_Hook func, int mask, int count);
static lua_Hook (*lua_gethook) (lua_State *L);
static int (*lua_gethookmask) (lua_State *L);
static int (*lua_gethookcount) (lua_State *L);
#define lua_h
#define LUA_VERSION_MAJOR "5"
#define LUA_VERSION_MINOR "2"
#define LUA_VERSION_NUM 502
#define LUA_VERSION_RELEASE "4"
#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR
#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE
#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2015 Lua.org, PUC-Rio"
#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes"
#define LUA_SIGNATURE "\033Lua"
#define LUA_MULTRET (-1)
#define LUA_REGISTRYINDEX LUAI_FIRSTPSEUDOIDX
#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i))
#define LUA_OK 0
#define LUA_YIELD 1
#define LUA_ERRRUN 2
#define LUA_ERRSYNTAX 3
#define LUA_ERRMEM 4
#define LUA_ERRGCMM 5
#define LUA_ERRERR 6
#define LUA_TNONE (-1)
#define LUA_TNIL 0
#define LUA_TBOOLEAN 1
#define LUA_TLIGHTUSERDATA 2
#define LUA_TNUMBER 3
#define LUA_TSTRING 4
#define LUA_TTABLE 5
#define LUA_TFUNCTION 6
#define LUA_TUSERDATA 7
#define LUA_TTHREAD 8
#define LUA_NUMTAGS 9
#define LUA_MINSTACK 20
#define LUA_RIDX_MAINTHREAD 1
#define LUA_RIDX_GLOBALS 2
#define LUA_RIDX_LAST LUA_RIDX_GLOBALS
#define LUA_OPADD 0
#define LUA_OPSUB 1
#define LUA_OPMUL 2
#define LUA_OPDIV 3
#define LUA_OPMOD 4
#define LUA_OPPOW 5
#define LUA_OPUNM 6
#define LUA_OPEQ 0
#define LUA_OPLT 1
#define LUA_OPLE 2
#define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL)
#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL)
#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL)
#define LUA_GCSTOP 0
#define LUA_GCRESTART 1
#define LUA_GCCOLLECT 2
#define LUA_GCCOUNT 3
#define LUA_GCCOUNTB 4
#define LUA_GCSTEP 5
#define LUA_GCSETPAUSE 6
#define LUA_GCSETSTEPMUL 7
#define LUA_GCSETMAJORINC 8
#define LUA_GCISRUNNING 9
#define LUA_GCGEN 10
#define LUA_GCINC 11
#define lua_tonumber(L,i) lua_tonumberx(L,i,NULL)
#define lua_tointeger(L,i) lua_tointegerx(L,i,NULL)
#define lua_tounsigned(L,i) lua_tounsignedx(L,i,NULL)
#define lua_pop(L,n) lua_settop(L, -(n)-1)
#define lua_newtable(L) lua_createtable(L, 0, 0)
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)
#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION)
#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE)
#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL)
#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN)
#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD)
#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE)
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0)
#define lua_pushliteral(L, s) lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1)
#define lua_pushglobaltable(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)
#define lua_tostring(L,i) lua_tolstring(L, (i), NULL)
#define LUA_HOOKCALL 0
#define LUA_HOOKRET 1
#define LUA_HOOKLINE 2
#define LUA_HOOKCOUNT 3
#define LUA_HOOKTAILCALL 4
#define LUA_MASKCALL (1 << LUA_HOOKCALL)
#define LUA_MASKRET (1 << LUA_HOOKRET)
#define LUA_MASKLINE (1 << LUA_HOOKLINE)
#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT)
static lua_State * __lite_xl_fallback_lua_newstate (lua_Alloc f, void *ud) { fputs("warning: lua_newstate is a stub", stderr); }
static void __lite_xl_fallback_lua_close (lua_State *L) { fputs("warning: lua_close is a stub", stderr); }
static lua_State * __lite_xl_fallback_lua_newthread (lua_State *L) { fputs("warning: lua_newthread is a stub", stderr); }
static lua_CFunction __lite_xl_fallback_lua_atpanic (lua_State *L, lua_CFunction panicf) { fputs("warning: lua_atpanic is a stub", stderr); }
static const lua_Number * __lite_xl_fallback_lua_version (lua_State *L) { fputs("warning: lua_version is a stub", stderr); }
static int __lite_xl_fallback_lua_absindex (lua_State *L, int idx) { fputs("warning: lua_absindex is a stub", stderr); }
static int __lite_xl_fallback_lua_gettop (lua_State *L) { fputs("warning: lua_gettop is a stub", stderr); }
static void __lite_xl_fallback_lua_settop (lua_State *L, int idx) { fputs("warning: lua_settop is a stub", stderr); }
static void __lite_xl_fallback_lua_pushvalue (lua_State *L, int idx) { fputs("warning: lua_pushvalue is a stub", stderr); }
static void __lite_xl_fallback_lua_remove (lua_State *L, int idx) { fputs("warning: lua_remove is a stub", stderr); }
static void __lite_xl_fallback_lua_insert (lua_State *L, int idx) { fputs("warning: lua_insert is a stub", stderr); }
static void __lite_xl_fallback_lua_replace (lua_State *L, int idx) { fputs("warning: lua_replace is a stub", stderr); }
static void __lite_xl_fallback_lua_copy (lua_State *L, int fromidx, int toidx) { fputs("warning: lua_copy is a stub", stderr); }
static int __lite_xl_fallback_lua_checkstack (lua_State *L, int sz) { fputs("warning: lua_checkstack is a stub", stderr); }
static void __lite_xl_fallback_lua_xmove (lua_State *from, lua_State *to, int n) { fputs("warning: lua_xmove is a stub", stderr); }
static int __lite_xl_fallback_lua_isnumber (lua_State *L, int idx) { fputs("warning: lua_isnumber is a stub", stderr); }
static int __lite_xl_fallback_lua_isstring (lua_State *L, int idx) { fputs("warning: lua_isstring is a stub", stderr); }
static int __lite_xl_fallback_lua_iscfunction (lua_State *L, int idx) { fputs("warning: lua_iscfunction is a stub", stderr); }
static int __lite_xl_fallback_lua_isuserdata (lua_State *L, int idx) { fputs("warning: lua_isuserdata is a stub", stderr); }
static int __lite_xl_fallback_lua_type (lua_State *L, int idx) { fputs("warning: lua_type is a stub", stderr); }
static const char * __lite_xl_fallback_lua_typename (lua_State *L, int tp) { fputs("warning: lua_typename is a stub", stderr); }
static lua_Number __lite_xl_fallback_lua_tonumberx (lua_State *L, int idx, int *isnum) { fputs("warning: lua_tonumberx is a stub", stderr); }
static lua_Integer __lite_xl_fallback_lua_tointegerx (lua_State *L, int idx, int *isnum) { fputs("warning: lua_tointegerx is a stub", stderr); }
static lua_Unsigned __lite_xl_fallback_lua_tounsignedx (lua_State *L, int idx, int *isnum) { fputs("warning: lua_tounsignedx is a stub", stderr); }
static int __lite_xl_fallback_lua_toboolean (lua_State *L, int idx) { fputs("warning: lua_toboolean is a stub", stderr); }
static const char * __lite_xl_fallback_lua_tolstring (lua_State *L, int idx, size_t *len) { fputs("warning: lua_tolstring is a stub", stderr); }
static size_t __lite_xl_fallback_lua_rawlen (lua_State *L, int idx) { fputs("warning: lua_rawlen is a stub", stderr); }
static lua_CFunction __lite_xl_fallback_lua_tocfunction (lua_State *L, int idx) { fputs("warning: lua_tocfunction is a stub", stderr); }
static void * __lite_xl_fallback_lua_touserdata (lua_State *L, int idx) { fputs("warning: lua_touserdata is a stub", stderr); }
static lua_State * __lite_xl_fallback_lua_tothread (lua_State *L, int idx) { fputs("warning: lua_tothread is a stub", stderr); }
static const void * __lite_xl_fallback_lua_topointer (lua_State *L, int idx) { fputs("warning: lua_topointer is a stub", stderr); }
static void __lite_xl_fallback_lua_arith (lua_State *L, int op) { fputs("warning: lua_arith is a stub", stderr); }
static int __lite_xl_fallback_lua_rawequal (lua_State *L, int idx1, int idx2) { fputs("warning: lua_rawequal is a stub", stderr); }
static int __lite_xl_fallback_lua_compare (lua_State *L, int idx1, int idx2, int op) { fputs("warning: lua_compare is a stub", stderr); }
static void __lite_xl_fallback_lua_pushnil (lua_State *L) { fputs("warning: lua_pushnil is a stub", stderr); }
static void __lite_xl_fallback_lua_pushnumber (lua_State *L, lua_Number n) { fputs("warning: lua_pushnumber is a stub", stderr); }
static void __lite_xl_fallback_lua_pushinteger (lua_State *L, lua_Integer n) { fputs("warning: lua_pushinteger is a stub", stderr); }
static void __lite_xl_fallback_lua_pushunsigned (lua_State *L, lua_Unsigned n) { fputs("warning: lua_pushunsigned is a stub", stderr); }
static const char * __lite_xl_fallback_lua_pushlstring (lua_State *L, const char *s, size_t l) { fputs("warning: lua_pushlstring is a stub", stderr); }
static const char * __lite_xl_fallback_lua_pushstring (lua_State *L, const char *s) { fputs("warning: lua_pushstring is a stub", stderr); }
static const char * __lite_xl_fallback_lua_pushvfstring (lua_State *L, const char *fmt, va_list argp) { fputs("warning: lua_pushvfstring is a stub", stderr); }
static const char * __lite_xl_fallback_lua_pushfstring (lua_State *L, const char *fmt, ...) { fputs("warning: lua_pushfstring is a stub", stderr); }
static void __lite_xl_fallback_lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { fputs("warning: lua_pushcclosure is a stub", stderr); }
static void __lite_xl_fallback_lua_pushboolean (lua_State *L, int b) { fputs("warning: lua_pushboolean is a stub", stderr); }
static void __lite_xl_fallback_lua_pushlightuserdata (lua_State *L, void *p) { fputs("warning: lua_pushlightuserdata is a stub", stderr); }
static int __lite_xl_fallback_lua_pushthread (lua_State *L) { fputs("warning: lua_pushthread is a stub", stderr); }
static void __lite_xl_fallback_lua_getglobal (lua_State *L, const char *var) { fputs("warning: lua_getglobal is a stub", stderr); }
static void __lite_xl_fallback_lua_gettable (lua_State *L, int idx) { fputs("warning: lua_gettable is a stub", stderr); }
static void __lite_xl_fallback_lua_getfield (lua_State *L, int idx, const char *k) { fputs("warning: lua_getfield is a stub", stderr); }
static void __lite_xl_fallback_lua_rawget (lua_State *L, int idx) { fputs("warning: lua_rawget is a stub", stderr); }
static void __lite_xl_fallback_lua_rawgeti (lua_State *L, int idx, int n) { fputs("warning: lua_rawgeti is a stub", stderr); }
static void __lite_xl_fallback_lua_rawgetp (lua_State *L, int idx, const void *p) { fputs("warning: lua_rawgetp is a stub", stderr); }
static void __lite_xl_fallback_lua_createtable (lua_State *L, int narr, int nrec) { fputs("warning: lua_createtable is a stub", stderr); }
static void * __lite_xl_fallback_lua_newuserdata (lua_State *L, size_t sz) { fputs("warning: lua_newuserdata is a stub", stderr); }
static int __lite_xl_fallback_lua_getmetatable (lua_State *L, int objindex) { fputs("warning: lua_getmetatable is a stub", stderr); }
static void __lite_xl_fallback_lua_getuservalue (lua_State *L, int idx) { fputs("warning: lua_getuservalue is a stub", stderr); }
static void __lite_xl_fallback_lua_setglobal (lua_State *L, const char *var) { fputs("warning: lua_setglobal is a stub", stderr); }
static void __lite_xl_fallback_lua_settable (lua_State *L, int idx) { fputs("warning: lua_settable is a stub", stderr); }
static void __lite_xl_fallback_lua_setfield (lua_State *L, int idx, const char *k) { fputs("warning: lua_setfield is a stub", stderr); }
static void __lite_xl_fallback_lua_rawset (lua_State *L, int idx) { fputs("warning: lua_rawset is a stub", stderr); }
static void __lite_xl_fallback_lua_rawseti (lua_State *L, int idx, int n) { fputs("warning: lua_rawseti is a stub", stderr); }
static void __lite_xl_fallback_lua_rawsetp (lua_State *L, int idx, const void *p) { fputs("warning: lua_rawsetp is a stub", stderr); }
static int __lite_xl_fallback_lua_setmetatable (lua_State *L, int objindex) { fputs("warning: lua_setmetatable is a stub", stderr); }
static void __lite_xl_fallback_lua_setuservalue (lua_State *L, int idx) { fputs("warning: lua_setuservalue is a stub", stderr); }
static void __lite_xl_fallback_lua_callk (lua_State *L, int nargs, int nresults, int ctx, lua_CFunction k) { fputs("warning: lua_callk is a stub", stderr); }
static int __lite_xl_fallback_lua_getctx (lua_State *L, int *ctx) { fputs("warning: lua_getctx is a stub", stderr); }
static int __lite_xl_fallback_lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k) { fputs("warning: lua_pcallk is a stub", stderr); }
static int __lite_xl_fallback_lua_load (lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode) { fputs("warning: lua_load is a stub", stderr); }
static int __lite_xl_fallback_lua_dump (lua_State *L, lua_Writer writer, void *data) { fputs("warning: lua_dump is a stub", stderr); }
static int __lite_xl_fallback_lua_yieldk (lua_State *L, int nresults, int ctx, lua_CFunction k) { fputs("warning: lua_yieldk is a stub", stderr); }
static int __lite_xl_fallback_lua_resume (lua_State *L, lua_State *from, int narg) { fputs("warning: lua_resume is a stub", stderr); }
static int __lite_xl_fallback_lua_status (lua_State *L) { fputs("warning: lua_status is a stub", stderr); }
static int __lite_xl_fallback_lua_gc (lua_State *L, int what, int data) { fputs("warning: lua_gc is a stub", stderr); }
static int __lite_xl_fallback_lua_error (lua_State *L) { fputs("warning: lua_error is a stub", stderr); }
static int __lite_xl_fallback_lua_next (lua_State *L, int idx) { fputs("warning: lua_next is a stub", stderr); }
static void __lite_xl_fallback_lua_concat (lua_State *L, int n) { fputs("warning: lua_concat is a stub", stderr); }
static void __lite_xl_fallback_lua_len (lua_State *L, int idx) { fputs("warning: lua_len is a stub", stderr); }
static lua_Alloc __lite_xl_fallback_lua_getallocf (lua_State *L, void **ud) { fputs("warning: lua_getallocf is a stub", stderr); }
static void __lite_xl_fallback_lua_setallocf (lua_State *L, lua_Alloc f, void *ud) { fputs("warning: lua_setallocf is a stub", stderr); }
static int __lite_xl_fallback_lua_getstack (lua_State *L, int level, lua_Debug *ar) { fputs("warning: lua_getstack is a stub", stderr); }
static int __lite_xl_fallback_lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { fputs("warning: lua_getinfo is a stub", stderr); }
static const char * __lite_xl_fallback_lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { fputs("warning: lua_getlocal is a stub", stderr); }
static const char * __lite_xl_fallback_lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { fputs("warning: lua_setlocal is a stub", stderr); }
static const char * __lite_xl_fallback_lua_getupvalue (lua_State *L, int funcindex, int n) { fputs("warning: lua_getupvalue is a stub", stderr); }
static const char * __lite_xl_fallback_lua_setupvalue (lua_State *L, int funcindex, int n) { fputs("warning: lua_setupvalue is a stub", stderr); }
static void * __lite_xl_fallback_lua_upvalueid (lua_State *L, int fidx, int n) { fputs("warning: lua_upvalueid is a stub", stderr); }
static void __lite_xl_fallback_lua_upvaluejoin (lua_State *L, int fidx1, int n1, int fidx2, int n2) { fputs("warning: lua_upvaluejoin is a stub", stderr); }
static int __lite_xl_fallback_lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { fputs("warning: lua_sethook is a stub", stderr); }
static lua_Hook __lite_xl_fallback_lua_gethook (lua_State *L) { fputs("warning: lua_gethook is a stub", stderr); }
static int __lite_xl_fallback_lua_gethookmask (lua_State *L) { fputs("warning: lua_gethookmask is a stub", stderr); }
static int __lite_xl_fallback_lua_gethookcount (lua_State *L) { fputs("warning: lua_gethookcount is a stub", stderr); }
/** lauxlib.h **/
typedef struct luaL_Reg {
const char *name;
lua_CFunction func;
} luaL_Reg;
typedef struct luaL_Buffer {
char *b;
size_t size;
size_t n;
lua_State *L;
char initb[LUAL_BUFFERSIZE];
} luaL_Buffer;
typedef struct luaL_Stream {
FILE *f;
lua_CFunction closef;
} luaL_Stream;
static void (*luaL_checkversion_) (lua_State *L, lua_Number ver);
static int (*luaL_getmetafield) (lua_State *L, int obj, const char *e);
static int (*luaL_callmeta) (lua_State *L, int obj, const char *e);
static const char *(*luaL_tolstring) (lua_State *L, int idx, size_t *len);
static int (*luaL_argerror) (lua_State *L, int numarg, const char *extramsg);
static const char *(*luaL_checklstring) (lua_State *L, int numArg, size_t *l);
static const char *(*luaL_optlstring) (lua_State *L, int numArg, const char *def, size_t *l);
static lua_Number (*luaL_checknumber) (lua_State *L, int numArg);
static lua_Number (*luaL_optnumber) (lua_State *L, int nArg, lua_Number def);
static lua_Integer (*luaL_checkinteger) (lua_State *L, int numArg);
static lua_Integer (*luaL_optinteger) (lua_State *L, int nArg, lua_Integer def);
static lua_Unsigned (*luaL_checkunsigned) (lua_State *L, int numArg);
static lua_Unsigned (*luaL_optunsigned) (lua_State *L, int numArg, lua_Unsigned def);
static void (*luaL_checkstack) (lua_State *L, int sz, const char *msg);
static void (*luaL_checktype) (lua_State *L, int narg, int t);
static void (*luaL_checkany) (lua_State *L, int narg);
static int (*luaL_newmetatable) (lua_State *L, const char *tname);
static void (*luaL_setmetatable) (lua_State *L, const char *tname);
static void *(*luaL_testudata) (lua_State *L, int ud, const char *tname);
static void *(*luaL_checkudata) (lua_State *L, int ud, const char *tname);
static void (*luaL_where) (lua_State *L, int lvl);
static int (*luaL_error) (lua_State *L, const char *fmt, ...);
static int (*luaL_checkoption) (lua_State *L, int narg, const char *def, const char *const lst[]);
static int (*luaL_fileresult) (lua_State *L, int stat, const char *fname);
static int (*luaL_execresult) (lua_State *L, int stat);
static int (*luaL_ref) (lua_State *L, int t);
static void (*luaL_unref) (lua_State *L, int t, int ref);
static int (*luaL_loadfilex) (lua_State *L, const char *filename, const char *mode);
static int (*luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, const char *name, const char *mode);
static int (*luaL_loadstring) (lua_State *L, const char *s);
static lua_State *(*luaL_newstate) (void);
static int (*luaL_len) (lua_State *L, int idx);
static const char *(*luaL_gsub) (lua_State *L, const char *s, const char *p, const char *r);
static void (*luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);
static int (*luaL_getsubtable) (lua_State *L, int idx, const char *fname);
static void (*luaL_traceback) (lua_State *L, lua_State *L1, const char *msg, int level);
static void (*luaL_requiref) (lua_State *L, const char *modname, lua_CFunction openf, int glb);
static void (*luaL_buffinit) (lua_State *L, luaL_Buffer *B);
static char *(*luaL_prepbuffsize) (luaL_Buffer *B, size_t sz);
static void (*luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l);
static void (*luaL_addstring) (luaL_Buffer *B, const char *s);
static void (*luaL_addvalue) (luaL_Buffer *B);
static void (*luaL_pushresult) (luaL_Buffer *B);
static void (*luaL_pushresultsize) (luaL_Buffer *B, size_t sz);
static char *(*luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz);
#define lauxlib_h
#define LUA_ERRFILE (LUA_ERRERR+1)
#define luaL_checkversion(L) luaL_checkversion_(L, LUA_VERSION_NUM)
#define LUA_NOREF (-2)
#define LUA_REFNIL (-1)
#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL)
#define luaL_newlibtable(L,l) lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)
#define luaL_newlib(L,l) (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
#define luaL_argcheck(L, cond,numarg,extramsg) ((void)((cond) || luaL_argerror(L, (numarg), (extramsg))))
#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL))
#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL))
#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))
#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d)))
#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n)))
#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d)))
#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i)))
#define luaL_dofile(L, fn) (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
#define luaL_dostring(L, s) (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))
#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))
#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL)
#define luaL_addchar(B,c) ((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), ((B)->b[(B)->n++] = (c)))
#define luaL_addsize(B,s) ((B)->n += (s))
#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
#define LUA_FILEHANDLE "FILE*"
static void __lite_xl_fallback_luaL_checkversion_ (lua_State *L, lua_Number ver) { fputs("warning: luaL_checkversion_ is a stub", stderr); }
static int __lite_xl_fallback_luaL_getmetafield (lua_State *L, int obj, const char *e) { fputs("warning: luaL_getmetafield is a stub", stderr); }
static int __lite_xl_fallback_luaL_callmeta (lua_State *L, int obj, const char *e) { fputs("warning: luaL_callmeta is a stub", stderr); }
static const char * __lite_xl_fallback_luaL_tolstring (lua_State *L, int idx, size_t *len) { fputs("warning: luaL_tolstring is a stub", stderr); }
static int __lite_xl_fallback_luaL_argerror (lua_State *L, int numarg, const char *extramsg) { fputs("warning: luaL_argerror is a stub", stderr); }
static const char * __lite_xl_fallback_luaL_checklstring (lua_State *L, int numArg, size_t *l) { fputs("warning: luaL_checklstring is a stub", stderr); }
static const char * __lite_xl_fallback_luaL_optlstring (lua_State *L, int numArg, const char *def, size_t *l) { fputs("warning: luaL_optlstring is a stub", stderr); }
static lua_Number __lite_xl_fallback_luaL_checknumber (lua_State *L, int numArg) { fputs("warning: luaL_checknumber is a stub", stderr); }
static lua_Number __lite_xl_fallback_luaL_optnumber (lua_State *L, int nArg, lua_Number def) { fputs("warning: luaL_optnumber is a stub", stderr); }
static lua_Integer __lite_xl_fallback_luaL_checkinteger (lua_State *L, int numArg) { fputs("warning: luaL_checkinteger is a stub", stderr); }
static lua_Integer __lite_xl_fallback_luaL_optinteger (lua_State *L, int nArg, lua_Integer def) { fputs("warning: luaL_optinteger is a stub", stderr); }
static lua_Unsigned __lite_xl_fallback_luaL_checkunsigned (lua_State *L, int numArg) { fputs("warning: luaL_checkunsigned is a stub", stderr); }
static lua_Unsigned __lite_xl_fallback_luaL_optunsigned (lua_State *L, int numArg, lua_Unsigned def) { fputs("warning: luaL_optunsigned is a stub", stderr); }
static void __lite_xl_fallback_luaL_checkstack (lua_State *L, int sz, const char *msg) { fputs("warning: luaL_checkstack is a stub", stderr); }
static void __lite_xl_fallback_luaL_checktype (lua_State *L, int narg, int t) { fputs("warning: luaL_checktype is a stub", stderr); }
static void __lite_xl_fallback_luaL_checkany (lua_State *L, int narg) { fputs("warning: luaL_checkany is a stub", stderr); }
static int __lite_xl_fallback_luaL_newmetatable (lua_State *L, const char *tname) { fputs("warning: luaL_newmetatable is a stub", stderr); }
static void __lite_xl_fallback_luaL_setmetatable (lua_State *L, const char *tname) { fputs("warning: luaL_setmetatable is a stub", stderr); }
static void * __lite_xl_fallback_luaL_testudata (lua_State *L, int ud, const char *tname) { fputs("warning: luaL_testudata is a stub", stderr); }
static void * __lite_xl_fallback_luaL_checkudata (lua_State *L, int ud, const char *tname) { fputs("warning: luaL_checkudata is a stub", stderr); }
static void __lite_xl_fallback_luaL_where (lua_State *L, int lvl) { fputs("warning: luaL_where is a stub", stderr); }
static int __lite_xl_fallback_luaL_error (lua_State *L, const char *fmt, ...) { fputs("warning: luaL_error is a stub", stderr); }
static int __lite_xl_fallback_luaL_checkoption (lua_State *L, int narg, const char *def, const char *const lst[]) { fputs("warning: luaL_checkoption is a stub", stderr); }
static int __lite_xl_fallback_luaL_fileresult (lua_State *L, int stat, const char *fname) { fputs("warning: luaL_fileresult is a stub", stderr); }
static int __lite_xl_fallback_luaL_execresult (lua_State *L, int stat) { fputs("warning: luaL_execresult is a stub", stderr); }
static int __lite_xl_fallback_luaL_ref (lua_State *L, int t) { fputs("warning: luaL_ref is a stub", stderr); }
static void __lite_xl_fallback_luaL_unref (lua_State *L, int t, int ref) { fputs("warning: luaL_unref is a stub", stderr); }
static int __lite_xl_fallback_luaL_loadfilex (lua_State *L, const char *filename, const char *mode) { fputs("warning: luaL_loadfilex is a stub", stderr); }
static int __lite_xl_fallback_luaL_loadbufferx (lua_State *L, const char *buff, size_t sz, const char *name, const char *mode) { fputs("warning: luaL_loadbufferx is a stub", stderr); }
static int __lite_xl_fallback_luaL_loadstring (lua_State *L, const char *s) { fputs("warning: luaL_loadstring is a stub", stderr); }
static lua_State * __lite_xl_fallback_luaL_newstate (void) { fputs("warning: luaL_newstate is a stub", stderr); }
static int __lite_xl_fallback_luaL_len (lua_State *L, int idx) { fputs("warning: luaL_len is a stub", stderr); }
static const char * __lite_xl_fallback_luaL_gsub (lua_State *L, const char *s, const char *p, const char *r) { fputs("warning: luaL_gsub is a stub", stderr); }
static void __lite_xl_fallback_luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { fputs("warning: luaL_setfuncs is a stub", stderr); }
static int __lite_xl_fallback_luaL_getsubtable (lua_State *L, int idx, const char *fname) { fputs("warning: luaL_getsubtable is a stub", stderr); }
static void __lite_xl_fallback_luaL_traceback (lua_State *L, lua_State *L1, const char *msg, int level) { fputs("warning: luaL_traceback is a stub", stderr); }
static void __lite_xl_fallback_luaL_requiref (lua_State *L, const char *modname, lua_CFunction openf, int glb) { fputs("warning: luaL_requiref is a stub", stderr); }
static void __lite_xl_fallback_luaL_buffinit (lua_State *L, luaL_Buffer *B) { fputs("warning: luaL_buffinit is a stub", stderr); }
static char * __lite_xl_fallback_luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { fputs("warning: luaL_prepbuffsize is a stub", stderr); }
static void __lite_xl_fallback_luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) { fputs("warning: luaL_addlstring is a stub", stderr); }
static void __lite_xl_fallback_luaL_addstring (luaL_Buffer *B, const char *s) { fputs("warning: luaL_addstring is a stub", stderr); }
static void __lite_xl_fallback_luaL_addvalue (luaL_Buffer *B) { fputs("warning: luaL_addvalue is a stub", stderr); }
static void __lite_xl_fallback_luaL_pushresult (luaL_Buffer *B) { fputs("warning: luaL_pushresult is a stub", stderr); }
static void __lite_xl_fallback_luaL_pushresultsize (luaL_Buffer *B, size_t sz) { fputs("warning: luaL_pushresultsize is a stub", stderr); }
static char * __lite_xl_fallback_luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz) { fputs("warning: luaL_buffinitsize is a stub", stderr); }
#define IMPORT_SYMBOL(name, ret, ...) name = (name = (ret (*) (__VA_ARGS__)) symbol(#name), name == NULL ? &__lite_xl_fallback_##name : name)
static void lite_xl_plugin_init(void *XL) {
void* (*symbol)(const char *) = (void* (*) (const char *)) XL;
IMPORT_SYMBOL(lua_newstate, lua_State *, lua_Alloc f, void *ud);
IMPORT_SYMBOL(lua_close, void , lua_State *L);
IMPORT_SYMBOL(lua_newthread, lua_State *, lua_State *L);
IMPORT_SYMBOL(lua_atpanic, lua_CFunction , lua_State *L, lua_CFunction panicf);
IMPORT_SYMBOL(lua_version, const lua_Number *, lua_State *L);
IMPORT_SYMBOL(lua_absindex, int , lua_State *L, int idx);
IMPORT_SYMBOL(lua_gettop, int , lua_State *L);
IMPORT_SYMBOL(lua_settop, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_pushvalue, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_remove, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_insert, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_replace, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_copy, void , lua_State *L, int fromidx, int toidx);
IMPORT_SYMBOL(lua_checkstack, int , lua_State *L, int sz);
IMPORT_SYMBOL(lua_xmove, void , lua_State *from, lua_State *to, int n);
IMPORT_SYMBOL(lua_isnumber, int , lua_State *L, int idx);
IMPORT_SYMBOL(lua_isstring, int , lua_State *L, int idx);
IMPORT_SYMBOL(lua_iscfunction, int , lua_State *L, int idx);
IMPORT_SYMBOL(lua_isuserdata, int , lua_State *L, int idx);
IMPORT_SYMBOL(lua_type, int , lua_State *L, int idx);
IMPORT_SYMBOL(lua_typename, const char *, lua_State *L, int tp);
IMPORT_SYMBOL(lua_tonumberx, lua_Number , lua_State *L, int idx, int *isnum);
IMPORT_SYMBOL(lua_tointegerx, lua_Integer , lua_State *L, int idx, int *isnum);
IMPORT_SYMBOL(lua_tounsignedx, lua_Unsigned , lua_State *L, int idx, int *isnum);
IMPORT_SYMBOL(lua_toboolean, int , lua_State *L, int idx);
IMPORT_SYMBOL(lua_tolstring, const char *, lua_State *L, int idx, size_t *len);
IMPORT_SYMBOL(lua_rawlen, size_t , lua_State *L, int idx);
IMPORT_SYMBOL(lua_tocfunction, lua_CFunction , lua_State *L, int idx);
IMPORT_SYMBOL(lua_touserdata, void *, lua_State *L, int idx);
IMPORT_SYMBOL(lua_tothread, lua_State *, lua_State *L, int idx);
IMPORT_SYMBOL(lua_topointer, const void *, lua_State *L, int idx);
IMPORT_SYMBOL(lua_arith, void , lua_State *L, int op);
IMPORT_SYMBOL(lua_rawequal, int , lua_State *L, int idx1, int idx2);
IMPORT_SYMBOL(lua_compare, int , lua_State *L, int idx1, int idx2, int op);
IMPORT_SYMBOL(lua_pushnil, void , lua_State *L);
IMPORT_SYMBOL(lua_pushnumber, void , lua_State *L, lua_Number n);
IMPORT_SYMBOL(lua_pushinteger, void , lua_State *L, lua_Integer n);
IMPORT_SYMBOL(lua_pushunsigned, void , lua_State *L, lua_Unsigned n);
IMPORT_SYMBOL(lua_pushlstring, const char *, lua_State *L, const char *s, size_t l);
IMPORT_SYMBOL(lua_pushstring, const char *, lua_State *L, const char *s);
IMPORT_SYMBOL(lua_pushvfstring, const char *, lua_State *L, const char *fmt, va_list argp);
IMPORT_SYMBOL(lua_pushfstring, const char *, lua_State *L, const char *fmt, ...);
IMPORT_SYMBOL(lua_pushcclosure, void , lua_State *L, lua_CFunction fn, int n);
IMPORT_SYMBOL(lua_pushboolean, void , lua_State *L, int b);
IMPORT_SYMBOL(lua_pushlightuserdata, void , lua_State *L, void *p);
IMPORT_SYMBOL(lua_pushthread, int , lua_State *L);
IMPORT_SYMBOL(lua_getglobal, void , lua_State *L, const char *var);
IMPORT_SYMBOL(lua_gettable, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_getfield, void , lua_State *L, int idx, const char *k);
IMPORT_SYMBOL(lua_rawget, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_rawgeti, void , lua_State *L, int idx, int n);
IMPORT_SYMBOL(lua_rawgetp, void , lua_State *L, int idx, const void *p);
IMPORT_SYMBOL(lua_createtable, void , lua_State *L, int narr, int nrec);
IMPORT_SYMBOL(lua_newuserdata, void *, lua_State *L, size_t sz);
IMPORT_SYMBOL(lua_getmetatable, int , lua_State *L, int objindex);
IMPORT_SYMBOL(lua_getuservalue, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_setglobal, void , lua_State *L, const char *var);
IMPORT_SYMBOL(lua_settable, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_setfield, void , lua_State *L, int idx, const char *k);
IMPORT_SYMBOL(lua_rawset, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_rawseti, void , lua_State *L, int idx, int n);
IMPORT_SYMBOL(lua_rawsetp, void , lua_State *L, int idx, const void *p);
IMPORT_SYMBOL(lua_setmetatable, int , lua_State *L, int objindex);
IMPORT_SYMBOL(lua_setuservalue, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_callk, void , lua_State *L, int nargs, int nresults, int ctx, lua_CFunction k);
IMPORT_SYMBOL(lua_getctx, int , lua_State *L, int *ctx);
IMPORT_SYMBOL(lua_pcallk, int , lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k);
IMPORT_SYMBOL(lua_load, int , lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode);
IMPORT_SYMBOL(lua_dump, int , lua_State *L, lua_Writer writer, void *data);
IMPORT_SYMBOL(lua_yieldk, int , lua_State *L, int nresults, int ctx, lua_CFunction k);
IMPORT_SYMBOL(lua_resume, int , lua_State *L, lua_State *from, int narg);
IMPORT_SYMBOL(lua_status, int , lua_State *L);
IMPORT_SYMBOL(lua_gc, int , lua_State *L, int what, int data);
IMPORT_SYMBOL(lua_error, int , lua_State *L);
IMPORT_SYMBOL(lua_next, int , lua_State *L, int idx);
IMPORT_SYMBOL(lua_concat, void , lua_State *L, int n);
IMPORT_SYMBOL(lua_len, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_getallocf, lua_Alloc , lua_State *L, void **ud);
IMPORT_SYMBOL(lua_setallocf, void , lua_State *L, lua_Alloc f, void *ud);
IMPORT_SYMBOL(lua_getstack, int , lua_State *L, int level, lua_Debug *ar);
IMPORT_SYMBOL(lua_getinfo, int , lua_State *L, const char *what, lua_Debug *ar);
IMPORT_SYMBOL(lua_getlocal, const char *, lua_State *L, const lua_Debug *ar, int n);
IMPORT_SYMBOL(lua_setlocal, const char *, lua_State *L, const lua_Debug *ar, int n);
IMPORT_SYMBOL(lua_getupvalue, const char *, lua_State *L, int funcindex, int n);
IMPORT_SYMBOL(lua_setupvalue, const char *, lua_State *L, int funcindex, int n);
IMPORT_SYMBOL(lua_upvalueid, void *, lua_State *L, int fidx, int n);
IMPORT_SYMBOL(lua_upvaluejoin, void , lua_State *L, int fidx1, int n1, int fidx2, int n2);
IMPORT_SYMBOL(lua_sethook, int , lua_State *L, lua_Hook func, int mask, int count);
IMPORT_SYMBOL(lua_gethook, lua_Hook , lua_State *L);
IMPORT_SYMBOL(lua_gethookmask, int , lua_State *L);
IMPORT_SYMBOL(lua_gethookcount, int , lua_State *L);
IMPORT_SYMBOL(luaL_checkversion_, void , lua_State *L, lua_Number ver);
IMPORT_SYMBOL(luaL_getmetafield, int , lua_State *L, int obj, const char *e);
IMPORT_SYMBOL(luaL_callmeta, int , lua_State *L, int obj, const char *e);
IMPORT_SYMBOL(luaL_tolstring, const char *, lua_State *L, int idx, size_t *len);
IMPORT_SYMBOL(luaL_argerror, int , lua_State *L, int numarg, const char *extramsg);
IMPORT_SYMBOL(luaL_checklstring, const char *, lua_State *L, int numArg, size_t *l);
IMPORT_SYMBOL(luaL_optlstring, const char *, lua_State *L, int numArg, const char *def, size_t *l);
IMPORT_SYMBOL(luaL_checknumber, lua_Number , lua_State *L, int numArg);
IMPORT_SYMBOL(luaL_optnumber, lua_Number , lua_State *L, int nArg, lua_Number def);
IMPORT_SYMBOL(luaL_checkinteger, lua_Integer , lua_State *L, int numArg);
IMPORT_SYMBOL(luaL_optinteger, lua_Integer , lua_State *L, int nArg, lua_Integer def);
IMPORT_SYMBOL(luaL_checkunsigned, lua_Unsigned , lua_State *L, int numArg);
IMPORT_SYMBOL(luaL_optunsigned, lua_Unsigned , lua_State *L, int numArg, lua_Unsigned def);
IMPORT_SYMBOL(luaL_checkstack, void , lua_State *L, int sz, const char *msg);
IMPORT_SYMBOL(luaL_checktype, void , lua_State *L, int narg, int t);
IMPORT_SYMBOL(luaL_checkany, void , lua_State *L, int narg);
IMPORT_SYMBOL(luaL_newmetatable, int , lua_State *L, const char *tname);
IMPORT_SYMBOL(luaL_setmetatable, void , lua_State *L, const char *tname);
IMPORT_SYMBOL(luaL_testudata, void *, lua_State *L, int ud, const char *tname);
IMPORT_SYMBOL(luaL_checkudata, void *, lua_State *L, int ud, const char *tname);
IMPORT_SYMBOL(luaL_where, void , lua_State *L, int lvl);
IMPORT_SYMBOL(luaL_error, int , lua_State *L, const char *fmt, ...);
IMPORT_SYMBOL(luaL_checkoption, int , lua_State *L, int narg, const char *def, const char *const lst[]);
IMPORT_SYMBOL(luaL_fileresult, int , lua_State *L, int stat, const char *fname);
IMPORT_SYMBOL(luaL_execresult, int , lua_State *L, int stat);
IMPORT_SYMBOL(luaL_ref, int , lua_State *L, int t);
IMPORT_SYMBOL(luaL_unref, void , lua_State *L, int t, int ref);
IMPORT_SYMBOL(luaL_loadfilex, int , lua_State *L, const char *filename, const char *mode);
IMPORT_SYMBOL(luaL_loadbufferx, int , lua_State *L, const char *buff, size_t sz, const char *name, const char *mode);
IMPORT_SYMBOL(luaL_loadstring, int , lua_State *L, const char *s);
IMPORT_SYMBOL(luaL_newstate, lua_State *, void);
IMPORT_SYMBOL(luaL_len, int , lua_State *L, int idx);
IMPORT_SYMBOL(luaL_gsub, const char *, lua_State *L, const char *s, const char *p, const char *r);
IMPORT_SYMBOL(luaL_setfuncs, void , lua_State *L, const luaL_Reg *l, int nup);
IMPORT_SYMBOL(luaL_getsubtable, int , lua_State *L, int idx, const char *fname);
IMPORT_SYMBOL(luaL_traceback, void , lua_State *L, lua_State *L1, const char *msg, int level);
IMPORT_SYMBOL(luaL_requiref, void , lua_State *L, const char *modname, lua_CFunction openf, int glb);
IMPORT_SYMBOL(luaL_buffinit, void , lua_State *L, luaL_Buffer *B);
IMPORT_SYMBOL(luaL_prepbuffsize, char *, luaL_Buffer *B, size_t sz);
IMPORT_SYMBOL(luaL_addlstring, void , luaL_Buffer *B, const char *s, size_t l);
IMPORT_SYMBOL(luaL_addstring, void , luaL_Buffer *B, const char *s);
IMPORT_SYMBOL(luaL_addvalue, void , luaL_Buffer *B);
IMPORT_SYMBOL(luaL_pushresult, void , luaL_Buffer *B);
IMPORT_SYMBOL(luaL_pushresultsize, void , luaL_Buffer *B, size_t sz);
IMPORT_SYMBOL(luaL_buffinitsize, char *, lua_State *L, luaL_Buffer *B, size_t sz);
}
#endif

View File

@ -22,8 +22,6 @@ Various scripts and configurations used to configure, build, and package Lite XL
and run Lite XL, mainly useful for CI and documentation purpose.
Preferably not to be used in user systems.
- **fontello-config.json**: Used by the icons generator.
- **generate_header.sh**: Generates a header file for native plugin API
- **keymap-generator**: Generates a JSON file containing the keymap
[1]: https://github.com/LinusU/node-appdmg
[2]: https://docs.appimage.org/

View File

@ -1,144 +0,0 @@
#!/bin/bash
##### CONFIG
# symbols to ignore
IGNORE_SYM='luaL_pushmodule\|luaL_openlib'
##### CONFIG
# https://stackoverflow.com/a/13062682
uncomment() {
[ $# -eq 2 ] && arg="$1" || arg=""
eval file="\$$#"
sed 's/a/aA/g; s/__/aB/g; s/#/aC/g' "$file" | \
gcc -P -E $arg - | \
sed 's/aC/#/g; s/aB/__/g; s/aA/a/g'
}
# this is the magic that turns multiline statements into
# single line statements
# LITERALLY DOES NOT WORK WITH PREPROCESSOR
onelineize() {
grep -v '^#' | sed -e ':r;$!{N;br};s/\([^{;]\)\n\s*/\1 /g'
}
discard_preprocessors() {
grep -v '#\(include\|if\|endif\)'
}
# sed regex for extracting data from function signature
# if this isn't regex, idk what is
# LUA_API (return type as \2) (function name as \3) (args as \4)
sym_regex='^LUA\(LIB\)\?_API\s\+\([^(]\+\)\s*(\([^)]\+\))\s\+(\([^)]\+\));'
# get funcptr declarations
ptrize() {
grep '^LUA' | grep -v "$IGNORE_SYM" | sed -e "s/$sym_regex/static\t\2(*\3)\t(\4);/"
}
# create a stub function that warns user when calling it
makestub() {
grep '^LUA' | grep -v "$IGNORE_SYM" | sed -e "s/$sym_regex/static\t\2\t__lite_xl_fallback_\3\t(\4) { fputs(\"warning: \3 is a stub\", stderr); }/"
}
import_sym() {
grep '^LUA' | grep -v "$IGNORE_SYM" | sed -e "s/$sym_regex/\tIMPORT_SYMBOL(\3, \2, \4);/"
}
decl() {
echo "/** $(basename "$1") **/"
echo
header="$(uncomment $1 | discard_preprocessors)"
header1="$(onelineize <<< "$header")"
# typedef
grep -v '^\(LUA\|#\|extern\)' <<< "$header1"
# funcptrs
ptrize <<< "$header1"
# defines
(grep '^#' | grep -v "$IGNORE_SYM") <<< "$header"
# stubs
makestub <<< "$header1"
}
decl_import() {
uncomment $1 | onelineize | import_sym
}
generate_header() {
local LUA_PATH="$1"
echo "#ifndef LITE_XL_PLUGIN_API"
echo "#define LITE_XL_PLUGIN_API"
echo "/**"
echo "The lite_xl plugin API is quite simple. Any shared library can be a plugin file, so long"
echo "as it has an entrypoint that looks like the following, where xxxxx is the plugin name:"
echo '#include "lite_xl_plugin_api.h"'
echo "int lua_open_lite_xl_xxxxx(lua_State* L, void* XL) {"
echo " lite_xl_plugin_init(XL);"
echo " ..."
echo " return 1;"
echo "}"
echo "In linux, to compile this file, you'd do: 'gcc -o xxxxx.so -shared xxxxx.c'. Simple!"
echo "Due to the way the API is structured, you *should not* link or include lua libraries."
echo "This file was automatically generated. DO NOT MODIFY DIRECTLY."
echo "**/"
echo
echo
echo "#include <stdarg.h>"
echo "#include <stdio.h> // for BUFSIZ? this is kinda weird"
echo
echo "/** luaconf.h **/"
echo
uncomment "$LUA_PATH/luaconf.h"
echo
decl "$LUA_PATH/lua.h"
echo
decl "$LUA_PATH/lauxlib.h"
echo
echo "#define IMPORT_SYMBOL(name, ret, ...) name = (name = (ret (*) (__VA_ARGS__)) symbol(#name), name == NULL ? &__lite_xl_fallback_##name : name)"
echo "static void lite_xl_plugin_init(void *XL) {"
echo -e "\tvoid* (*symbol)(const char *) = (void* (*) (const char *)) XL;"
decl_import "$LUA_PATH/lua.h"
decl_import "$LUA_PATH/lauxlib.h"
echo "}"
echo "#endif"
}
show_help() {
echo -e "Usage: $0 <OPTIONS> prefix"
echo
echo -e "Available options:"
echo -e "-p\t--prefix\tSet prefix (where to find lua.h and lauxlib.h)"
}
main() {
local prefix=""
for i in "$@"; do
case $i in
-h|--help)
show_help
exit 0
;;
-p|--prefix)
prefix="$2"
shift
shift
;;
*)
;;
esac
done
generate_header "$prefix"
}
main "$@"
# create a stub function that warns user when calling it

View File

@ -1,88 +1,88 @@
#define MyAppName "Lite XL"
#define MyAppVersion "@PROJECT_VERSION@"
#define MyAppPublisher "Lite XL Team"
#define MyAppURL "https://lite-xl.github.io"
#define MyAppExeName "lite-xl.exe"
#define BuildDir "@PROJECT_BUILD_DIR@"
#define SourceDir "@PROJECT_SOURCE_DIR@"
; Use /dArch option to create a setup for a different architecture, e.g.:
; iscc /dArch=x86 innosetup.iss
#ifndef Arch
#define Arch "x64"
#endif
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; To generate a new GUID, click Tools | Generate GUID inside the InnoSetup IDE.
AppId={{06761240-D97C-43DE-B9ED-C15F765A2D65}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
#if Arch=="x64"
ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64
#define ArchInternal "x86_64"
#else
#define ArchInternal "i686"
#endif
AllowNoIcons=yes
Compression=lzma
SolidCompression=yes
DefaultDirName={autopf}/{#MyAppName}
DefaultGroupName={#MyAppPublisher}
UninstallFilesDir={app}
; Uncomment the following line to run in non administrative install mode
; (install for current user only.)
;PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
; The [Icons] "quicklaunchicon" entry uses {userappdata}
; but its [Tasks] entry has a proper IsAdminInstallMode Check.
UsedUserAreasWarning=no
OutputDir=.
OutputBaseFilename=LiteXL-{#MyAppVersion}-{#ArchInternal}-setup
;DisableDirPage=yes
;DisableProgramGroupPage=yes
LicenseFile={#SourceDir}/LICENSE
SetupIconFile={#SourceDir}/resources/icons/icon.ico
WizardImageFile="{#SourceDir}/scripts/innosetup/wizard-modern-image.bmp"
WizardSmallImageFile="{#SourceDir}/scripts/innosetup/litexl-55px.bmp"
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
Name: "portablemode"; Description: "Portable Mode"; Flags: unchecked
[Files]
Source: "{#BuildDir}/src/lite-xl.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#BuildDir}/mingwLibs{#Arch}/*"; DestDir: "{app}"; Flags: ignoreversion ; Check: DirExists(ExpandConstant('{#BuildDir}/mingwLibs{#Arch}'))
Source: "{#SourceDir}/data/*"; DestDir: "{app}/data"; Flags: ignoreversion recursesubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: not WizardIsTaskSelected('portablemode')
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"; Check: not WizardIsTaskSelected('portablemode')
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon; Check: not WizardIsTaskSelected('portablemode')
; Name: "{usersendto}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
[Run]
Filename: "{app}/{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
[Setup]
Uninstallable=not WizardIsTaskSelected('portablemode')
#define MyAppName "Lite XL"
#define MyAppVersion "@PROJECT_VERSION@"
#define MyAppPublisher "Lite XL Team"
#define MyAppURL "https://lite-xl.github.io"
#define MyAppExeName "lite-xl.exe"
#define BuildDir "@PROJECT_BUILD_DIR@"
#define SourceDir "@PROJECT_SOURCE_DIR@"
; Use /dArch option to create a setup for a different architecture, e.g.:
; iscc /dArch=x86 innosetup.iss
#ifndef Arch
#define Arch "x64"
#endif
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; To generate a new GUID, click Tools | Generate GUID inside the InnoSetup IDE.
AppId={{06761240-D97C-43DE-B9ED-C15F765A2D65}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
#if Arch=="x64"
ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64
#define ArchInternal "x86_64"
#else
#define ArchInternal "i686"
#endif
AllowNoIcons=yes
Compression=lzma
SolidCompression=yes
DefaultDirName={autopf}/{#MyAppName}
DefaultGroupName={#MyAppPublisher}
UninstallFilesDir={app}
; Uncomment the following line to run in non administrative install mode
; (install for current user only.)
;PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
; The [Icons] "quicklaunchicon" entry uses {userappdata}
; but its [Tasks] entry has a proper IsAdminInstallMode Check.
UsedUserAreasWarning=no
OutputDir=.
OutputBaseFilename=LiteXL-{#MyAppVersion}-{#ArchInternal}-setup
;DisableDirPage=yes
;DisableProgramGroupPage=yes
LicenseFile={#SourceDir}/LICENSE
SetupIconFile={#SourceDir}/resources/icons/icon.ico
WizardImageFile="{#SourceDir}/scripts/innosetup/wizard-modern-image.bmp"
WizardSmallImageFile="{#SourceDir}/scripts/innosetup/litexl-55px.bmp"
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
Name: "portablemode"; Description: "Portable Mode"; Flags: unchecked
[Files]
Source: "{#BuildDir}/src/lite-xl.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#BuildDir}/mingwLibs{#Arch}/*"; DestDir: "{app}"; Flags: ignoreversion ; Check: DirExists(ExpandConstant('{#BuildDir}/mingwLibs{#Arch}'))
Source: "{#SourceDir}/data/*"; DestDir: "{app}/data"; Flags: ignoreversion recursesubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: not WizardIsTaskSelected('portablemode')
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"; Check: not WizardIsTaskSelected('portablemode')
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon; Check: not WizardIsTaskSelected('portablemode')
; Name: "{usersendto}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
[Run]
Filename: "{app}/{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
[Setup]
Uninstallable=not WizardIsTaskSelected('portablemode')

View File

@ -1,714 +0,0 @@
-- Module options:
local always_try_using_lpeg = true
local register_global_module_table = false
local global_module_name = 'json'
--[==[
David Kolf's JSON module for Lua 5.1/5.2
Version 2.5
For the documentation see the corresponding readme.txt or visit
<http://dkolf.de/src/dkjson-lua.fsl/>.
You can contact the author by sending an e-mail to 'david' at the
domain 'dkolf.de'.
Copyright (C) 2010-2013 David Heiko Kolf
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.
--]==]
-- global dependencies:
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
local error, require, pcall, select = error, require, pcall, select
local floor, huge = math.floor, math.huge
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
string.rep, string.gsub, string.sub, string.byte, string.char,
string.find, string.len, string.format
local strmatch = string.match
local concat = table.concat
local json = { version = "dkjson 2.5" }
if register_global_module_table then
_G[global_module_name] = json
end
local _ENV = nil -- blocking globals in Lua 5.2
pcall (function()
-- Enable access to blocked metatables.
-- Don't worry, this module doesn't change anything in them.
local debmeta = require "debug".getmetatable
if debmeta then getmetatable = debmeta end
end)
json.null = setmetatable ({}, {
__tojson = function () return "null" end
})
local function isarray (tbl)
local max, n, arraylen = 0, 0, 0
for k,v in pairs (tbl) do
if k == 'n' and type(v) == 'number' then
arraylen = v
if v > max then
max = v
end
else
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
return false
end
if k > max then
max = k
end
n = n + 1
end
end
if max > 10 and max > arraylen and max > n * 2 then
return false -- don't create an array with too many holes
end
return true, max
end
local escapecodes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
}
local function escapeutf8 (uchar)
local value = escapecodes[uchar]
if value then
return value
end
local a, b, c, d = strbyte (uchar, 1, 4)
a, b, c, d = a or 0, b or 0, c or 0, d or 0
if a <= 0x7f then
value = a
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
value = (a - 0xc0) * 0x40 + b - 0x80
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
else
return ""
end
if value <= 0xffff then
return strformat ("\\u%.4x", value)
elseif value <= 0x10ffff then
-- encode as UTF-16 surrogate pair
value = value - 0x10000
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
else
return ""
end
end
local function fsub (str, pattern, repl)
-- gsub always builds a new string in a buffer, even when no match
-- exists. First using find should be more efficient when most strings
-- don't contain the pattern.
if strfind (str, pattern) then
return gsub (str, pattern, repl)
else
return str
end
end
local function quotestring (value)
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
if strfind (value, "[\194\216\220\225\226\239]") then
value = fsub (value, "\194[\128-\159\173]", escapeutf8)
value = fsub (value, "\216[\128-\132]", escapeutf8)
value = fsub (value, "\220\143", escapeutf8)
value = fsub (value, "\225\158[\180\181]", escapeutf8)
value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
value = fsub (value, "\226\129[\160-\175]", escapeutf8)
value = fsub (value, "\239\187\191", escapeutf8)
value = fsub (value, "\239\191[\176-\191]", escapeutf8)
end
return "\"" .. value .. "\""
end
json.quotestring = quotestring
local function replace(str, o, n)
local i, j = strfind (str, o, 1, true)
if i then
return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
else
return str
end
end
-- locale independent num2str and str2num functions
local decpoint, numfilter
local function updatedecpoint ()
decpoint = strmatch(tostring(0.5), "([^05+])")
-- build a filter that can be used to remove group separators
numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
end
updatedecpoint()
local function num2str (num)
return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
end
local function str2num (str)
local num = tonumber(replace(str, ".", decpoint))
if not num then
updatedecpoint()
num = tonumber(replace(str, ".", decpoint))
end
return num
end
local function addnewline2 (level, buffer, buflen)
buffer[buflen+1] = "\n"
buffer[buflen+2] = strrep (" ", level)
buflen = buflen + 2
return buflen
end
function json.addnewline (state)
if state.indent then
state.bufferlen = addnewline2 (state.level or 0,
state.buffer, state.bufferlen or #(state.buffer))
end
end
local encode2 -- forward declaration
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
local kt = type (key)
if kt ~= 'string' and kt ~= 'number' then
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
end
if prev then
buflen = buflen + 1
buffer[buflen] = ","
end
if indent then
buflen = addnewline2 (level, buffer, buflen)
end
buffer[buflen+1] = quotestring (key)
buffer[buflen+2] = ":"
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
end
local function appendcustom(res, buffer, state)
local buflen = state.bufferlen
if type (res) == 'string' then
buflen = buflen + 1
buffer[buflen] = res
end
return buflen
end
local function exception(reason, value, state, buffer, buflen, defaultmessage)
defaultmessage = defaultmessage or reason
local handler = state.exception
if not handler then
return nil, defaultmessage
else
state.bufferlen = buflen
local ret, msg = handler (reason, value, state, defaultmessage)
if not ret then return nil, msg or defaultmessage end
return appendcustom(ret, buffer, state)
end
end
function json.encodeexception(reason, value, state, defaultmessage)
return quotestring("<" .. defaultmessage .. ">")
end
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
local valtype = type (value)
local valmeta = getmetatable (value)
valmeta = type (valmeta) == 'table' and valmeta -- only tables
local valtojson = valmeta and valmeta.__tojson
if valtojson then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
state.bufferlen = buflen
local ret, msg = valtojson (value, state)
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
tables[value] = nil
buflen = appendcustom(ret, buffer, state)
elseif value == nil then
buflen = buflen + 1
buffer[buflen] = "null"
elseif valtype == 'number' then
local s
if value ~= value or value >= huge or -value >= huge then
-- This is the behaviour of the original JSON implementation.
s = "null"
else
s = num2str (value)
end
buflen = buflen + 1
buffer[buflen] = s
elseif valtype == 'boolean' then
buflen = buflen + 1
buffer[buflen] = value and "true" or "false"
elseif valtype == 'string' then
buflen = buflen + 1
buffer[buflen] = quotestring (value)
elseif valtype == 'table' then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
level = level + 1
local isa, n = isarray (value)
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
isa = false
end
local msg
if isa then -- JSON array
buflen = buflen + 1
buffer[buflen] = "["
for i = 1, n do
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
if i < n then
buflen = buflen + 1
buffer[buflen] = ","
end
end
buflen = buflen + 1
buffer[buflen] = "]"
else -- JSON object
local prev = false
buflen = buflen + 1
buffer[buflen] = "{"
local order = valmeta and valmeta.__jsonorder or globalorder
if order then
local used = {}
n = #order
for i = 1, n do
local k = order[i]
local v = value[k]
if v then
used[k] = true
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
prev = true -- add a seperator before the next element
end
end
for k,v in pairs (value) do
if not used[k] then
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
else -- unordered
for k,v in pairs (value) do
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
if indent then
buflen = addnewline2 (level - 1, buffer, buflen)
end
buflen = buflen + 1
buffer[buflen] = "}"
end
tables[value] = nil
else
return exception ('unsupported type', value, state, buffer, buflen,
"type '" .. valtype .. "' is not supported by JSON.")
end
return buflen
end
function json.encode (value, state)
state = state or {}
local oldbuffer = state.buffer
local buffer = oldbuffer or {}
state.buffer = buffer
updatedecpoint()
local ret, msg = encode2 (value, state.indent, state.level or 0,
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
if not ret then
error (msg, 2)
elseif oldbuffer == buffer then
state.bufferlen = ret
return true
else
state.bufferlen = nil
state.buffer = nil
return concat (buffer)
end
end
local function loc (str, where)
local line, pos, linepos = 1, 1, 0
while true do
pos = strfind (str, "\n", pos, true)
if pos and pos < where then
line = line + 1
linepos = pos
pos = pos + 1
else
break
end
end
return "line " .. line .. ", column " .. (where - linepos)
end
local function unterminated (str, what, where)
return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
end
local function scanwhite (str, pos)
while true do
pos = strfind (str, "%S", pos)
if not pos then return nil end
local sub2 = strsub (str, pos, pos + 1)
if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
-- UTF-8 Byte Order Mark
pos = pos + 3
elseif sub2 == "//" then
pos = strfind (str, "[\n\r]", pos + 2)
if not pos then return nil end
elseif sub2 == "/*" then
pos = strfind (str, "*/", pos + 2)
if not pos then return nil end
pos = pos + 2
else
return pos
end
end
end
local escapechars = {
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
}
local function unichar (value)
if value < 0 then
return nil
elseif value <= 0x007f then
return strchar (value)
elseif value <= 0x07ff then
return strchar (0xc0 + floor(value/0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0xffff then
return strchar (0xe0 + floor(value/0x1000),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0x10ffff then
return strchar (0xf0 + floor(value/0x40000),
0x80 + (floor(value/0x1000) % 0x40),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
else
return nil
end
end
local function scanstring (str, pos)
local lastpos = pos + 1
local buffer, n = {}, 0
while true do
local nextpos = strfind (str, "[\"\\]", lastpos)
if not nextpos then
return unterminated (str, "string", pos)
end
if nextpos > lastpos then
n = n + 1
buffer[n] = strsub (str, lastpos, nextpos - 1)
end
if strsub (str, nextpos, nextpos) == "\"" then
lastpos = nextpos + 1
break
else
local escchar = strsub (str, nextpos + 1, nextpos + 1)
local value
if escchar == "u" then
value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
if value then
local value2
if 0xD800 <= value and value <= 0xDBff then
-- we have the high surrogate of UTF-16. Check if there is a
-- low surrogate escaped nearby to combine them.
if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
else
value2 = nil -- in case it was out of range for a low surrogate
end
end
end
value = value and unichar (value)
if value then
if value2 then
lastpos = nextpos + 12
else
lastpos = nextpos + 6
end
end
end
end
if not value then
value = escapechars[escchar] or escchar
lastpos = nextpos + 2
end
n = n + 1
buffer[n] = value
end
end
if n == 1 then
return buffer[1], lastpos
elseif n > 1 then
return concat (buffer), lastpos
else
return "", lastpos
end
end
local scanvalue -- forward declaration
local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
local len = strlen (str)
local tbl, n = {}, 0
local pos = startpos + 1
if what == 'object' then
setmetatable (tbl, objectmeta)
else
setmetatable (tbl, arraymeta)
end
while true do
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
local char = strsub (str, pos, pos)
if char == closechar then
return tbl, pos + 1
end
local val1, err
val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
if char == ":" then
if val1 == nil then
return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
end
pos = scanwhite (str, pos + 1)
if not pos then return unterminated (str, what, startpos) end
local val2
val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
tbl[val1] = val2
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
else
n = n + 1
tbl[n] = val1
end
if char == "," then
pos = pos + 1
end
end
end
scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
pos = pos or 1
pos = scanwhite (str, pos)
if not pos then
return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
end
local char = strsub (str, pos, pos)
if char == "{" then
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
elseif char == "[" then
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
elseif char == "\"" then
return scanstring (str, pos)
else
local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
if pstart then
local number = str2num (strsub (str, pstart, pend))
if number then
return number, pend + 1
end
end
pstart, pend = strfind (str, "^%a%w*", pos)
if pstart then
local name = strsub (str, pstart, pend)
if name == "true" then
return true, pend + 1
elseif name == "false" then
return false, pend + 1
elseif name == "null" then
return nullval, pend + 1
end
end
return nil, pos, "no valid JSON value at " .. loc (str, pos)
end
end
local function optionalmetatables(...)
if select("#", ...) > 0 then
return ...
else
return {__jsontype = 'object'}, {__jsontype = 'array'}
end
end
function json.decode (str, pos, nullval, ...)
local objectmeta, arraymeta = optionalmetatables(...)
return scanvalue (str, pos, nullval, objectmeta, arraymeta)
end
function json.use_lpeg ()
local g = require ("lpeg")
if g.version() == "0.11" then
error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
end
local pegmatch = g.match
local P, S, R = g.P, g.S, g.R
local function ErrorCall (str, pos, msg, state)
if not state.msg then
state.msg = msg .. " at " .. loc (str, pos)
state.pos = pos
end
return false
end
local function Err (msg)
return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
end
local SingleLineComment = P"//" * (1 - S"\n\r")^0
local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
local PlainChar = 1 - S"\"\\\n\r"
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
local HexDigit = R("09", "af", "AF")
local function UTF16Surrogate (match, pos, high, low)
high, low = tonumber (high, 16), tonumber (low, 16)
if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
else
return false
end
end
local function UTF16BMP (hex)
return unichar (tonumber (hex, 16))
end
local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
local Char = UnicodeEscape + EscapeSequence + PlainChar
local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string")
local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
local Fractal = P"." * R"09"^0
local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
local SimpleValue = Number + String + Constant
local ArrayContent, ObjectContent
-- The functions parsearray and parseobject parse only a single value/pair
-- at a time and store them directly to avoid hitting the LPeg limits.
local function parsearray (str, pos, nullval, state)
local obj, cont
local npos
local t, nt = {}, 0
repeat
obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
if not npos then break end
pos = npos
nt = nt + 1
t[nt] = obj
until cont == 'last'
return pos, setmetatable (t, state.arraymeta)
end
local function parseobject (str, pos, nullval, state)
local obj, key, cont
local npos
local t = {}
repeat
key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
if not npos then break end
pos = npos
t[key] = obj
until cont == 'last'
return pos, setmetatable (t, state.objectmeta)
end
local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
local Value = Space * (Array + Object + SimpleValue)
local ExpectedValue = Value + Space * Err "value expected"
ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
local DecodeValue = ExpectedValue * g.Cp ()
function json.decode (str, pos, nullval, ...)
local state = {}
state.objectmeta, state.arraymeta = optionalmetatables(...)
local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
if state.msg then
return nil, state.pos, state.msg
else
return obj, retpos
end
end
-- use this function only once:
json.use_lpeg = function () return json end
json.using_lpeg = true
return json -- so you can get the module using json = require "dkjson".use_lpeg()
end
if always_try_using_lpeg then
pcall (json.use_lpeg)
end
return json

View File

@ -1,75 +0,0 @@
#!/usr/bin/env lua
local dkjson = require "dkjson"
local function load_keymap(target, target_map, macos)
_G.MACOS = macos
package.loaded["core.keymap"] = nil
local keymap = require "core.keymap"
if target then
keymap.map = {}
require(target)
end
target_map = target_map or {}
-- keymap.reverse_map does not do this?
for key, actions in pairs(keymap.map) do
for _, action in ipairs(actions) do
target_map[action] = target_map[action] or {}
table.insert(target_map[action], key)
end
end
return target_map
end
local function normalize(map)
local result = {}
for action, keys in pairs(map) do
local uniq = {}
local r = { combination = {}, action = action }
for _, v in ipairs(keys) do
if not uniq[v] then
uniq[v] = true
r.combination[#r.combination+1] = v
end
end
result[#result+1] = r
end
table.sort(result, function(a, b) return a.action < b.action end)
return result
end
local function process_module(mod, filename)
local map = {}
load_keymap(mod, map)
load_keymap(mod, map, true)
map = normalize(map)
local f = assert(io.open(filename, "wb"))
f:write(dkjson.encode(map, { indent = true }))
f:close()
end
print("Warning: this is not guaranteed to work outside lite-xl's own keymap. Proceed with caution")
local LITE_ROOT = arg[1]
if not LITE_ROOT then
error("LITE_ROOT is not given")
end
package.path = package.path .. ";" .. LITE_ROOT .. "/?.lua;" .. LITE_ROOT .. "/?/init.lua"
-- fix core.command (because we don't want load the entire thing)
package.loaded["core.command"] = {}
if #arg > 1 then
for i = 2, #arg do
process_module(arg[i], arg[i] .. ".json")
print(string.format("Exported keymap in %q.", arg[i]))
end
else
process_module(nil, "core.keymap.json")
print("Exported the default keymap.")
end

View File

@ -20,7 +20,7 @@ show_help() {
echo "-h --help Show this help and exit."
echo "-p --prefix PREFIX Install directory prefix. Default: '/'."
echo "-v --version VERSION Sets the version on the package name."
echo " --addons Install 3rd party addons (currently Lite XL colors)."
echo " --addons Install 3rd party addons (currently RXI colors)."
echo " --debug Debug this script."
echo "-A --appimage Create an AppImage (Linux only)."
echo "-B --binary Create a normal / portable package or macOS bundle,"
@ -45,13 +45,13 @@ install_addons() {
# Copy third party color themes
curl --insecure \
-L "https://github.com/lite-xl/lite-xl-colors/archive/master.zip" \
-o "${build_dir}/lite-xl-colors.zip"
-L "https://github.com/rxi/lite-colors/archive/master.zip" \
-o "${build_dir}/rxi-lite-colors.zip"
mkdir -p "${build_dir}/third/data/colors"
unzip "${build_dir}/lite-xl-colors.zip" -d "${build_dir}"
mv "${build_dir}/lite-xl-colors-master/colors" "${build_dir}/third/data"
rm -rf "${build_dir}/lite-xl-colors-master"
unzip "${build_dir}/rxi-lite-colors.zip" -d "${build_dir}"
mv "${build_dir}/lite-colors-master/colors" "${build_dir}/third/data"
rm -rf "${build_dir}/lite-colors-master"
for module_name in colors; do
cp -r "${build_dir}/third/data/$module_name" "${data_dir}"
@ -186,7 +186,7 @@ main() {
rm -rf "${dest_dir}"
DESTDIR="$(pwd)/${dest_dir}" meson install --skip-subprojects -C "${build_dir}"
DESTDIR="$(pwd)/${dest_dir}" meson install -C "${build_dir}"
local data_dir="$(pwd)/${dest_dir}/data"
local exe_file="$(pwd)/${dest_dir}/lite-xl"

View File

@ -15,12 +15,12 @@ copy_directory_from_repo () {
lite_copy_third_party_modules () {
local build="$1"
curl --retry 5 --retry-delay 3 --insecure -L "https://github.com/lite-xl/lite-xl-colors/archive/master.zip" -o "$build/lite-xl-colors.zip" || exit 1
curl --retry 5 --retry-delay 3 --insecure -L "https://github.com/rxi/lite-colors/archive/master.zip" -o "$build/rxi-lite-colors.zip" || exit 1
mkdir -p "$build/third/data/colors" "$build/third/data/plugins"
unzip -qq "$build/lite-xl-colors.zip" -d "$build"
mv "$build/lite-xl-colors-master/colors" "$build/third/data"
rm -fr "$build/lite-xl-colors-master"
rm "$build/lite-xl-colors.zip"
unzip -qq "$build/rxi-lite-colors.zip" -d "$build"
mv "$build/lite-colors-master/colors" "$build/third/data"
rm -fr "$build/lite-colors-master"
rm "$build/rxi-lite-colors.zip"
}
lite_branch=master
@ -47,7 +47,7 @@ workdir=".repackage"
rm -fr "$workdir" && mkdir "$workdir" && pushd "$workdir"
fetch_packages_from_github () {
assets=($($wget -q -nv -O- https://api.github.com/repos/lite-xl/lite-xl/releases/latest | grep "browser_download_url" | cut -d '"' -f 4))
assets=($($wget -q -nv -O- https://api.github.com/repos/franko/lite-xl/releases/latest | grep "browser_download_url" | cut -d '"' -f 4))
for url in "${assets[@]}"; do
echo "getting: $url"

View File

@ -4,18 +4,19 @@
int luaopen_system(lua_State *L);
int luaopen_renderer(lua_State *L);
int luaopen_regex(lua_State *L);
// int luaopen_process(lua_State *L);
int luaopen_process(lua_State *L);
static const luaL_Reg libs[] = {
{ "system", luaopen_system },
{ "renderer", luaopen_renderer },
{ "regex", luaopen_regex },
// { "process", luaopen_process },
{ "process", luaopen_process },
{ NULL, NULL }
};
void api_load_libs(lua_State *L) {
for (int i = 0; libs[i].name; i++)
for (int i = 0; libs[i].name; i++) {
luaL_requiref(L, libs[i].name, libs[i].func, 1);
}
}

View File

@ -1,15 +1,14 @@
#ifndef API_H
#define API_H
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#define API_TYPE_FONT "Font"
#define API_TYPE_REPLACE "Replace"
#define API_TYPE_PROCESS "Process"
#define API_CONSTANT_DEFINE(L, idx, key, n) (lua_pushnumber(L, n), lua_setfield(L, idx - 1, key))
void api_load_libs(lua_State *L);
#endif

42
src/api/cp_replace.c Normal file
View File

@ -0,0 +1,42 @@
#include "api.h"
#include "renderer.h"
static int f_new(lua_State *L) {
CPReplaceTable *rep_table = lua_newuserdata(L, sizeof(CPReplaceTable));
luaL_setmetatable(L, API_TYPE_REPLACE);
ren_cp_replace_init(rep_table);
return 1;
}
static int f_gc(lua_State *L) {
CPReplaceTable *rep_table = luaL_checkudata(L, 1, API_TYPE_REPLACE);
ren_cp_replace_free(rep_table);
return 0;
}
static int f_add(lua_State *L) {
CPReplaceTable *rep_table = luaL_checkudata(L, 1, API_TYPE_REPLACE);
const char *src = luaL_checkstring(L, 2);
const char *dst = luaL_checkstring(L, 3);
ren_cp_replace_add(rep_table, src, dst);
return 0;
}
static const luaL_Reg lib[] = {
{ "__gc", f_gc },
{ "new", f_new },
{ "add", f_add },
{ NULL, NULL }
};
int luaopen_renderer_replacements(lua_State *L) {
luaL_newmetatable(L, API_TYPE_REPLACE);
luaL_setfuncs(L, lib, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
return 1;
}

View File

@ -1,468 +1,408 @@
#include "api.h"
/**
* Basic binding of reproc into Lua.
* @copyright Jefferson Gonzalez
* @license MIT
*/
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <SDL.h>
#if _WIN32
// https://stackoverflow.com/questions/60645/overlapped-i-o-on-anonymous-pipe
// https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
#include <windows.h>
#else
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#endif
#include <reproc/reproc.h>
#include "api.h"
#define READ_BUF_SIZE 2048
#define L_GETTABLE(L, idx, key, conv, def) ( \
lua_getfield(L, idx, key), \
conv(L, -1, def) \
)
#define L_GETNUM(L, idx, key, def) L_GETTABLE(L, idx, key, luaL_optnumber, def)
#define L_GETSTR(L, idx, key, def) L_GETTABLE(L, idx, key, luaL_optstring, def)
#define L_SETNUM(L, idx, key, n) (lua_pushnumber(L, n), lua_setfield(L, idx - 1, key))
#define L_RETURN_REPROC_ERROR(L, code) { \
lua_pushnil(L); \
lua_pushstring(L, reproc_strerror(code)); \
lua_pushnumber(L, code); \
return 3; \
}
#define ASSERT_MALLOC(ptr) \
if (ptr == NULL) \
L_RETURN_REPROC_ERROR(L, REPROC_ENOMEM)
#define ASSERT_REPROC_ERRNO(L, code) { \
if (code < 0) \
L_RETURN_REPROC_ERROR(L, code) \
}
typedef struct {
bool running;
int returncode, deadline;
long pid;
#if _WIN32
PROCESS_INFORMATION process_information;
HANDLE child_pipes[3][2];
OVERLAPPED overlapped[2];
bool reading[2];
char buffer[2][READ_BUF_SIZE];
#else
int child_pipes[3][2];
#endif
reproc_t * process;
bool running;
int returncode;
} process_t;
typedef enum {
SIGNAL_KILL,
SIGNAL_TERM,
SIGNAL_INTERRUPT
} signal_e;
typedef enum {
WAIT_NONE = 0,
WAIT_DEADLINE = -1,
WAIT_INFINITE = -2
} wait_e;
typedef enum {
STDIN_FD,
STDOUT_FD,
STDERR_FD,
// Special values for redirection.
REDIRECT_DEFAULT = -1,
REDIRECT_DISCARD = -2,
REDIRECT_PARENT = -3,
} filed_e;
#ifdef _WIN32
static volatile long PipeSerialNumber;
static void close_fd(HANDLE handle) { CloseHandle(handle); }
#else
static void close_fd(int fd) { close(fd); }
#endif
static bool poll_process(process_t* proc, int timeout) {
if (!proc->running)
return false;
unsigned int ticks = SDL_GetTicks();
if (timeout == WAIT_DEADLINE)
timeout = proc->deadline;
do {
#ifdef _WIN32
DWORD exit_code = -1;
if (!GetExitCodeProcess( proc->process_information.hProcess, &exit_code ) || exit_code != STILL_ACTIVE) {
proc->returncode = exit_code;
// this function should be called instead of reproc_wait
static int poll_process(process_t* proc, int timeout)
{
int ret = reproc_wait(proc->process, timeout);
if (ret != REPROC_ETIMEDOUT) {
proc->running = false;
break;
}
#else
int status;
pid_t wait_response = waitpid(proc->pid, &status, WNOHANG);
if (wait_response != 0) {
proc->running = false;
proc->returncode = WEXITSTATUS(status);
break;
}
#endif
if (timeout)
SDL_Delay(5);
} while (timeout == WAIT_INFINITE || SDL_GetTicks() - ticks < timeout);
if (!proc->running) {
close_fd(proc->child_pipes[STDIN_FD ][1]);
close_fd(proc->child_pipes[STDOUT_FD][0]);
close_fd(proc->child_pipes[STDERR_FD][0]);
return false;
}
return true;
proc->returncode = ret;
}
return ret;
}
static bool signal_process(process_t* proc, signal_e sig) {
bool terminate = false;
#if _WIN32
switch(sig) {
case SIGNAL_TERM: terminate = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, GetProcessId(proc->process_information.hProcess)); break;
case SIGNAL_KILL: terminate = TerminateProcess(proc->process_information.hProcess, -1); break;
case SIGNAL_INTERRUPT: DebugBreakProcess(proc->process_information.hProcess); break;
}
#else
switch (sig) {
case SIGNAL_TERM: terminate = kill(proc->pid, SIGTERM) == 1; break;
case SIGNAL_KILL: terminate = kill(proc->pid, SIGKILL) == 1; break;
case SIGNAL_INTERRUPT: kill(proc->pid, SIGINT); break;
}
#endif
if (terminate)
poll_process(proc, WAIT_NONE);
return true;
}
static int process_start(lua_State* L) {
size_t env_len = 0, key_len, val_len;
const char *cmd[256], *env[256] = { NULL }, *cwd = NULL;
bool detach = false;
int deadline = 10, new_fds[3] = { STDIN_FD, STDOUT_FD, STDERR_FD };
luaL_checktype(L, 1, LUA_TTABLE);
#if LUA_VERSION_NUM > 501
lua_len(L, 1);
#else
lua_pushnumber(L, (int)lua_objlen(L, 1));
#endif
size_t cmd_len = luaL_checknumber(L, -1); lua_pop(L, 1);
size_t arg_len = lua_gettop(L);
for (size_t i = 1; i <= cmd_len; ++i) {
lua_pushnumber(L, i);
lua_rawget(L, 1);
cmd[i-1] = luaL_checkstring(L, -1);
}
cmd[cmd_len] = NULL;
if (arg_len > 1) {
lua_getfield(L, 2, "env");
if (!lua_isnil(L, -1)) {
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
const char* key = luaL_checklstring(L, -2, &key_len);
const char* val = luaL_checklstring(L, -1, &val_len);
env[env_len] = malloc(key_len+val_len+2);
snprintf((char*)env[env_len++], key_len+val_len+2, "%s=%s", key, val);
lua_pop(L, 1);
}
} else
lua_pop(L, 1);
lua_getfield(L, 2, "detach"); detach = lua_toboolean(L, -1);
lua_getfield(L, 2, "timeout"); deadline = luaL_optnumber(L, -1, deadline);
lua_getfield(L, 2, "cwd"); cwd = luaL_optstring(L, -1, NULL);
lua_getfield(L, 2, "stdin"); new_fds[STDIN_FD] = luaL_optnumber(L, -1, STDIN_FD);
lua_getfield(L, 2, "stdout"); new_fds[STDOUT_FD] = luaL_optnumber(L, -1, STDOUT_FD);
lua_getfield(L, 2, "stderr"); new_fds[STDERR_FD] = luaL_optnumber(L, -1, STDERR_FD);
for (int stream = STDIN_FD; stream <= STDERR_FD; ++stream) {
if (new_fds[stream] > STDERR_FD || new_fds[stream] < REDIRECT_PARENT)
return luaL_error(L, "redirect to handles, FILE* and paths are not supported");
}
}
env[env_len] = NULL;
process_t* self = lua_newuserdata(L, sizeof(process_t));
memset(self, 0, sizeof(process_t));
luaL_setmetatable(L, API_TYPE_PROCESS);
self->deadline = deadline;
#if _WIN32
for (int i = 0; i < 3; ++i) {
switch (new_fds[i]) {
case REDIRECT_PARENT:
switch (i) {
case STDIN_FD: self->child_pipes[i][0] = GetStdHandle(STD_INPUT_HANDLE); break;
case STDOUT_FD: self->child_pipes[i][1] = GetStdHandle(STD_OUTPUT_HANDLE); break;
case STDERR_FD: self->child_pipes[i][1] = GetStdHandle(STD_ERROR_HANDLE); break;
}
self->child_pipes[i][i == STDIN_FD ? 1 : 0] = INVALID_HANDLE_VALUE;
break;
case REDIRECT_DISCARD:
self->child_pipes[i][0] = INVALID_HANDLE_VALUE;
self->child_pipes[i][1] = INVALID_HANDLE_VALUE;
break;
default: {
if (new_fds[i] == i) {
char pipeNameBuffer[MAX_PATH];
sprintf(pipeNameBuffer, "\\\\.\\Pipe\\RemoteExeAnon.%08lx.%08lx", GetCurrentProcessId(), InterlockedIncrement(&PipeSerialNumber));
self->child_pipes[i][0] = CreateNamedPipeA(pipeNameBuffer, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_WAIT, 1, READ_BUF_SIZE, READ_BUF_SIZE, 0, NULL);
if (self->child_pipes[i][0] == INVALID_HANDLE_VALUE)
return luaL_error(L, "Error creating read pipe: %d.", GetLastError());
self->child_pipes[i][1] = CreateFileA(pipeNameBuffer, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (self->child_pipes[i][1] == INVALID_HANDLE_VALUE) {
CloseHandle(self->child_pipes[i][0]);
return luaL_error(L, "Error creating write pipe: %d.", GetLastError());
}
if (!SetHandleInformation(self->child_pipes[i][i == STDIN_FD ? 1 : 0], HANDLE_FLAG_INHERIT, 0) ||
!SetHandleInformation(self->child_pipes[i][i == STDIN_FD ? 0 : 1], HANDLE_FLAG_INHERIT, 1))
return luaL_error(L, "Error inheriting pipes: %d.", GetLastError());
}
} break;
}
}
for (int i = 0; i < 3; ++i) {
if (new_fds[i] != i) {
self->child_pipes[i][0] = self->child_pipes[new_fds[i]][0];
self->child_pipes[i][1] = self->child_pipes[new_fds[i]][1];
}
}
STARTUPINFO siStartInfo;
memset(&self->process_information, 0, sizeof(self->process_information));
memset(&siStartInfo, 0, sizeof(siStartInfo));
siStartInfo.cb = sizeof(siStartInfo);
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
siStartInfo.hStdInput = self->child_pipes[STDIN_FD][0];
siStartInfo.hStdOutput = self->child_pipes[STDOUT_FD][1];
siStartInfo.hStdError = self->child_pipes[STDERR_FD][1];
char commandLine[32767] = { 0 }, environmentBlock[32767];
int offset = 0;
strcpy(commandLine, cmd[0]);
for (size_t i = 1; i < cmd_len; ++i) {
size_t len = strlen(cmd[i]);
if (offset + len + 1 >= sizeof(commandLine))
break;
strcat(commandLine, " ");
strcat(commandLine, cmd[i]);
}
for (size_t i = 0; i < env_len; ++i) {
size_t len = strlen(env[i]);
if (offset + len >= sizeof(environmentBlock))
break;
memcpy(&environmentBlock[offset], env[i], len);
offset += len;
environmentBlock[offset++] = 0;
}
environmentBlock[offset++] = 0;
if (!CreateProcess(NULL, commandLine, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_len > 0 ? environmentBlock : NULL, cwd, &siStartInfo, &self->process_information))
return luaL_error(L, "Error creating a process: %d.", GetLastError());
self->pid = (long)self->process_information.dwProcessId;
if (detach)
CloseHandle(self->process_information.hProcess);
CloseHandle(self->process_information.hThread);
#else
for (int i = 0; i < 3; ++i) { // Make only the parents fd's non-blocking. Children should block.
if (pipe(self->child_pipes[i]) || fcntl(self->child_pipes[i][i == STDIN_FD ? 1 : 0], F_SETFL, O_NONBLOCK) == -1)
return luaL_error(L, "Error creating pipes: %s", strerror(errno));
}
self->pid = (long)fork();
if (self->pid < 0) {
for (int i = 0; i < 3; ++i) {
close(self->child_pipes[i][0]);
close(self->child_pipes[i][1]);
}
return luaL_error(L, "Error running fork: %s.", strerror(errno));
} else if (!self->pid) {
for (int stream = 0; stream < 3; ++stream) {
if (new_fds[stream] == REDIRECT_DISCARD) { // Close the stream if we don't want it.
close(self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]);
close(stream);
} else if (new_fds[stream] != REDIRECT_PARENT) // Use the parent handles if we redirect to parent.
dup2(self->child_pipes[new_fds[stream]][new_fds[stream] == STDIN_FD ? 0 : 1], stream);
close(self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]);
}
if ((!detach || setsid() != -1) && (!cwd || chdir(cwd) != -1))
execvp((const char*)cmd[0], (char* const*)cmd);
const char* msg = strerror(errno);
int result = write(STDERR_FD, msg, strlen(msg)+1);
exit(result == strlen(msg)+1 ? -1 : -2);
}
#endif
for (size_t i = 0; i < env_len; ++i)
free((char*)env[i]);
for (int stream = 0; stream < 3; ++stream)
close_fd(self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]);
self->running = true;
return 1;
}
static int g_read(lua_State* L, int stream, unsigned long read_size) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
long length = 0;
if (stream != STDOUT_FD && stream != STDERR_FD)
return luaL_error(L, "redirect to handles, FILE* and paths are not supported");
#if _WIN32
int writable_stream_idx = stream - 1;
if (self->reading[writable_stream_idx] || !ReadFile(self->child_pipes[stream][0], self->buffer[writable_stream_idx], READ_BUF_SIZE, NULL, &self->overlapped[writable_stream_idx])) {
if (self->reading[writable_stream_idx] || GetLastError() == ERROR_IO_PENDING) {
self->reading[writable_stream_idx] = true;
DWORD bytesTransferred = 0;
if (GetOverlappedResult(self->child_pipes[stream][0], &self->overlapped[writable_stream_idx], &bytesTransferred, false)) {
self->reading[writable_stream_idx] = false;
length = bytesTransferred;
memset(&self->overlapped[writable_stream_idx], 0, sizeof(self->overlapped[writable_stream_idx]));
static int kill_process(process_t* proc)
{
int ret = reproc_stop(
proc->process,
(reproc_stop_actions) {
{REPROC_STOP_KILL, 0},
{REPROC_STOP_TERMINATE, 0},
{REPROC_STOP_NOOP, 0}
}
} else {
signal_process(self, SIGNAL_TERM);
return 0;
}
} else {
length = self->overlapped[writable_stream_idx].InternalHigh;
memset(&self->overlapped[writable_stream_idx], 0, sizeof(self->overlapped[writable_stream_idx]));
);
if (ret != REPROC_ETIMEDOUT) {
proc->running = false;
proc->returncode = ret;
}
lua_pushlstring(L, self->buffer[writable_stream_idx], length);
#else
luaL_Buffer b;
luaL_buffinit(L, &b);
uint8_t* buffer = (uint8_t*)luaL_prepbuffer(&b);
length = read(self->child_pipes[stream][0], buffer, read_size > READ_BUF_SIZE ? READ_BUF_SIZE : read_size);
if (length == 0 && !poll_process(self, WAIT_NONE))
return 0;
else if (length < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
length = 0;
if (length < 0) {
signal_process(self, SIGNAL_TERM);
return 0;
}
luaL_addsize(&b, length);
luaL_pushresult(&b);
#endif
return 1;
return ret;
}
static int f_write(lua_State* L) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
size_t data_size = 0;
const char* data = luaL_checklstring(L, 2, &data_size);
long length;
#if _WIN32
DWORD dwWritten;
if (!WriteFile(self->child_pipes[STDIN_FD][1], data, data_size, &dwWritten, NULL)) {
signal_process(self, SIGNAL_TERM);
return luaL_error(L, "error writing to process: %d", GetLastError());
static int process_start(lua_State* L)
{
luaL_checktype(L, 1, LUA_TTABLE);
if (lua_isnoneornil(L, 2)) {
lua_settop(L, 1); // remove the nil if it's there
lua_newtable(L);
}
length = dwWritten;
#else
length = write(self->child_pipes[STDIN_FD][1], data, data_size);
if (length < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
length = 0;
else if (length < 0) {
signal_process(self, SIGNAL_TERM);
return luaL_error(L, "error writing to process: %s", strerror(errno));
luaL_checktype(L, 2, LUA_TTABLE);
int cmd_len = lua_rawlen(L, 1);
const char** cmd = malloc(sizeof(char *) * (cmd_len + 1));
ASSERT_MALLOC(cmd);
cmd[cmd_len] = NULL;
for(int i = 0; i < cmd_len; i++) {
lua_rawgeti(L, 1, i + 1);
cmd[i] = luaL_checkstring(L, -1);
lua_pop(L, 1);
}
#endif
lua_pushnumber(L, length);
return 1;
}
static int f_close_stream(lua_State* L) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
int stream = luaL_checknumber(L, 2);
close_fd(self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]);
lua_pushboolean(L, 1);
return 1;
}
int deadline = L_GETNUM(L, 2, "timeout", 0);
const char* cwd =L_GETSTR(L, 2, "cwd", NULL);
int redirect_in = L_GETNUM(L, 2, "stdin", REPROC_REDIRECT_DEFAULT);
int redirect_out = L_GETNUM(L, 2, "stdout", REPROC_REDIRECT_DEFAULT);
int redirect_err = L_GETNUM(L, 2, "stderr", REPROC_REDIRECT_DEFAULT);
lua_pop(L, 5); // remove args we just read
// Generic stuff below here.
static int process_strerror(lua_State* L) {
#if _WIN32
if (
redirect_in > REPROC_REDIRECT_STDOUT
|| redirect_out > REPROC_REDIRECT_STDOUT
|| redirect_err > REPROC_REDIRECT_STDOUT)
{
lua_pushnil(L);
lua_pushliteral(L, "redirect to handles, FILE* and paths are not supported");
return 2;
}
// env
luaL_getsubtable(L, 2, "env");
const char **env = NULL;
int env_len = 0;
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
env_len++;
lua_pop(L, 1);
}
if (env_len > 0) {
env = malloc(sizeof(char*) * (env_len + 1));
env[env_len] = NULL;
int i = 0;
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
lua_pushliteral(L, "=");
lua_pushvalue(L, -3); // push the key to the top
lua_concat(L, 3); // key=value
env[i++] = luaL_checkstring(L, -1);
lua_pop(L, 1);
}
}
reproc_t* proc = reproc_new();
int out = reproc_start(
proc,
(const char* const*) cmd,
(reproc_options) {
.working_directory = cwd,
.deadline = deadline,
.nonblocking = true,
.env = {
.behavior = REPROC_ENV_EXTEND,
.extra = env
},
.redirect = {
.in.type = redirect_in,
.out.type = redirect_out,
.err.type = redirect_err
}
}
);
if (out < 0) {
reproc_destroy(proc);
L_RETURN_REPROC_ERROR(L, out);
}
process_t* self = lua_newuserdata(L, sizeof(process_t));
self->process = proc;
self->running = true;
// this is equivalent to using lua_setmetatable()
luaL_setmetatable(L, API_TYPE_PROCESS);
return 1;
#endif
int error_code = luaL_checknumber(L, 1);
if (error_code < 0)
lua_pushstring(L, strerror(error_code));
else
lua_pushnil(L);
return 1;
}
static int f_tostring(lua_State* L) {
lua_pushliteral(L, API_TYPE_PROCESS);
return 1;
static int process_strerror(lua_State* L)
{
int error_code = luaL_checknumber(L, 1);
if (error_code < 0)
lua_pushstring(L, reproc_strerror(error_code));
else
lua_pushnil(L);
return 1;
}
static int f_pid(lua_State* L) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
lua_pushnumber(L, self->pid);
return 1;
}
static int f_gc(lua_State* L)
{
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
if(self->process) {
kill_process(self);
reproc_destroy(self->process);
self->process = NULL;
}
static int f_returncode(lua_State *L) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
if (self->running)
return 0;
lua_pushnumber(L, self->returncode);
return 1;
}
static int f_read_stdout(lua_State* L) {
return g_read(L, STDOUT_FD, luaL_optinteger(L, 2, READ_BUF_SIZE));
static int f_tostring(lua_State* L)
{
luaL_checkudata(L, 1, API_TYPE_PROCESS);
lua_pushliteral(L, API_TYPE_PROCESS);
return 1;
}
static int f_read_stderr(lua_State* L) {
return g_read(L, STDERR_FD, luaL_optinteger(L, 2, READ_BUF_SIZE));
static int f_pid(lua_State* L)
{
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
lua_pushnumber(L, reproc_pid(self->process));
return 1;
}
static int f_read(lua_State* L) {
return g_read(L, luaL_checknumber(L, 2), luaL_optinteger(L, 3, READ_BUF_SIZE));
static int f_returncode(lua_State *L)
{
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
int ret = poll_process(self, 0);
if (self->running)
lua_pushnil(L);
else
lua_pushnumber(L, ret);
return 1;
}
static int f_wait(lua_State* L) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
int timeout = luaL_optnumber(L, 2, 0);
if (poll_process(self, timeout))
return 0;
lua_pushnumber(L, self->returncode);
return 1;
static int g_read(lua_State* L, int stream)
{
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
unsigned long read_size = luaL_optunsigned(L, 2, READ_BUF_SIZE);
luaL_Buffer b;
uint8_t* buffer = (uint8_t*) luaL_buffinitsize(L, &b, read_size);
int out = reproc_read(
self->process,
stream,
buffer,
read_size
);
if (out >= 0)
luaL_addsize(&b, out);
luaL_pushresult(&b);
if (out == REPROC_EPIPE) {
kill_process(self);
ASSERT_REPROC_ERRNO(L, out);
}
return 1;
}
static int self_signal(lua_State* L, signal_e sig) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
signal_process(self, sig);
lua_pushboolean(L, 1);
return 1;
static int f_read_stdout(lua_State* L)
{
return g_read(L, REPROC_STREAM_OUT);
}
static int f_terminate(lua_State* L) { return self_signal(L, SIGNAL_TERM); }
static int f_kill(lua_State* L) { return self_signal(L, SIGNAL_KILL); }
static int f_interrupt(lua_State* L) { return self_signal(L, SIGNAL_INTERRUPT); }
static int f_gc(lua_State* L) { return self_signal(L, SIGNAL_TERM); }
static int f_running(lua_State* L) {
process_t* self = (process_t*)luaL_checkudata(L, 1, API_TYPE_PROCESS);
lua_pushboolean(L, self->running);
return 1;
static int f_read_stderr(lua_State* L)
{
return g_read(L, REPROC_STREAM_ERR);
}
static int f_read(lua_State* L)
{
int stream = luaL_checknumber(L, 2);
lua_remove(L, 2);
if (stream > REPROC_STREAM_ERR)
L_RETURN_REPROC_ERROR(L, REPROC_EINVAL);
return g_read(L, stream);
}
static int f_write(lua_State* L)
{
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
size_t data_size = 0;
const char* data = luaL_checklstring(L, 2, &data_size);
int out = reproc_write(
self->process,
(uint8_t*) data,
data_size
);
if (out == REPROC_EPIPE) {
kill_process(self);
L_RETURN_REPROC_ERROR(L, out);
}
lua_pushnumber(L, out);
return 1;
}
static int f_close_stream(lua_State* L)
{
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
int stream = luaL_checknumber(L, 2);
int out = reproc_close(self->process, stream);
ASSERT_REPROC_ERRNO(L, out);
lua_pushboolean(L, 1);
return 1;
}
static int f_wait(lua_State* L)
{
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
int timeout = luaL_optnumber(L, 2, 0);
int ret = poll_process(self, timeout);
// negative returncode is also used for signals on POSIX
if (ret == REPROC_ETIMEDOUT)
L_RETURN_REPROC_ERROR(L, ret);
lua_pushnumber(L, ret);
return 1;
}
static int f_terminate(lua_State* L)
{
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
int out = reproc_terminate(self->process);
ASSERT_REPROC_ERRNO(L, out);
poll_process(self, 0);
lua_pushboolean(L, 1);
return 1;
}
static int f_kill(lua_State* L)
{
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
int out = reproc_kill(self->process);
ASSERT_REPROC_ERRNO(L, out);
poll_process(self, 0);
lua_pushboolean(L, 1);
return 1;
}
static int f_running(lua_State* L)
{
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
poll_process(self, 0);
lua_pushboolean(L, self->running);
return 1;
}
static const struct luaL_Reg lib[] = {
{"__gc", f_gc},
{"__tostring", f_tostring},
{"start", process_start},
{"strerror", process_strerror},
{"pid", f_pid},
{"returncode", f_returncode},
{"read", f_read},
{"read_stdout", f_read_stdout},
{"read_stderr", f_read_stderr},
{"write", f_write},
{"close_stream", f_close_stream},
{"wait", f_wait},
{"terminate", f_terminate},
{"kill", f_kill},
{"interrupt", f_interrupt},
{"running", f_running},
{NULL, NULL}
{"start", process_start},
{"strerror", process_strerror},
{"__gc", f_gc},
{"__tostring", f_tostring},
{"pid", f_pid},
{"returncode", f_returncode},
{"read", f_read},
{"read_stdout", f_read_stdout},
{"read_stderr", f_read_stderr},
{"write", f_write},
{"close_stream", f_close_stream},
{"wait", f_wait},
{"terminate", f_terminate},
{"kill", f_kill},
{"running", f_running},
{NULL, NULL}
};
int luaopen_process(lua_State *L) {
luaL_newmetatable(L, API_TYPE_PROCESS);
luaL_setfuncs(L, lib, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
int luaopen_process(lua_State *L)
{
luaL_newmetatable(L, API_TYPE_PROCESS);
luaL_setfuncs(L, lib, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
API_CONSTANT_DEFINE(L, -1, "WAIT_INFINITE", WAIT_INFINITE);
API_CONSTANT_DEFINE(L, -1, "WAIT_DEADLINE", WAIT_DEADLINE);
// constants
L_SETNUM(L, -1, "ERROR_INVAL", REPROC_EINVAL);
L_SETNUM(L, -1, "ERROR_TIMEDOUT", REPROC_ETIMEDOUT);
L_SETNUM(L, -1, "ERROR_PIPE", REPROC_EPIPE);
L_SETNUM(L, -1, "ERROR_NOMEM", REPROC_ENOMEM);
L_SETNUM(L, -1, "ERROR_WOULDBLOCK", REPROC_EWOULDBLOCK);
API_CONSTANT_DEFINE(L, -1, "STREAM_STDIN", STDIN_FD);
API_CONSTANT_DEFINE(L, -1, "STREAM_STDOUT", STDOUT_FD);
API_CONSTANT_DEFINE(L, -1, "STREAM_STDERR", STDERR_FD);
L_SETNUM(L, -1, "WAIT_INFINITE", REPROC_INFINITE);
L_SETNUM(L, -1, "WAIT_DEADLINE", REPROC_DEADLINE);
API_CONSTANT_DEFINE(L, -1, "REDIRECT_DEFAULT", REDIRECT_DEFAULT);
API_CONSTANT_DEFINE(L, -1, "REDIRECT_STDOUT", STDOUT_FD);
API_CONSTANT_DEFINE(L, -1, "REDIRECT_STDERR", STDERR_FD);
API_CONSTANT_DEFINE(L, -1, "REDIRECT_PARENT", REDIRECT_PARENT); // Redirects to parent's STDOUT/STDERR
API_CONSTANT_DEFINE(L, -1, "REDIRECT_DISCARD", REDIRECT_DISCARD); // Closes the filehandle, discarding it.
L_SETNUM(L, -1, "STREAM_STDIN", REPROC_STREAM_IN);
L_SETNUM(L, -1, "STREAM_STDOUT", REPROC_STREAM_OUT);
L_SETNUM(L, -1, "STREAM_STDERR", REPROC_STREAM_ERR);
return 1;
L_SETNUM(L, -1, "REDIRECT_DEFAULT", REPROC_REDIRECT_DEFAULT);
L_SETNUM(L, -1, "REDIRECT_PIPE", REPROC_REDIRECT_PIPE);
L_SETNUM(L, -1, "REDIRECT_PARENT", REPROC_REDIRECT_PARENT);
L_SETNUM(L, -1, "REDIRECT_DISCARD", REPROC_REDIRECT_DISCARD);
L_SETNUM(L, -1, "REDIRECT_STDOUT", REPROC_REDIRECT_STDOUT);
return 1;
}

View File

@ -68,11 +68,8 @@ static int f_pcre_match(lua_State *L) {
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) {
PCRE2_UCHAR buffer[120];
pcre2_get_error_message(rc, buffer, sizeof(buffer));
luaL_error(L, "regex matching error %d: %s", rc, buffer);
}
if (rc != PCRE2_ERROR_NOMATCH)
luaL_error(L, "regex matching error %d", rc);
return 0;
}
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(md);

View File

@ -2,133 +2,6 @@
#include "renderer.h"
#include "rencache.h"
static int f_font_load(lua_State *L) {
const char *filename = luaL_checkstring(L, 1);
float size = luaL_checknumber(L, 2);
unsigned int font_hinting = FONT_HINTING_SLIGHT, font_style = 0;
ERenFontAntialiasing font_antialiasing = FONT_ANTIALIASING_SUBPIXEL;
if (lua_gettop(L) > 2 && lua_istable(L, 3)) {
lua_getfield(L, 3, "antialiasing");
if (lua_isstring(L, -1)) {
const char *antialiasing = lua_tostring(L, -1);
if (antialiasing) {
if (strcmp(antialiasing, "none") == 0) {
font_antialiasing = FONT_ANTIALIASING_NONE;
} else if (strcmp(antialiasing, "grayscale") == 0) {
font_antialiasing = FONT_ANTIALIASING_GRAYSCALE;
} else if (strcmp(antialiasing, "subpixel") == 0) {
font_antialiasing = FONT_ANTIALIASING_SUBPIXEL;
} else {
return luaL_error(L, "error in renderer.font.load, unknown antialiasing option: \"%s\"", antialiasing);
}
}
}
lua_getfield(L, 3, "hinting");
if (lua_isstring(L, -1)) {
const char *hinting = lua_tostring(L, -1);
if (hinting) {
if (strcmp(hinting, "slight") == 0) {
font_hinting = FONT_HINTING_SLIGHT;
} else if (strcmp(hinting, "none") == 0) {
font_hinting = FONT_HINTING_NONE;
} else if (strcmp(hinting, "full") == 0) {
font_hinting = FONT_HINTING_FULL;
} else {
return luaL_error(L, "error in renderer.font.load, unknown hinting option: \"%s\"", hinting);
}
}
}
lua_getfield(L, 3, "italic");
if (lua_toboolean(L, -1))
font_style |= FONT_STYLE_ITALIC;
lua_getfield(L, 3, "bold");
if (lua_toboolean(L, -1))
font_style |= FONT_STYLE_BOLD;
lua_getfield(L, 3, "underline");
if (lua_toboolean(L, -1))
font_style |= FONT_STYLE_UNDERLINE;
lua_pop(L, 5);
}
RenFont** font = lua_newuserdata(L, sizeof(RenFont*));
*font = ren_font_load(filename, size, font_antialiasing, font_hinting, font_style);
if (!*font)
return luaL_error(L, "failed to load font");
luaL_setmetatable(L, API_TYPE_FONT);
return 1;
}
static bool font_retrieve(lua_State* L, RenFont** fonts, int idx) {
memset(fonts, 0, sizeof(RenFont*)*FONT_FALLBACK_MAX);
if (lua_type(L, 1) != LUA_TTABLE) {
fonts[0] = *(RenFont**)luaL_checkudata(L, idx, API_TYPE_FONT);
return false;
}
int i = 0;
do {
lua_rawgeti(L, idx, i+1);
fonts[i] = !lua_isnil(L, -1) ? *(RenFont**)luaL_checkudata(L, -1, API_TYPE_FONT) : NULL;
lua_pop(L, 1);
} while (fonts[i] && i++ < FONT_FALLBACK_MAX);
return true;
}
static int f_font_copy(lua_State *L) {
RenFont* fonts[FONT_FALLBACK_MAX];
bool table = font_retrieve(L, fonts, 1);
float size = lua_gettop(L) >= 2 ? luaL_checknumber(L, 2) : ren_font_group_get_height(fonts);
if (table) {
lua_newtable(L);
luaL_setmetatable(L, API_TYPE_FONT);
}
for (int i = 0; i < FONT_FALLBACK_MAX && fonts[i]; ++i) {
RenFont** font = lua_newuserdata(L, sizeof(RenFont*));
*font = ren_font_copy(fonts[i], size);
if (!*font)
return luaL_error(L, "failed to copy font");
luaL_setmetatable(L, API_TYPE_FONT);
if (table)
lua_rawseti(L, -2, i+1);
}
return 1;
}
static int f_font_group(lua_State* L) {
luaL_checktype(L, 1, LUA_TTABLE);
luaL_setmetatable(L, API_TYPE_FONT);
return 1;
}
static int f_font_set_tab_size(lua_State *L) {
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
int n = luaL_checknumber(L, 2);
ren_font_group_set_tab_size(fonts, n);
return 0;
}
static int f_font_gc(lua_State *L) {
RenFont** self = luaL_checkudata(L, 1, API_TYPE_FONT);
ren_font_free(*self);
return 0;
}
static int f_font_get_width(lua_State *L) {
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
lua_pushnumber(L, ren_font_group_get_width(fonts, luaL_checkstring(L, 2)));
return 1;
}
static int f_font_get_height(lua_State *L) {
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
lua_pushnumber(L, ren_font_group_get_height(fonts));
return 1;
}
static int f_font_get_size(lua_State *L) {
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
lua_pushnumber(L, ren_font_group_get_size(fonts));
return 1;
}
static RenColor checkcolor(lua_State *L, int idx, int def) {
RenColor color;
@ -176,47 +49,62 @@ static int f_end_frame(lua_State *L) {
}
static RenRect rect_to_grid(lua_Number x, lua_Number y, lua_Number w, lua_Number h) {
int x1 = (int) (x + 0.5), y1 = (int) (y + 0.5);
int x2 = (int) (x + w + 0.5), y2 = (int) (y + h + 0.5);
return (RenRect) {x1, y1, x2 - x1, y2 - y1};
}
static int f_set_clip_rect(lua_State *L) {
lua_Number x = luaL_checknumber(L, 1);
lua_Number y = luaL_checknumber(L, 2);
lua_Number w = luaL_checknumber(L, 3);
lua_Number h = luaL_checknumber(L, 4);
RenRect rect = rect_to_grid(x, y, w, h);
RenRect rect;
rect.x = luaL_checknumber(L, 1);
rect.y = luaL_checknumber(L, 2);
rect.width = luaL_checknumber(L, 3);
rect.height = luaL_checknumber(L, 4);
rencache_set_clip_rect(rect);
return 0;
}
static int f_draw_rect(lua_State *L) {
lua_Number x = luaL_checknumber(L, 1);
lua_Number y = luaL_checknumber(L, 2);
lua_Number w = luaL_checknumber(L, 3);
lua_Number h = luaL_checknumber(L, 4);
RenRect rect = rect_to_grid(x, y, w, h);
RenRect rect;
rect.x = luaL_checknumber(L, 1);
rect.y = luaL_checknumber(L, 2);
rect.width = luaL_checknumber(L, 3);
rect.height = luaL_checknumber(L, 4);
RenColor color = checkcolor(L, 5, 255);
rencache_draw_rect(rect, color);
return 0;
}
static int f_draw_text(lua_State *L) {
RenFont* fonts[FONT_FALLBACK_MAX];
font_retrieve(L, fonts, 1);
static int draw_text_subpixel_impl(lua_State *L, bool draw_subpixel) {
FontDesc *font_desc = luaL_checkudata(L, 1, API_TYPE_FONT);
const char *text = luaL_checkstring(L, 2);
float x = luaL_checknumber(L, 3);
/* The coordinate below will be in subpixel iff draw_subpixel is true.
Otherwise it will be in pixels. */
int x_subpixel = luaL_checknumber(L, 3);
int y = luaL_checknumber(L, 4);
RenColor color = checkcolor(L, 5, 255);
x = rencache_draw_text(L, fonts, text, x, y, color);
lua_pushnumber(L, x);
CPReplaceTable *rep_table;
RenColor replace_color;
if (lua_gettop(L) >= 7) {
rep_table = luaL_checkudata(L, 6, API_TYPE_REPLACE);
replace_color = checkcolor(L, 7, 255);
} else {
rep_table = NULL;
replace_color = (RenColor) {0};
}
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);
return 1;
}
static int f_draw_text(lua_State *L) {
return draw_text_subpixel_impl(L, false);
}
static int f_draw_text_subpixel(lua_State *L) {
return draw_text_subpixel_impl(L, true);
}
static const luaL_Reg lib[] = {
{ "show_debug", f_show_debug },
{ "get_size", f_get_size },
@ -225,28 +113,19 @@ static const luaL_Reg lib[] = {
{ "set_clip_rect", f_set_clip_rect },
{ "draw_rect", f_draw_rect },
{ "draw_text", f_draw_text },
{ "draw_text_subpixel", f_draw_text_subpixel },
{ NULL, NULL }
};
static const luaL_Reg fontLib[] = {
{ "__gc", f_font_gc },
{ "load", f_font_load },
{ "copy", f_font_copy },
{ "group", f_font_group },
{ "set_tab_size", f_font_set_tab_size },
{ "get_width", f_font_get_width },
{ "get_height", f_font_get_height },
{ "get_size", f_font_get_size },
{ NULL, NULL }
};
int luaopen_renderer_font(lua_State *L);
int luaopen_renderer_replacements(lua_State *L);
int luaopen_renderer(lua_State *L) {
luaL_newlib(L, lib);
luaL_newmetatable(L, API_TYPE_FONT);
luaL_setfuncs(L, fontLib, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaopen_renderer_font(L);
lua_setfield(L, -2, "font");
luaopen_renderer_replacements(L);
lua_setfield(L, -2, "replacements");
return 1;
}

158
src/api/renderer_font.c Normal file
View File

@ -0,0 +1,158 @@
#include <lua.h>
#include <lauxlib.h>
#include "api.h"
#include "fontdesc.h"
#include "renderer.h"
#include "rencache.h"
static int f_load(lua_State *L) {
const char *filename = luaL_checkstring(L, 1);
float size = luaL_checknumber(L, 2);
unsigned int font_options = 0;
if (lua_gettop(L) > 2 && lua_istable(L, 3)) {
lua_getfield(L, 3, "antialiasing");
if (lua_isstring(L, -1)) {
const char *antialiasing = lua_tostring(L, -1);
if (antialiasing) {
if (strcmp(antialiasing, "grayscale") == 0) {
font_options |= RenFontGrayscale;
} else if (strcmp(antialiasing, "subpixel") == 0) {
font_options |= RenFontSubpixel;
} else {
return luaL_error(L, "error in renderer.font.load, unknown antialiasing option: \"%s\"", antialiasing);
}
}
}
lua_pop(L, 1);
lua_getfield(L, 3, "hinting");
if (lua_isstring(L, -1)) {
const char *hinting = lua_tostring(L, -1);
if (hinting) {
if (strcmp(hinting, "slight") == 0) {
font_options |= RenFontHintingSlight;
} else if (strcmp(hinting, "none") == 0) {
font_options |= RenFontHintingNone;
} else if (strcmp(hinting, "full") == 0) {
font_options |= RenFontHintingFull;
} else {
return luaL_error(L, "error in renderer.font.load, unknown hinting option: \"%s\"", hinting);
}
}
}
lua_pop(L, 1);
}
if (ren_verify_font(filename)) {
luaL_error(L, "failed to load font");
}
FontDesc *font_desc = lua_newuserdata(L, font_desc_alloc_size(filename));
font_desc_init(font_desc, filename, size, font_options);
luaL_setmetatable(L, API_TYPE_FONT);
return 1;
}
static int f_copy(lua_State *L) {
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
float size;
if (lua_gettop(L) >= 2) {
size = luaL_checknumber(L, 2);
} else {
size = self->size;
}
FontDesc *new_font_desc = lua_newuserdata(L, font_desc_alloc_size(self->filename));
font_desc_init(new_font_desc, self->filename, size, self->options);
luaL_setmetatable(L, API_TYPE_FONT);
return 1;
}
static int f_set_tab_size(lua_State *L) {
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
int n = luaL_checknumber(L, 2);
font_desc_set_tab_size(self, n);
return 0;
}
static int f_gc(lua_State *L) {
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
font_desc_clear(self);
return 0;
}
static int f_get_width(lua_State *L) {
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
const char *text = luaL_checkstring(L, 2);
/* By calling ren_get_font_width with NULL as third arguments
we will obtain the width in points. */
int w = ren_get_font_width(self, text, NULL);
lua_pushnumber(L, w);
return 1;
}
static int f_subpixel_scale(lua_State *L) {
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
lua_pushnumber(L, ren_get_font_subpixel_scale(self));
return 1;
}
static int f_get_width_subpixel(lua_State *L) {
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
const char *text = luaL_checkstring(L, 2);
int subpixel_scale;
/* We need to pass a non-null subpixel_scale pointer to force
subpixel width calculation. */
lua_pushnumber(L, ren_get_font_width(self, text, &subpixel_scale));
return 1;
}
static int f_get_height(lua_State *L) {
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
lua_pushnumber(L, ren_get_font_height(self) );
return 1;
}
static int f_get_size(lua_State *L) {
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
lua_pushnumber(L, self->size);
return 1;
}
static int f_set_size(lua_State *L) {
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
float new_size = luaL_checknumber(L, 2);
font_desc_clear(self);
self->size = new_size;
return 0;
}
static const luaL_Reg lib[] = {
{ "__gc", f_gc },
{ "load", f_load },
{ "copy", f_copy },
{ "set_tab_size", f_set_tab_size },
{ "get_width", f_get_width },
{ "get_width_subpixel", f_get_width_subpixel },
{ "get_height", f_get_height },
{ "subpixel_scale", f_subpixel_scale },
{ "get_size", f_get_size },
{ "set_size", f_set_size },
{ NULL, NULL }
};
int luaopen_renderer_font(lua_State *L) {
luaL_newmetatable(L, API_TYPE_FONT);
luaL_setfuncs(L, lib, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
return 1;
}

View File

@ -6,16 +6,12 @@
#include <errno.h>
#include <sys/stat.h>
#include "api.h"
// #include "dirmonitor.h"
#include "dirmonitor.h"
#include "rencache.h"
#ifdef _WIN32
#include <direct.h>
#include <windows.h>
#include <fileapi.h>
#elif __linux__
#include <sys/vfs.h>
#elif __amigaos4__
#include "platform/amigaos4.h"
#endif
extern SDL_Window *window;
@ -23,11 +19,9 @@ extern SDL_Window *window;
static const char* button_name(int button) {
switch (button) {
case SDL_BUTTON_LEFT : return "left";
case SDL_BUTTON_MIDDLE : return "middle";
case SDL_BUTTON_RIGHT : return "right";
case SDL_BUTTON_X1 : return "x";
case SDL_BUTTON_X2 : return "y";
case 1 : return "left";
case 2 : return "middle";
case 3 : return "right";
default : return "?";
}
}
@ -101,8 +95,6 @@ static const char *numpad[] = { "end", "down", "pagedown", "left", "", "right",
static const char *get_key_name(const SDL_Event *e, char *buf) {
SDL_Scancode scancode = e->key.keysym.scancode;
#if !defined(__amigaos4__) && !defined(__MORPHOS__)
/* Is the scancode from the keypad and the number-lock off?
** We assume that SDL_SCANCODE_KP_1 up to SDL_SCANCODE_KP_9 and SDL_SCANCODE_KP_0
** and SDL_SCANCODE_KP_PERIOD are declared in SDL2 in that order. */
@ -110,23 +102,10 @@ static const char *get_key_name(const SDL_Event *e, char *buf) {
!(e->key.keysym.mod & KMOD_NUM)) {
return numpad[scancode - SDL_SCANCODE_KP_1];
} else {
#endif
/* We need to correctly handle non-standard layouts such as dvorak.
Therefore, if a Latin letter(code<128) is pressed in the current layout,
then we transmit it as it is. But we also need to support shortcuts in
other languages, so for non-Latin characters we pass the scancode that
matches the letter in the QWERTY layout. */
if (e->key.keysym.sym < 128)
strcpy(buf, SDL_GetKeyName(e->key.keysym.sym));
else
strcpy(buf, SDL_GetScancodeName(scancode));
strcpy(buf, SDL_GetKeyName(e->key.keysym.sym));
str_tolower(buf);
return buf;
#if !defined(__amigaos4__) && !defined(__MORPHOS__)
}
#endif
}
static int f_poll_event(lua_State *L) {
@ -261,19 +240,7 @@ top:
case SDL_USEREVENT:
lua_pushstring(L, "dirchange");
lua_pushnumber(L, e.user.code >> 16);
// switch (e.user.code & 0xffff) {
// case DMON_ACTION_DELETE:
// lua_pushstring(L, "delete");
// break;
// case DMON_ACTION_CREATE:
// lua_pushstring(L, "create");
// break;
// case DMON_ACTION_MODIFY:
// lua_pushstring(L, "modify");
// break;
// default:
// return luaL_error(L, "unknown dmon event action: %d", e.user.code & 0xffff);
// }
lua_pushstring(L, (e.user.code & 0xffff) == DMON_ACTION_DELETE ? "delete" : "create");
lua_pushstring(L, e.user.data1);
free(e.user.data1);
return 4;
@ -344,11 +311,7 @@ static int f_set_window_mode(lua_State *L) {
int n = luaL_checkoption(L, 1, "normal", window_opts);
SDL_SetWindowFullscreen(window,
n == WIN_FULLSCREEN ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
if (n == WIN_NORMAL)
{
ren_resize_window();
SDL_RestoreWindow(window);
}
if (n == WIN_NORMAL) { SDL_RestoreWindow(window); }
if (n == WIN_MAXIMIZED) { SDL_MaximizeWindow(window); }
if (n == WIN_MINIMIZED) { SDL_MinimizeWindow(window); }
return 0;
@ -530,10 +493,6 @@ static int f_list_dir(lua_State *L) {
#define realpath(x, y) _fullpath(y, x, MAX_PATH)
#endif
#ifdef __amigaos4__
#define realpath(x, y) _fullpath(x)
#endif
static int f_absolute_path(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
char *res = realpath(path, NULL);
@ -574,45 +533,6 @@ static int f_get_file_info(lua_State *L) {
return 1;
}
#if __linux__
// https://man7.org/linux/man-pages/man2/statfs.2.html
struct f_type_names {
uint32_t magic;
const char *name;
};
static struct f_type_names fs_names[] = {
{ 0xef53, "ext2/ext3" },
{ 0x6969, "nfs" },
{ 0x65735546, "fuse" },
{ 0x517b, "smb" },
{ 0xfe534d42, "smb2" },
{ 0x52654973, "reiserfs" },
{ 0x01021994, "tmpfs" },
{ 0x858458f6, "ramfs" },
{ 0x5346544e, "ntfs" },
{ 0x0, NULL },
};
static int f_get_fs_type(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
struct statfs buf;
int status = statfs(path, &buf);
if (status != 0) {
return luaL_error(L, "error calling statfs on %s", path);
}
for (int i = 0; fs_names[i].magic; i++) {
if (fs_names[i].magic == buf.f_type) {
lua_pushstring(L, fs_names[i].name);
return 1;
}
}
lua_pushstring(L, "unknown");
return 1;
}
#endif
static int f_mkdir(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
@ -680,32 +600,56 @@ static int f_exec(lua_State *L) {
return 0;
}
static int f_fuzzy_match(lua_State *L) {
size_t strLen, ptnLen;
const char *str = luaL_checklstring(L, 1, &strLen);
const char *ptn = luaL_checklstring(L, 2, &ptnLen);
// If true match things *backwards*. This allows for better matching on filenames than the above
bool files = false;
if (lua_gettop(L) > 2 && lua_isboolean(L,3))
files = lua_toboolean(L, 3);
int score = 0;
int run = 0;
// Match things *backwards*. This allows for better matching on filenames than the above
// function. For example, in the lite project, opening "renderer" has lib/font_render/build.sh
// as the first result, rather than src/renderer.c. Clearly that's wrong.
bool files = lua_gettop(L) > 2 && lua_isboolean(L,3) && lua_toboolean(L, 3);
int score = 0, run = 0, increment = files ? -1 : 1;
const char* strTarget = files ? str + strLen - 1 : str;
const char* ptnTarget = files ? ptn + ptnLen - 1 : ptn;
while (strTarget >= str && ptnTarget >= ptn && *strTarget && *ptnTarget) {
while (strTarget >= str && *strTarget == ' ') { strTarget += increment; }
while (ptnTarget >= ptn && *ptnTarget == ' ') { ptnTarget += increment; }
if (tolower(*strTarget) == tolower(*ptnTarget)) {
score += run * 10 - (*strTarget != *ptnTarget);
run++;
ptnTarget += increment;
} else {
score -= 10;
run = 0;
if (files) {
const char* strEnd = str + strLen - 1;
const char* ptnEnd = ptn + ptnLen - 1;
while (strEnd >= str && ptnEnd >= ptn) {
while (*strEnd == ' ') { strEnd--; }
while (*ptnEnd == ' ') { ptnEnd--; }
if (tolower(*strEnd) == tolower(*ptnEnd)) {
score += run * 10 - (*strEnd != *ptnEnd);
run++;
ptnEnd--;
} else {
score -= 10;
run = 0;
}
strEnd--;
}
strTarget += increment;
if (ptnEnd >= ptn) { return 0; }
} else {
while (*str && *ptn) {
while (*str == ' ') { str++; }
while (*ptn == ' ') { ptn++; }
if (tolower(*str) == tolower(*ptn)) {
score += run * 10 - (*str != *ptn);
run++;
ptn++;
} else {
score -= 10;
run = 0;
}
str++;
}
if (*ptn) { return 0; }
}
if (ptnTarget >= ptn && *ptnTarget) { return 0; }
lua_pushnumber(L, score - (int)strLen * 10);
lua_pushnumber(L, score - (int)strLen);
return 1;
}
@ -716,118 +660,30 @@ static int f_set_window_opacity(lua_State *L) {
return 1;
}
typedef struct lua_function_node {
const char *symbol;
void *address;
} lua_function_node;
#define P(FUNC) { "lua_" #FUNC, (void*)(lua_##FUNC) }
#define U(FUNC) { "luaL_" #FUNC, (void*)(luaL_##FUNC) }
static void* api_require(const char* symbol) {
static lua_function_node nodes[] = {
P(atpanic), P(checkstack),
P(close), P(concat), P(copy), P(createtable), P(dump),
P(error), P(gc), P(getallocf), P(getfield),
P(gethook), P(gethookcount), P(gethookmask), P(getinfo), P(getlocal),
P(getmetatable), P(getstack), P(gettable), P(gettop), P(getupvalue),
P(insert), P(isnumber), P(isstring), P(isuserdata),
P(load), P(newstate), P(newthread), P(newuserdata), P(next),
P(pushboolean), P(pushcclosure), P(pushfstring), P(pushinteger),
P(pushlightuserdata), P(pushlstring), P(pushnil), P(pushnumber),
P(pushstring), P(pushthread), P(pushvalue),
P(pushvfstring), P(rawequal), P(rawget), P(rawgeti),
P(rawset), P(rawseti), P(remove), P(replace), P(resume),
P(setallocf), P(setfield), P(sethook), P(setlocal),
P(setmetatable), P(settable), P(settop), P(setupvalue),
P(status), P(tocfunction), P(tointegerx), P(tolstring), P(toboolean),
P(tonumberx), P(topointer), P(tothread), P(touserdata),
P(type), P(typename), P(upvalueid), P(upvaluejoin), P(version), P(xmove),
U(getmetafield), U(callmeta), U(argerror), U(checknumber), U(optnumber),
U(checkinteger), U(checkstack), U(checktype), U(checkany),
U(newmetatable), U(setmetatable), U(testudata), U(checkudata), U(where),
U(error), U(fileresult), U(execresult), U(ref), U(unref), U(loadstring),
U(newstate), U(setfuncs), U(buffinit), U(addlstring), U(addstring),
U(addvalue), U(pushresult),
#if LUA_VERSION_NUM >= 502
P(absindex), P(arith), P(callk), P(compare), P(getctx), P(getglobal), P(getuservalue),
P(len), P(pcallk), P(pushunsigned), P(rawgetp), P(rawlen), P(rawsetp), P(setglobal),
P(iscfunction), P(setuservalue), P(tounsignedx), P(yieldk),
U(checkversion_), U(tolstring), U(checkunsigned), U(len), U(getsubtable), U(prepbuffsize),
U(pushresultsize), U(buffinitsize), U(checklstring), U(checkoption), U(gsub), U(loadbufferx),
U(loadfilex), U(optinteger), U(optlstring), U(optunsigned), U(requiref), U(traceback)
#else
P(objlen)
#endif
};
for (int i = 0; i < sizeof(nodes) / sizeof(lua_function_node); ++i) {
if (strcmp(nodes[i].symbol, symbol) == 0)
return nodes[i].address;
}
return NULL;
}
static int f_load_native_plugin(lua_State *L) {
char entrypoint_name[512]; entrypoint_name[sizeof(entrypoint_name) - 1] = '\0';
int result;
const char *name = luaL_checkstring(L, 1);
const char *path = luaL_checkstring(L, 2);
void *library = SDL_LoadObject(path);
if (!library)
return luaL_error(L, "Unable to load %s: %s", name, SDL_GetError());
lua_getglobal(L, "package");
lua_getfield(L, -1, "native_plugins");
lua_pushlightuserdata(L, library);
lua_setfield(L, -2, name);
lua_pop(L, 1);
const char *basename = strrchr(name, '.');
basename = !basename ? name : basename + 1;
snprintf(entrypoint_name, sizeof(entrypoint_name), "luaopen_lite_xl_%s", basename);
int (*ext_entrypoint) (lua_State *L, void*) = SDL_LoadFunction(library, entrypoint_name);
if (!ext_entrypoint) {
snprintf(entrypoint_name, sizeof(entrypoint_name), "luaopen_%s", basename);
int (*entrypoint)(lua_State *L) = SDL_LoadFunction(library, entrypoint_name);
if (!entrypoint)
return luaL_error(L, "Unable to load %s: Can't find %s(lua_State *L, void *XL)", name, entrypoint_name);
result = entrypoint(L);
} else {
result = ext_entrypoint(L, api_require);
}
if (!result)
return luaL_error(L, "Unable to load %s: entrypoint must return a value", name);
return result;
}
static int f_watch_dir(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
const int recursive = lua_toboolean(L, 2);
// uint32_t dmon_flags = (recursive ? DMON_WATCHFLAGS_RECURSIVE : 0);
// dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL);
// if (watch_id.id == 0) { luaL_error(L, "directory monitoring watch failed"); }
// lua_pushnumber(L, watch_id.id);
lua_pushnumber(L, 0);
uint32_t dmon_flags = (recursive ? DMON_WATCHFLAGS_RECURSIVE : 0);
dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL);
if (watch_id.id == 0) { luaL_error(L, "directory monitoring watch failed"); }
lua_pushnumber(L, watch_id.id);
return 1;
}
#if __linux__
static int f_watch_dir_add(lua_State *L) {
// dmon_watch_id watch_id;
// watch_id.id = luaL_checkinteger(L, 1);
// const char *subdir = luaL_checkstring(L, 2);
// lua_pushboolean(L, dmon_watch_add(watch_id, subdir));
dmon_watch_id watch_id;
watch_id.id = luaL_checkinteger(L, 1);
const char *subdir = luaL_checkstring(L, 2);
lua_pushboolean(L, dmon_watch_add(watch_id, subdir));
return 1;
}
static int f_watch_dir_rm(lua_State *L) {
// dmon_watch_id watch_id;
// watch_id.id = luaL_checkinteger(L, 1);
// const char *subdir = luaL_checkstring(L, 2);
// lua_pushboolean(L, dmon_watch_rm(watch_id, subdir));
dmon_watch_id watch_id;
watch_id.id = luaL_checkinteger(L, 1);
const char *subdir = luaL_checkstring(L, 2);
lua_pushboolean(L, dmon_watch_rm(watch_id, subdir));
return 1;
}
#endif
@ -916,13 +772,11 @@ static const luaL_Reg lib[] = {
{ "exec", f_exec },
{ "fuzzy_match", f_fuzzy_match },
{ "set_window_opacity", f_set_window_opacity },
{ "load_native_plugin", f_load_native_plugin },
{ "watch_dir", f_watch_dir },
{ "path_compare", f_path_compare },
#if __linux__
{ "watch_dir_add", f_watch_dir_add },
{ "watch_dir_rm", f_watch_dir_rm },
{ "get_fs_type", f_get_fs_type },
#endif
{ NULL, NULL }
};
@ -932,4 +786,3 @@ int luaopen_system(lua_State *L) {
luaL_newlib(L, lib);
return 1;
}

View File

@ -1,5 +1,5 @@
#import <Foundation/Foundation.h>
#include <lua.h>
#include "lua.h"
#ifdef MACOS_USE_BUNDLE
void set_macos_bundle_resources(lua_State *L)

View File

@ -3,11 +3,8 @@
#include <SDL.h>
#ifndef __amigaos4__
#define DMON_IMPL
#include "dmon.h"
#include "dmon_extra.h"
#endif
#include "dirmonitor.h"
@ -34,14 +31,14 @@ static void send_sdl_event(dmon_watch_id watch_id, dmon_action action, const cha
}
void dirmonitor_init() {
//dmon_init();
dmon_init();
/* In theory we should register our user event but since we
have just one type of user event this is not really needed. */
/* sdl_dmon_event_type = SDL_RegisterEvents(1); */
}
void dirmonitor_deinit() {
//dmon_deinit();
dmon_deinit();
}
void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const char *rootdir,
@ -51,11 +48,13 @@ void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const
(void) user;
switch (action) {
case DMON_ACTION_MOVE:
//send_sdl_event(watch_id, DMON_ACTION_DELETE, oldfilepath);
//send_sdl_event(watch_id, DMON_ACTION_CREATE, filepath);
send_sdl_event(watch_id, DMON_ACTION_DELETE, oldfilepath);
send_sdl_event(watch_id, DMON_ACTION_CREATE, filepath);
break;
//default:
//send_sdl_event(watch_id, action, filepath);
case DMON_ACTION_MODIFY:
break;
default:
send_sdl_event(watch_id, action, filepath);
}
}

View File

@ -4,7 +4,6 @@
#include <stdint.h>
#include "dmon.h"
#include "dmon_extra.h"
void dirmonitor_init();
void dirmonitor_deinit();

View File

@ -1,6 +1,3 @@
#ifndef __DMON_H__
#define __DMON_H__
//
// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved.
// License: https://github.com/septag/dmon#license-bsd-2-clause
@ -71,9 +68,9 @@
// 1.1.1 Minor fixes, eliminate gcc/clang warnings with -Wall
// 1.1.2 Eliminate some win32 dead code
// 1.1.3 Fixed select not resetting causing high cpu usage on linux
// 1.2.1 inotify (linux) fixes and improvements, added extra functionality header for linux
// to manually add/remove directories manually to the watch handle, in case of large file sets
//
#ifndef __DMON_H__
#define __DMON_H__
#include <stdbool.h>
#include <stdint.h>
@ -117,6 +114,8 @@ DMON_API_DECL dmon_watch_id dmon_watch(const char* rootdir,
const char* oldfilepath, void* user),
uint32_t flags, void* user_data);
DMON_API_DECL void dmon_unwatch(dmon_watch_id id);
DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir);
DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir);
#ifdef __cplusplus
}
@ -137,9 +136,6 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id);
#elif defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__)
# undef DMON_OS_MACOS
# define DMON_OS_MACOS __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
#elif defined(__amigaos4__)
# undef DMON_OS_AMIGAOS4
# define DMON_OS_AMIGAOS4 1
#else
# define DMON_OS 0
# error "unsupported platform"
@ -178,7 +174,6 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id);
# include <sys/time.h>
# include <sys/stat.h>
# include <dispatch/dispatch.h>
#elif DMON_OS_AMIGAOS4
#endif
#ifndef DMON_MALLOC
@ -327,7 +322,6 @@ _DMON_PRIVATE char* dmon__strcat(char* dst, int dst_sz, const char* src)
// stretchy buffer: https://github.com/nothings/stb/blob/master/stretchy_buffer.h
#define stb_sb_free(a) ((a) ? DMON_FREE(stb__sbraw(a)),0 : 0)
#define stb_sb_push(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v))
#define stb_sb_pop(a) (stb__sbn(a)--)
#define stb_sb_count(a) ((a) ? stb__sbn(a) : 0)
#define stb_sb_add(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)])
#define stb_sb_last(a) ((a)[stb__sbn(a)-1])
@ -769,6 +763,126 @@ _DMON_PRIVATE void dmon__watch_recursive(const char* dirname, int fd, uint32_t m
closedir(dir);
}
DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
{
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
bool skip_lock = pthread_self() == _dmon.thread_handle;
if (!skip_lock)
pthread_mutex_lock(&_dmon.mutex);
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
// check if the directory exists
// if watchdir contains absolute/root-included path, try to strip the rootdir from it
// else, we assume that watchdir is correct, so save it as it is
struct stat st;
dmon__watch_subdir subdir;
if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) {
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) {
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir));
}
} else {
char fullpath[DMON_MAX_PATH];
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
dmon__strcat(fullpath, sizeof(fullpath), watchdir);
if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) {
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
}
int dirlen = (int)strlen(subdir.rootdir);
if (subdir.rootdir[dirlen - 1] != '/') {
subdir.rootdir[dirlen] = '/';
subdir.rootdir[dirlen + 1] = '\0';
}
// check that the directory is not already added
for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) {
if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) {
_DMON_LOG_ERRORF("Error watching directory '%s', because it is already added.", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
}
const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY;
char fullpath[DMON_MAX_PATH];
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir);
int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask);
if (wd == -1) {
_DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watchdir, errno);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
stb_sb_push(watch->subdirs, subdir);
stb_sb_push(watch->wds, wd);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return true;
}
DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir)
{
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
bool skip_lock = pthread_self() == _dmon.thread_handle;
if (!skip_lock)
pthread_mutex_lock(&_dmon.mutex);
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
char subdir[DMON_MAX_PATH];
dmon__strcpy(subdir, sizeof(subdir), watchdir);
if (strstr(subdir, watch->rootdir) == subdir) {
dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir));
}
int dirlen = (int)strlen(subdir);
if (subdir[dirlen - 1] != '/') {
subdir[dirlen] = '/';
subdir[dirlen + 1] = '\0';
}
int i, c = stb_sb_count(watch->subdirs);
for (i = 0; i < c; i++) {
if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) {
break;
}
}
if (i >= c) {
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
inotify_rm_watch(watch->fd, watch->wds[i]);
for (int j = i; j < c - 1; j++) {
memcpy(watch->subdirs + j, watch->subdirs + j + 1, sizeof(dmon__watch_subdir));
memcpy(watch->wds + j, watch->wds + j + 1, sizeof(int));
}
stb__sbraw(watch->subdirs)[1] = c - 1;
stb__sbraw(watch->wds)[1] = c - 1;
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return true;
}
_DMON_PRIVATE const char* dmon__find_subdir(const dmon__watch_state* watch, int wd)
{
const int* wds = watch->wds;
@ -931,11 +1045,8 @@ _DMON_PRIVATE void dmon__inotify_process_events(void)
dmon__strcat(watchdir, sizeof(watchdir), "/");
uint32_t mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY;
int wd = inotify_add_watch(watch->fd, watchdir, mask);
// Removing the assertion below because it was giving errors for some reason
// when building a new package.
// _DMON_UNUSED(wd);
// DMON_ASSERT(wd != -1);
if (wd == -1) continue;
_DMON_UNUSED(wd);
DMON_ASSERT(wd != -1);
dmon__watch_subdir subdir;
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);

77
src/fontdesc.c Normal file
View File

@ -0,0 +1,77 @@
#include <stddef.h>
#include <stdlib.h>
#include "fontdesc.h"
#include "renderer.h"
int font_desc_alloc_size(const char *filename) {
return offsetof(FontDesc, filename) + strlen(filename) + 1;
}
void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsigned int font_options) {
memcpy(font_desc->filename, filename, strlen(filename) + 1);
font_desc->size = size;
font_desc->options = font_options;
font_desc->tab_size = 4;
font_desc->cache_length = 0;
font_desc->cache_last_index = 0; /* Normally no need to initialize. */
}
void font_desc_clear(FontDesc *font_desc) {
for (int i = 0; i < font_desc->cache_length; i++) {
ren_free_font(font_desc->cache[i].font);
}
font_desc->cache_length = 0;
font_desc->cache_last_index = 0;
}
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size) {
font_desc->tab_size = tab_size;
for (int i = 0; i < font_desc->cache_length; i++) {
ren_set_font_tab_size(font_desc->cache[i].font, tab_size);
}
}
int font_desc_get_tab_size(FontDesc *font_desc) {
return font_desc->tab_size;
}
static void load_scaled_font(FontDesc *font_desc, int index, int scale) {
RenFont *font = ren_load_font(font_desc->filename, scale * font_desc->size, font_desc->options);
if (!font) {
/* The font was able to load when initially loaded using renderer.load.font.
If now is no longer available we just abort the application. */
fprintf(stderr, "Fatal error: unable to load font %s. Application will abort.\n",
font_desc->filename);
exit(1);
}
font_desc->cache[index].font = font;
font_desc->cache[index].scale = scale;
}
RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale) {
int index = -1;
for (int i = 0; i < font_desc->cache_length; i++) {
if (font_desc->cache[i].scale == scale) {
index = i;
break;
}
}
if (index < 0) {
index = font_desc->cache_length;
if (index < FONT_CACHE_ARRAY_MAX) {
load_scaled_font(font_desc, index, scale);
font_desc->cache_length = index + 1;
} else {
// FIXME: should not print into the stderr or stdout.
fprintf(stderr, "Warning: max array of font scale reached.\n");
index = (font_desc->cache_last_index == 0 ? 1 : 0);
ren_free_font(font_desc->cache[index].font);
load_scaled_font(font_desc, index, scale);
}
}
font_desc->cache_last_index = index;
return font_desc->cache[index].font;
}

33
src/fontdesc.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef FONT_DESC_H
#define FONT_DESC_H
typedef struct RenFont RenFont;
struct FontInstance {
RenFont *font;
short int scale;
};
typedef struct FontInstance FontInstance;
#define FONT_CACHE_ARRAY_MAX 2
struct FontDesc {
float size;
unsigned int options;
short int tab_size;
FontInstance cache[FONT_CACHE_ARRAY_MAX];
short int cache_length;
short int cache_last_index; /* More recently used instance. */
char filename[0];
};
typedef struct FontDesc FontDesc;
void font_desc_init(FontDesc *font_desc, const char *filename, float size, unsigned int font_options);
int font_desc_alloc_size(const char *filename);
int font_desc_get_tab_size(FontDesc *font_desc);
void font_desc_set_tab_size(FontDesc *font_desc, int tab_size);
void font_desc_clear(FontDesc *font_desc);
RenFont *font_desc_get_font_at_scale(FontDesc *font_desc, int scale);
#endif

View File

@ -12,8 +12,6 @@
#include <signal.h>
#elif __APPLE__
#include <mach-o/dyld.h>
#elif __amigaos4__
#include "platform/amigaos4.h"
#endif
#include "dirmonitor.h"
@ -36,7 +34,8 @@ static void get_exe_filename(char *buf, int sz) {
int len = GetModuleFileName(NULL, buf, sz - 1);
buf[len] = '\0';
#elif __linux__
char path[] = "/proc/self/exe";
char path[512];
sprintf(path, "/proc/%d/exe", getpid());
int len = readlink(path, buf, sz - 1);
buf[len] = '\0';
#elif __APPLE__
@ -47,8 +46,6 @@ static void get_exe_filename(char *buf, int sz) {
char exepath[size];
_NSGetExecutablePath(exepath, &size);
realpath(exepath, buf);
#elif __amigaos4__
strcpy(buf, _fullpath("./lite"));
#else
strcpy(buf, "./lite");
#endif
@ -112,13 +109,11 @@ int main(int argc, char **argv) {
SDL_DisplayMode dm;
SDL_GetCurrentDisplayMode(0, &dm);
// dirmonitor_init();
dirmonitor_init();
window = SDL_CreateWindow(
"", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
SDL_SetWindowDisplayMode(window, &dm);
init_window_icon();
ren_init(window);
@ -160,9 +155,6 @@ init_lua:
" HOME = os.getenv('" LITE_OS_HOME "')\n"
" local exedir = EXEFILE:match('^(.*)" LITE_PATHSEP_PATTERN LITE_NONPATHSEP_PATTERN "$')\n"
" local prefix = exedir:match('^(.*)" LITE_PATHSEP_PATTERN "bin$')\n"
" if not HOME then\n"
" HOME = exedir\n"
" end\n"
" dofile((MACOS_RESOURCES or (prefix and prefix .. '/share/lite-xl' or exedir .. '/data')) .. '/core/start.lua')\n"
" core = require(os.getenv('LITE_XL_RUNTIME') or 'core')\n"
" core.init()\n"
@ -201,8 +193,7 @@ init_lua:
lua_close(L);
ren_free_window_resources();
// dirmonitor_deinit();
dirmonitor_deinit();
return EXIT_SUCCESS;
}

View File

@ -1,12 +1,15 @@
lite_sources = [
'api/api.c',
'api/cp_replace.c',
'api/renderer.c',
'api/renderer_font.c',
'api/regex.c',
'api/system.c',
'api/process.c',
'dirmonitor.c',
'renderer.c',
'renwindow.c',
'fontdesc.c',
'rencache.c',
'main.c',
]
@ -19,14 +22,15 @@ elif host_machine.system() == 'darwin'
lite_sources += 'bundle_open.m'
endif
lite_includes += include_directories('.')
lite_include = include_directories('.')
executable('lite-xl',
lite_sources + lite_rc,
include_directories: lite_includes,
include_directories: [lite_include, font_renderer_include],
dependencies: lite_deps,
c_args: lite_cargs,
objc_args: lite_cargs,
link_with: libfontrenderer,
link_args: lite_link_args,
install_dir: lite_bindir,
install: true,

View File

@ -1,61 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "amigaos4.h"
static char *getFullPath(const char *path)
{
char *appPath = malloc(sizeof(char) * MAX_DOS_NAME);
BPTR pathLock = Lock(path, SHARED_LOCK);
if (pathLock)
{
NameFromLock(pathLock, appPath, sizeof(char) * MAX_DOS_NAME);
UnLock(pathLock);
return appPath;
}
return NULL;
}
static char *getCurrentPath(void)
{
char *appPath = malloc(sizeof(char) * MAX_DOS_NAME);
BPTR pathLock = GetCurrentDir();
if (pathLock)
{
NameFromLock(pathLock, appPath, sizeof(char) * MAX_DOS_NAME);
return appPath;
}
return NULL;
}
char *_fullpath(const char *path)
{
static char prvPath[MAX_DOS_NAME];
static char result[MAX_DOS_NAME];
if (!strcmp(path, prvPath))
{
return result;
}
strcpy(prvPath, path);
if (!strcmp(path, "./lite"))
{
// TODO: Add code to get the name of the executable
strcpy(result, getFullPath("PROGDIR:lite"));
return result;
}
if (!strcmp(path, "."))
{
strcpy(result, getCurrentPath());
return result;
}
strcpy(result, getFullPath(path));
return result;
}

View File

@ -1,17 +0,0 @@
#ifndef _AMIGAOS4_H
#define _AMIGAOS4_H
#include <proto/dos.h>
#include <proto/exec.h>
#define VSTRING "Lite XL OS4 2.0.3r1 (30.04.2022)"
#define VERSTAG "\0$VER: " VSTRING
static CONST_STRPTR stack USED = "$STACK:102400";
static CONST_STRPTR version USED = VERSTAG;
char *_fullpath(const char *);
#endif

View File

@ -1,8 +1,6 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdalign.h>
#include <lauxlib.h>
#include "rencache.h"
@ -18,19 +16,32 @@
#define COMMAND_BUF_SIZE (1024 * 512)
#define COMMAND_BARE_SIZE offsetof(Command, text)
enum { SET_CLIP, DRAW_TEXT, DRAW_RECT };
enum { SET_CLIP, DRAW_TEXT, DRAW_RECT, DRAW_TEXT_SUBPIXEL };
typedef struct {
int8_t type;
int8_t tab_size;
int8_t subpixel_scale;
int8_t x_subpixel_offset;
int32_t size;
RenRect rect;
RenColor color;
RenFont *fonts[FONT_FALLBACK_MAX];
float text_x;
FontDesc *font_desc;
CPReplaceTable *replacements;
RenColor replace_color;
char text[0];
} 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_buf2[CELLS_X * CELLS_Y];
static unsigned *cells_prev = cells_buf1;
@ -41,9 +52,36 @@ static int command_buf_idx;
static RenRect screen_rect;
static bool show_debug;
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 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 */
#define HASH_INITIAL 2166136261
@ -86,8 +124,6 @@ static RenRect merge_rects(RenRect a, RenRect b) {
static Command* push_command(int type, int size) {
size_t alignment = alignof(max_align_t) - 1;
size = (size + alignment) & ~alignment;
Command *cmd = (Command*) (command_buf + command_buf_idx);
int n = command_buf_idx + size;
if (n > COMMAND_BUF_SIZE) {
@ -124,9 +160,7 @@ void rencache_set_clip_rect(RenRect rect) {
void rencache_draw_rect(RenRect rect, RenColor color) {
if (!rects_overlap(screen_rect, rect) || rect.width == 0 || rect.height == 0) {
return;
}
if (!rects_overlap(screen_rect, rect)) { return; }
Command *cmd = push_command(DRAW_RECT, COMMAND_BARE_SIZE);
if (cmd) {
cmd->rect = rect;
@ -134,23 +168,35 @@ void rencache_draw_rect(RenRect rect, RenColor color) {
}
}
float rencache_draw_text(lua_State *L, RenFont **fonts, const char *text, float 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)
{
float width = ren_font_group_get_width(fonts, text);
RenRect rect = { x, y, (int)width, ren_font_group_get_height(fonts) };
if (rects_overlap(screen_rect, rect)) {
int subpixel_scale;
int w_subpixel = ren_get_font_width(font_desc, text, &subpixel_scale);
RenRect rect;
rect.x = (draw_subpixel ? ren_font_subpixel_round(x, subpixel_scale, -1) : x);
rect.y = y;
rect.width = ren_font_subpixel_round(w_subpixel, subpixel_scale, 0);
rect.height = ren_get_font_height(font_desc);
if (rects_overlap(screen_rect, rect) && font_refs_add(L, font_desc, font_index) >= 0) {
int sz = strlen(text) + 1;
Command *cmd = push_command(DRAW_TEXT, COMMAND_BARE_SIZE + sz);
Command *cmd = push_command(draw_subpixel ? DRAW_TEXT_SUBPIXEL : DRAW_TEXT, COMMAND_BARE_SIZE + sz);
if (cmd) {
memcpy(cmd->text, text, sz);
cmd->color = color;
memcpy(cmd->fonts, fonts, sizeof(RenFont*)*FONT_FALLBACK_MAX);
cmd->font_desc = font_desc;
cmd->rect = rect;
cmd->text_x = x;
cmd->tab_size = ren_font_group_get_tab_size(fonts);
cmd->subpixel_scale = (draw_subpixel ? subpixel_scale : 1);
cmd->x_subpixel_offset = x - subpixel_scale * rect.x;
cmd->tab_size = font_desc_get_tab_size(font_desc);
cmd->replacements = replacements;
cmd->replace_color = replace_color;
}
}
return x + width;
return x + (draw_subpixel ? w_subpixel : rect.width);
}
@ -168,6 +214,7 @@ void rencache_begin_frame(lua_State *L) {
screen_rect.height = h;
rencache_invalidate();
}
font_refs_clear(L);
}
@ -254,8 +301,15 @@ void rencache_end_frame(lua_State *L) {
ren_draw_rect(cmd->rect, cmd->color);
break;
case DRAW_TEXT:
ren_font_group_set_tab_size(cmd->fonts, cmd->tab_size);
ren_draw_text(cmd->fonts, cmd->text, cmd->text_x, cmd->rect.y, cmd->color);
font_desc_set_tab_size(cmd->font_desc, cmd->tab_size);
ren_draw_text(cmd->font_desc, cmd->text, cmd->rect.x, cmd->rect.y, cmd->color,
cmd->replacements, cmd->replace_color);
break;
case DRAW_TEXT_SUBPIXEL:
font_desc_set_tab_size(cmd->font_desc, cmd->tab_size);
ren_draw_text_subpixel(cmd->font_desc, cmd->text,
cmd->subpixel_scale * cmd->rect.x + cmd->x_subpixel_offset, cmd->rect.y, cmd->color,
cmd->replacements, cmd->replace_color);
break;
}
}

View File

@ -5,14 +5,13 @@
#include <lua.h>
#include "renderer.h"
void rencache_show_debug(bool enable);
void rencache_set_clip_rect(RenRect rect);
void rencache_draw_rect(RenRect rect, RenColor color);
float rencache_draw_text(lua_State *L, RenFont **font,
const char *text, float x, int y, RenColor color);
void rencache_invalidate(void);
void rencache_begin_frame(lua_State *L);
void rencache_end_frame(lua_State *L);
void rencache_show_debug(bool enable);
void rencache_set_clip_rect(RenRect rect);
void rencache_draw_rect(RenRect rect, 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);
void rencache_invalidate(void);
void rencache_begin_frame(lua_State *L);
void rencache_end_frame(lua_State *L);
#endif

View File

@ -1,22 +1,38 @@
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <assert.h>
#include <math.h>
#include <ft2build.h>
#include <freetype/ftlcdfil.h>
#include <freetype/ftoutln.h>
#include FT_FREETYPE_H
#include "font_renderer.h"
#include "renderer.h"
#include "renwindow.h"
#define MAX_GLYPHSET 256
#define MAX_LOADABLE_GLYPHSETS 1024
#define SUBPIXEL_BITMAPS_CACHED 3
#define REPLACEMENT_CHUNK_SIZE 8
struct RenImage {
RenColor *pixels;
int width, height;
};
struct GlyphSet {
FR_Bitmap *image;
FR_Bitmap_Glyph_Metrics glyphs[256];
};
typedef struct GlyphSet GlyphSet;
/* The field "padding" below must be there just before GlyphSet *sets[MAX_GLYPHSET]
because the field "sets" can be indexed and writted with an index -1. For this
reason the "padding" field must be there but is never explicitly used. */
struct RenFont {
GlyphSet *padding;
GlyphSet *sets[MAX_GLYPHSET];
float size;
int height;
int space_advance;
FR_Renderer *renderer;
};
static RenWindow window_renderer = {0};
static FT_Library library;
static void* check_alloc(void *ptr) {
if (!ptr) {
@ -26,29 +42,6 @@ static void* check_alloc(void *ptr) {
return ptr;
}
/************************* Fonts *************************/
typedef struct {
unsigned short x0, x1, y0, y1, loaded;
short bitmap_left, bitmap_top;
float xadvance;
} GlyphMetric;
typedef struct {
SDL_Surface* surface;
GlyphMetric metrics[MAX_GLYPHSET];
} GlyphSet;
typedef struct RenFont {
FT_Face face;
GlyphSet* sets[SUBPIXEL_BITMAPS_CACHED][MAX_LOADABLE_GLYPHSETS];
float size, space_advance, tab_advance;
short max_height;
ERenFontAntialiasing antialiasing;
ERenFontHinting hinting;
unsigned char style;
char path[0];
} RenFont;
static const char* utf8_to_codepoint(const char *p, unsigned *dst) {
unsigned res, n;
@ -66,311 +59,43 @@ static const char* utf8_to_codepoint(const char *p, unsigned *dst) {
return p + 1;
}
static int font_set_load_options(RenFont* font) {
int load_target = font->antialiasing == FONT_ANTIALIASING_NONE ? FT_LOAD_TARGET_MONO
: (font->hinting == FONT_HINTING_SLIGHT ? FT_LOAD_TARGET_LIGHT : FT_LOAD_TARGET_NORMAL);
int hinting = font->hinting == FONT_HINTING_NONE ? FT_LOAD_NO_HINTING : FT_LOAD_FORCE_AUTOHINT;
return load_target | hinting;
void ren_cp_replace_init(CPReplaceTable *rep_table) {
rep_table->size = 0;
rep_table->replacements = NULL;
}
static int font_set_render_options(RenFont* font) {
if (font->antialiasing == FONT_ANTIALIASING_NONE)
return FT_RENDER_MODE_MONO;
if (font->antialiasing == FONT_ANTIALIASING_SUBPIXEL) {
unsigned char weights[] = { 0x10, 0x40, 0x70, 0x40, 0x10 } ;
switch (font->hinting) {
case FONT_HINTING_NONE: FT_Library_SetLcdFilter(library, FT_LCD_FILTER_NONE); break;
case FONT_HINTING_SLIGHT:
case FONT_HINTING_FULL: FT_Library_SetLcdFilterWeights(library, weights); break;
}
return FT_RENDER_MODE_LCD;
} else {
switch (font->hinting) {
case FONT_HINTING_NONE: return FT_RENDER_MODE_NORMAL; break;
case FONT_HINTING_SLIGHT: return FT_RENDER_MODE_LIGHT; break;
case FONT_HINTING_FULL: return FT_RENDER_MODE_LIGHT; break;
void ren_cp_replace_free(CPReplaceTable *rep_table) {
free(rep_table->replacements);
}
void ren_cp_replace_add(CPReplaceTable *rep_table, const char *src, const char *dst) {
int table_size = rep_table->size;
if (table_size % REPLACEMENT_CHUNK_SIZE == 0) {
CPReplace *old_replacements = rep_table->replacements;
const int new_size = (table_size / REPLACEMENT_CHUNK_SIZE + 1) * REPLACEMENT_CHUNK_SIZE;
rep_table->replacements = malloc(new_size * sizeof(CPReplace));
if (!rep_table->replacements) {
rep_table->replacements = old_replacements;
return;
}
memcpy(rep_table->replacements, old_replacements, table_size * sizeof(CPReplace));
free(old_replacements);
}
return 0;
CPReplace *rep = &rep_table->replacements[table_size];
utf8_to_codepoint(src, &rep->codepoint_src);
utf8_to_codepoint(dst, &rep->codepoint_dst);
rep_table->size = table_size + 1;
}
static int font_set_style(FT_Outline* outline, int x_translation, unsigned char style) {
FT_Outline_Translate(outline, x_translation, 0 );
if (style & FONT_STYLE_BOLD)
FT_Outline_EmboldenXY(outline, 1 << 5, 0);
if (style & FONT_STYLE_ITALIC) {
FT_Matrix matrix = { 1 << 16, 1 << 14, 0, 1 << 16 };
FT_Outline_Transform(outline, &matrix);
}
return 0;
}
static void font_load_glyphset(RenFont* font, int idx) {
unsigned int render_option = font_set_render_options(font), load_option = font_set_load_options(font);
int bitmaps_cached = font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? SUBPIXEL_BITMAPS_CACHED : 1;
unsigned int byte_width = font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? 3 : 1;
for (int j = 0, pen_x = 0; j < bitmaps_cached; ++j) {
GlyphSet* set = check_alloc(calloc(1, sizeof(GlyphSet)));
font->sets[j][idx] = set;
for (int i = 0; i < MAX_GLYPHSET; ++i) {
int glyph_index = FT_Get_Char_Index(font->face, i + idx * MAX_GLYPHSET);
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option | FT_LOAD_BITMAP_METRICS_ONLY) || font_set_style(&font->face->glyph->outline, j * (64 / SUBPIXEL_BITMAPS_CACHED), font->style) || FT_Render_Glyph(font->face->glyph, render_option))
continue;
FT_GlyphSlot slot = font->face->glyph;
int glyph_width = slot->bitmap.width / byte_width;
if (font->antialiasing == FONT_ANTIALIASING_NONE)
glyph_width *= 8;
set->metrics[i] = (GlyphMetric){ pen_x, pen_x + glyph_width, 0, slot->bitmap.rows, true, slot->bitmap_left, slot->bitmap_top, (slot->advance.x + slot->lsb_delta - slot->rsb_delta) / 64.0f};
pen_x += glyph_width;
font->max_height = slot->bitmap.rows > font->max_height ? slot->bitmap.rows : font->max_height;
}
if (pen_x == 0)
continue;
set->surface = check_alloc(SDL_CreateRGBSurface(0, pen_x, font->max_height, font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? 24 : 8, 0, 0, 0, 0));
unsigned char* pixels = set->surface->pixels;
for (int i = 0; i < MAX_GLYPHSET; ++i) {
int glyph_index = FT_Get_Char_Index(font->face, i + idx * MAX_GLYPHSET);
if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option))
continue;
FT_GlyphSlot slot = font->face->glyph;
font_set_style(&slot->outline, (64 / bitmaps_cached) * j, font->style);
if (FT_Render_Glyph(slot, render_option))
continue;
for (int line = 0; line < slot->bitmap.rows; ++line) {
int target_offset = set->surface->pitch * line + set->metrics[i].x0 * byte_width;
int source_offset = line * slot->bitmap.pitch;
if (font->antialiasing == FONT_ANTIALIASING_NONE) {
for (int column = 0; column < slot->bitmap.width; ++column) {
int current_source_offset = source_offset + (column / 8);
int source_pixel = slot->bitmap.buffer[current_source_offset];
pixels[++target_offset] = ((source_pixel >> (7 - (column % 8))) & 0x1) << 7;
}
} else
memcpy(&pixels[target_offset], &slot->bitmap.buffer[source_offset], slot->bitmap.width);
}
}
}
}
static GlyphSet* font_get_glyphset(RenFont* font, unsigned int codepoint, int subpixel_idx) {
int idx = (codepoint >> 8) % MAX_LOADABLE_GLYPHSETS;
if (!font->sets[font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? subpixel_idx : 0][idx])
font_load_glyphset(font, idx);
return font->sets[font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? subpixel_idx : 0][idx];
}
static RenFont* font_group_get_glyph(GlyphSet** set, GlyphMetric** metric, RenFont** fonts, unsigned int codepoint, int bitmap_index) {
if (bitmap_index < 0)
bitmap_index += SUBPIXEL_BITMAPS_CACHED;
for (int i = 0; i < FONT_FALLBACK_MAX && fonts[i]; ++i) {
*set = font_get_glyphset(fonts[i], codepoint, bitmap_index);
*metric = &(*set)->metrics[codepoint % 256];
if ((*metric)->loaded || codepoint < 0xFF)
return fonts[i];
}
if (!(*metric)->loaded && codepoint > 0xFF && codepoint != 0x25A1)
return font_group_get_glyph(set, metric, fonts, 0x25A1, bitmap_index);
return fonts[0];
}
RenFont* ren_font_load(const char* path, float size, ERenFontAntialiasing antialiasing, ERenFontHinting hinting, unsigned char style) {
FT_Face face;
if (FT_New_Face( library, path, 0, &face))
return NULL;
const int surface_scale = renwin_surface_scale(&window_renderer);
if (FT_Set_Pixel_Sizes(face, 0, (int)(size*surface_scale)))
goto failure;
int len = strlen(path);
RenFont* font = check_alloc(calloc(1, sizeof(RenFont) + len + 1));
strcpy(font->path, path);
font->face = face;
font->size = size;
font->antialiasing = antialiasing;
font->hinting = hinting;
font->style = style;
font->space_advance = (int)font_get_glyphset(font, ' ', 0)->metrics[' '].xadvance;
font->tab_advance = font->space_advance * 2;
return font;
failure:
FT_Done_Face(face);
return NULL;
}
RenFont* ren_font_copy(RenFont* font, float size) {
return ren_font_load(font->path, size, font->antialiasing, font->hinting, font->style);
}
void ren_font_free(RenFont* font) {
for (int i = 0; i < SUBPIXEL_BITMAPS_CACHED; ++i) {
for (int j = 0; j < MAX_GLYPHSET; ++j) {
if (font->sets[i][j]) {
if (font->sets[i][j]->surface)
SDL_FreeSurface(font->sets[i][j]->surface);
free(font->sets[i][j]);
}
}
}
FT_Done_Face(font->face);
free(font);
}
void ren_font_group_set_tab_size(RenFont **fonts, int n) {
for (int j = 0; j < FONT_FALLBACK_MAX && fonts[j]; ++j) {
for (int i = 0; i < (fonts[j]->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? SUBPIXEL_BITMAPS_CACHED : 1); ++i)
font_get_glyphset(fonts[j], '\t', i)->metrics['\t'].xadvance = fonts[j]->space_advance * n;
}
}
int ren_font_group_get_tab_size(RenFont **fonts) {
return font_get_glyphset(fonts[0], '\t', 0)->metrics['\t'].xadvance / fonts[0]->space_advance;
}
float ren_font_group_get_size(RenFont **fonts) {
return fonts[0]->size;
}
int ren_font_group_get_height(RenFont **fonts) {
return fonts[0]->size + 3;
}
float ren_font_group_get_width(RenFont **fonts, const char *text) {
float width = 0;
const char* end = text + strlen(text);
GlyphMetric* metric = NULL; GlyphSet* set = NULL;
while (text < end) {
unsigned int codepoint;
text = utf8_to_codepoint(text, &codepoint);
font_group_get_glyph(&set, &metric, fonts, codepoint, 0);
width += metric->xadvance ? metric->xadvance : fonts[0]->space_advance;
}
const int surface_scale = renwin_surface_scale(&window_renderer);
return width / surface_scale;
}
float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor color) {
SDL_Surface *surface = renwin_get_surface(&window_renderer);
const RenRect clip = window_renderer.clip;
const int surface_scale = renwin_surface_scale(&window_renderer);
float pen_x = x * surface_scale;
y *= surface_scale;
int bytes_per_pixel = surface->format->BytesPerPixel;
const char* end = text + strlen(text);
unsigned char* destination_pixels = surface->pixels;
int clip_end_x = clip.x + clip.width, clip_end_y = clip.y + clip.height;
while (text < end) {
unsigned int codepoint, r, g, b;
text = utf8_to_codepoint(text, &codepoint);
GlyphSet* set = NULL; GlyphMetric* metric = NULL;
RenFont* font = font_group_get_glyph(&set, &metric, fonts, codepoint, (int)(fmod(pen_x, 1.0) * SUBPIXEL_BITMAPS_CACHED));
int start_x = floor(pen_x) + metric->bitmap_left;
int end_x = (metric->x1 - metric->x0) + start_x;
int glyph_end = metric->x1, glyph_start = metric->x0;
if (!metric->loaded && codepoint > 0xFF)
ren_draw_rect((RenRect){ start_x + 1, y, font->space_advance - 1, ren_font_group_get_height(fonts) }, color);
if (set->surface && color.a > 0 && end_x >= clip.x && start_x < clip_end_x) {
unsigned char* source_pixels = set->surface->pixels;
for (int line = metric->y0; line < metric->y1; ++line) {
int target_y = line + y - metric->y0 - metric->bitmap_top + font->size * surface_scale;
if (target_y < clip.y)
continue;
if (target_y >= clip_end_y)
break;
if (start_x + (glyph_end - glyph_start) >= clip_end_x)
glyph_end = glyph_start + (clip_end_x - start_x);
else if (start_x < clip.x) {
int offset = clip.x - start_x;
start_x += offset;
glyph_start += offset;
}
unsigned int* destination_pixel = (unsigned int*)&destination_pixels[surface->pitch * target_y + start_x * bytes_per_pixel];
unsigned char* source_pixel = &source_pixels[line * set->surface->pitch + glyph_start * (font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? 3 : 1)];
for (int x = glyph_start; x < glyph_end; ++x) {
unsigned int destination_color = *destination_pixel;
SDL_Color dst = { (destination_color & surface->format->Rmask) >> surface->format->Rshift, (destination_color & surface->format->Gmask) >> surface->format->Gshift, (destination_color & surface->format->Bmask) >> surface->format->Bshift, (destination_color & surface->format->Amask) >> surface->format->Ashift };
SDL_Color src = { *(font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? source_pixel++ : source_pixel), *(font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? source_pixel++ : source_pixel), *source_pixel++ };
r = (color.r * src.r * color.a + dst.r * (65025 - src.r * color.a) + 32767) / 65025;
g = (color.g * src.g * color.a + dst.g * (65025 - src.g * color.a) + 32767) / 65025;
b = (color.b * src.b * color.a + dst.b * (65025 - src.b * color.a) + 32767) / 65025;
*destination_pixel++ = dst.a << surface->format->Ashift | r << surface->format->Rshift | g << surface->format->Gshift | b << surface->format->Bshift;
}
}
}
pen_x += metric->xadvance ? metric->xadvance : font->space_advance;
}
if (fonts[0]->style & FONT_STYLE_UNDERLINE)
ren_draw_rect((RenRect){ x, y / surface_scale + ren_font_group_get_height(fonts) - 1, (pen_x - x) / surface_scale, 1 }, color);
return pen_x / surface_scale;
}
/******************* Rectangles **********************/
static inline RenColor blend_pixel(RenColor dst, RenColor src) {
int ia = 0xff - src.a;
dst.r = ((src.r * src.a) + (dst.r * ia)) >> 8;
dst.g = ((src.g * src.a) + (dst.g * ia)) >> 8;
dst.b = ((src.b * src.a) + (dst.b * ia)) >> 8;
return dst;
}
void ren_draw_rect(RenRect rect, RenColor color) {
if (color.a == 0) { return; }
const int surface_scale = renwin_surface_scale(&window_renderer);
/* transforms coordinates in pixels. */
rect.x *= surface_scale;
rect.y *= surface_scale;
rect.width *= surface_scale;
rect.height *= surface_scale;
const RenRect clip = window_renderer.clip;
int x1 = rect.x < clip.x ? clip.x : rect.x;
int y1 = rect.y < clip.y ? clip.y : rect.y;
int x2 = rect.x + rect.width;
int y2 = rect.y + rect.height;
x2 = x2 > clip.x + clip.width ? clip.x + clip.width : x2;
y2 = y2 > clip.y + clip.height ? clip.y + clip.height : y2;
SDL_Surface *surface = renwin_get_surface(&window_renderer);
uint32_t *d = surface->pixels;
#ifdef __amigaos4__
d += x1 + y1 * surface->pitch/sizeof(uint32_t);
int dr = surface->pitch/sizeof(uint32_t) - (x2 - x1);
#else
d += x1 + y1 * surface->w;
int dr = surface->w - (x2 - x1);
#endif
if (color.a == 0xff) {
uint32_t translated = SDL_MapRGB(surface->format, color.r, color.g, color.b);
SDL_Rect rect = { x1, y1, x2 - x1, y2 - y1 };
SDL_FillRect(surface, &rect, translated);
} else {
RenColor current_color;
RenColor blended_color;
for (int j = y1; j < y2; j++) {
for (int i = x1; i < x2; i++, d++) {
SDL_GetRGB(*d, surface->format, &current_color.r, &current_color.g, &current_color.b);
blended_color = blend_pixel(current_color, color);
*d = SDL_MapRGB(surface->format, blended_color.r, blended_color.g, blended_color.b);
}
d += dr;
}
}
}
/*************** Window Management ****************/
void ren_free_window_resources() {
renwin_free(&window_renderer);
}
void ren_init(SDL_Window *win) {
assert(win);
int error = FT_Init_FreeType( &library );
if ( error ) {
fprintf(stderr, "internal font error when starting the application\n");
return;
}
window_renderer.window = win;
renwin_init_surface(&window_renderer);
renwin_clip_to_surface(&window_renderer);
@ -405,3 +130,274 @@ void ren_get_size(int *x, int *y) {
*y = surface->h / scale;
}
RenImage* ren_new_image(int width, int height) {
assert(width > 0 && height > 0);
RenImage *image = malloc(sizeof(RenImage) + width * height * sizeof(RenColor));
check_alloc(image);
image->pixels = (void*) (image + 1);
image->width = width;
image->height = height;
return image;
}
void ren_free_image(RenImage *image) {
free(image);
}
static GlyphSet* load_glyphset(RenFont *font, int idx) {
GlyphSet *set = check_alloc(calloc(1, sizeof(GlyphSet)));
set->image = FR_Bake_Font_Bitmap(font->renderer, font->height, idx << 8, 256, set->glyphs);
check_alloc(set->image);
return set;
}
static GlyphSet* get_glyphset(RenFont *font, int codepoint) {
int idx = (codepoint >> 8) % MAX_GLYPHSET;
if (!font->sets[idx]) {
font->sets[idx] = load_glyphset(font, idx);
}
return font->sets[idx];
}
int ren_verify_font(const char *filename) {
RenFont font[1];
font->renderer = FR_Renderer_New(0);
if (FR_Load_Font(font->renderer, filename)) {
return 1;
}
FR_Renderer_Free(font->renderer);
return 0;
}
RenFont* ren_load_font(const char *filename, float size, unsigned int renderer_flags) {
RenFont *font = NULL;
/* init font */
font = check_alloc(calloc(1, sizeof(RenFont)));
font->size = size;
unsigned int fr_renderer_flags = 0;
if ((renderer_flags & RenFontAntialiasingMask) == RenFontSubpixel) {
fr_renderer_flags |= FR_SUBPIXEL;
}
if ((renderer_flags & RenFontHintingMask) == RenFontHintingSlight) {
fr_renderer_flags |= (FR_HINTING | FR_PRESCALE_X);
} else if ((renderer_flags & RenFontHintingMask) == RenFontHintingFull) {
fr_renderer_flags |= FR_HINTING;
}
font->renderer = FR_Renderer_New(fr_renderer_flags);
if (FR_Load_Font(font->renderer, filename)) {
free(font);
return NULL;
}
font->height = FR_Get_Font_Height(font->renderer, size);
FR_Bitmap_Glyph_Metrics *gs = get_glyphset(font, ' ')->glyphs;
font->space_advance = gs[' '].xadvance;
/* make tab and newline glyphs invisible */
FR_Bitmap_Glyph_Metrics *g = get_glyphset(font, '\n')->glyphs;
g['\t'].x1 = g['\t'].x0;
g['\n'].x1 = g['\n'].x0;
return font;
}
void ren_free_font(RenFont *font) {
for (int i = 0; i < MAX_GLYPHSET; i++) {
GlyphSet *set = font->sets[i];
if (set) {
FR_Bitmap_Free(set->image);
free(set);
}
}
FR_Renderer_Free(font->renderer);
free(font);
}
void ren_set_font_tab_size(RenFont *font, int n) {
GlyphSet *set = get_glyphset(font, '\t');
set->glyphs['\t'].xadvance = font->space_advance * n;
}
int ren_get_font_tab_size(RenFont *font) {
GlyphSet *set = get_glyphset(font, '\t');
return set->glyphs['\t'].xadvance / font->space_advance;
}
/* Important: if subpixel_scale is NULL we will return width in points. Otherwise we will
return width in subpixels. */
int ren_get_font_width(FontDesc *font_desc, const char *text, int *subpixel_scale) {
int x = 0;
const char *p = text;
unsigned codepoint;
const int surface_scale = renwin_surface_scale(&window_renderer);
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
while (*p) {
p = utf8_to_codepoint(p, &codepoint);
GlyphSet *set = get_glyphset(font, codepoint);
FR_Bitmap_Glyph_Metrics *g = &set->glyphs[codepoint & 0xff];
x += g->xadvance;
}
/* At this point here x is in subpixel units */
const int x_scale_to_points = FR_Subpixel_Scale(font->renderer) * surface_scale;
if (subpixel_scale) {
*subpixel_scale = x_scale_to_points;
return x;
}
return (x + x_scale_to_points / 2) / x_scale_to_points;
}
int ren_get_font_height(FontDesc *font_desc) {
const int surface_scale = renwin_surface_scale(&window_renderer);
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
return (font->height + surface_scale / 2) / surface_scale;
}
static inline RenColor blend_pixel(RenColor dst, RenColor src) {
int ia = 0xff - src.a;
dst.r = ((src.r * src.a) + (dst.r * ia)) >> 8;
dst.g = ((src.g * src.a) + (dst.g * ia)) >> 8;
dst.b = ((src.b * src.a) + (dst.b * ia)) >> 8;
return dst;
}
#define rect_draw_loop(expr) \
for (int j = y1; j < y2; j++) { \
for (int i = x1; i < x2; i++) { \
*d = expr; \
d++; \
} \
d += dr; \
}
void ren_draw_rect(RenRect rect, RenColor color) {
if (color.a == 0) { return; }
const int surface_scale = renwin_surface_scale(&window_renderer);
/* transforms coordinates in pixels. */
rect.x *= surface_scale;
rect.y *= surface_scale;
rect.width *= surface_scale;
rect.height *= surface_scale;
const RenRect clip = window_renderer.clip;
int x1 = rect.x < clip.x ? clip.x : rect.x;
int y1 = rect.y < clip.y ? clip.y : rect.y;
int x2 = rect.x + rect.width;
int y2 = rect.y + rect.height;
x2 = x2 > clip.x + clip.width ? clip.x + clip.width : x2;
y2 = y2 > clip.y + clip.height ? clip.y + clip.height : y2;
SDL_Surface *surface = renwin_get_surface(&window_renderer);
RenColor *d = (RenColor*) surface->pixels;
d += x1 + y1 * surface->w;
int dr = surface->w - (x2 - x1);
if (color.a == 0xff) {
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 {
rect_draw_loop(blend_pixel(*d, color));
}
}
static int codepoint_replace(CPReplaceTable *rep_table, unsigned *codepoint) {
for (int i = 0; i < rep_table->size; i++) {
const CPReplace *rep = &rep_table->replacements[i];
if (*codepoint == rep->codepoint_src) {
*codepoint = rep->codepoint_dst;
return 1;
}
}
return 0;
}
static FR_Clip_Area clip_area_from_rect(const RenRect r) {
return (FR_Clip_Area) {r.x, r.y, r.x + r.width, r.y + r.height};
}
static void draw_text_impl(RenFont *font, const char *text, int x_subpixel, int y_pixel, RenColor color,
CPReplaceTable *replacements, RenColor replace_color)
{
SDL_Surface *surf = renwin_get_surface(&window_renderer);
FR_Clip_Area clip = clip_area_from_rect(window_renderer.clip);
const char *p = text;
unsigned codepoint;
const FR_Color color_fr = { .r = color.r, .g = color.g, .b = color.b };
while (*p) {
FR_Color color_rep;
p = utf8_to_codepoint(p, &codepoint);
GlyphSet *set = get_glyphset(font, codepoint);
FR_Bitmap_Glyph_Metrics *g = &set->glyphs[codepoint & 0xff];
const int xadvance_original_cp = g->xadvance;
const int replaced = replacements ? codepoint_replace(replacements, &codepoint) : 0;
if (replaced) {
set = get_glyphset(font, codepoint);
g = &set->glyphs[codepoint & 0xff];
color_rep = (FR_Color) { .r = replace_color.r, .g = replace_color.g, .b = replace_color.b};
} else {
color_rep = color_fr;
}
if (color.a != 0) {
FR_Blend_Glyph(font->renderer, &clip,
x_subpixel, y_pixel, (uint8_t *) surf->pixels, surf->w, set->image, g, color_rep);
}
x_subpixel += xadvance_original_cp;
}
}
void ren_draw_text_subpixel(FontDesc *font_desc, const char *text, int x_subpixel, int y, RenColor color,
CPReplaceTable *replacements, RenColor replace_color)
{
const int surface_scale = renwin_surface_scale(&window_renderer);
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
draw_text_impl(font, text, x_subpixel, surface_scale * y, color, replacements, replace_color);
}
void ren_draw_text(FontDesc *font_desc, const char *text, int x, int y, RenColor color,
CPReplaceTable *replacements, RenColor replace_color)
{
const int surface_scale = renwin_surface_scale(&window_renderer);
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
const int subpixel_scale = surface_scale * FR_Subpixel_Scale(font->renderer);
draw_text_impl(font, text, subpixel_scale * x, surface_scale * y, color, replacements, replace_color);
}
// Could be declared as static inline
int ren_font_subpixel_round(int width, int subpixel_scale, int orientation) {
int w_mult;
if (orientation < 0) {
w_mult = width;
} else if (orientation == 0) {
w_mult = width + subpixel_scale / 2;
} else {
w_mult = width + subpixel_scale - 1;
}
return w_mult / subpixel_scale;
}
int ren_get_font_subpixel_scale(FontDesc *font_desc) {
const int surface_scale = renwin_surface_scale(&window_renderer);
RenFont *font = font_desc_get_font_at_scale(font_desc, surface_scale);
return FR_Subpixel_Scale(font->renderer) * surface_scale;
}

View File

@ -3,27 +3,37 @@
#include <SDL.h>
#include <stdint.h>
#include <stdbool.h>
#include "fontdesc.h"
typedef struct RenImage RenImage;
enum {
RenFontAntialiasingMask = 1,
RenFontGrayscale = 1,
RenFontSubpixel = 0,
RenFontHintingMask = 3 << 1,
RenFontHintingSlight = 0 << 1,
RenFontHintingNone = 1 << 1,
RenFontHintingFull = 2 << 1,
};
#define FONT_FALLBACK_MAX 4
typedef struct RenFont RenFont;
typedef enum { FONT_HINTING_NONE, FONT_HINTING_SLIGHT, FONT_HINTING_FULL } ERenFontHinting;
typedef enum { FONT_ANTIALIASING_NONE, FONT_ANTIALIASING_GRAYSCALE, FONT_ANTIALIASING_SUBPIXEL } ERenFontAntialiasing;
typedef enum { FONT_STYLE_BOLD = 1, FONT_STYLE_ITALIC = 2, FONT_STYLE_UNDERLINE = 4 } ERenFontStyle;
typedef struct { uint8_t b, g, r, a; } RenColor;
typedef struct { int x, y, width, height; } RenRect;
RenFont* ren_font_load(const char *filename, float size, ERenFontAntialiasing antialiasing, ERenFontHinting hinting, unsigned char style);
RenFont* ren_font_copy(RenFont* font, float size);
void ren_font_free(RenFont *font);
int ren_font_group_get_tab_size(RenFont **font);
int ren_font_group_get_height(RenFont **font);
float ren_font_group_get_size(RenFont **font);
void ren_font_group_set_tab_size(RenFont **font, int n);
float ren_font_group_get_width(RenFont **font, const char *text);
float ren_draw_text(RenFont **font, const char *text, float x, int y, RenColor color);
struct CPReplace {
unsigned codepoint_src;
unsigned codepoint_dst;
};
typedef struct CPReplace CPReplace;
struct CPReplaceTable {
int size;
CPReplace *replacements;
};
typedef struct CPReplaceTable CPReplaceTable;
void ren_draw_rect(RenRect rect, RenColor color);
void ren_init(SDL_Window *win);
void ren_resize_window();
@ -32,5 +42,27 @@ void ren_set_clip_rect(RenRect rect);
void ren_get_size(int *x, int *y); /* Reports the size in points. */
void ren_free_window_resources();
RenImage* ren_new_image(int width, int height);
void ren_free_image(RenImage *image);
RenFont* ren_load_font(const char *filename, float size, unsigned int renderer_flags);
int ren_verify_font(const char *filename);
void ren_free_font(RenFont *font);
void ren_set_font_tab_size(RenFont *font, int n);
int ren_get_font_tab_size(RenFont *font);
int ren_get_font_width(FontDesc *font_desc, const char *text, int *subpixel_scale);
int ren_get_font_height(FontDesc *font_desc);
int ren_get_font_subpixel_scale(FontDesc *font_desc);
int ren_font_subpixel_round(int width, int subpixel_scale, int orientation);
void ren_draw_rect(RenRect rect, RenColor color);
void ren_draw_text(FontDesc *font_desc, const char *text, int x, int y, RenColor color, CPReplaceTable *replacements, RenColor replace_color);
void ren_draw_text_subpixel(FontDesc *font_desc, const char *text, int x_subpixel, int y, RenColor color, CPReplaceTable *replacements, RenColor replace_color);
void ren_cp_replace_init(CPReplaceTable *rep_table);
void ren_cp_replace_free(CPReplaceTable *rep_table);
void ren_cp_replace_add(CPReplaceTable *rep_table, const char *src, const char *dst);
void ren_cp_replace_clear(CPReplaceTable *rep_table);
#endif

View File

@ -9,18 +9,6 @@ static int query_surface_scale(RenWindow *ren) {
SDL_GetWindowSize(ren->window, &w_points, &h_points);
/* We consider that the ratio pixel/point will always be an integer and
it is the same along the x and the y axis. */
#ifdef __amigaos4__
// This is a workaround when the w_pixels != w_points and h_pixels != h_points
// because of redraw delays, especially when the "Resize with contents" is enabled
if (w_pixels != w_points) {
w_pixels = w_points;
}
if (h_pixels != h_points) {
h_pixels = h_points;
}
#endif
assert(w_pixels % w_points == 0 && h_pixels % h_points == 0 && w_pixels / w_points == h_pixels / h_points);
return w_pixels / w_points;
}
@ -32,7 +20,7 @@ static void setup_renderer(RenWindow *ren, int w, int h) {
SDL_DestroyTexture(ren->texture);
SDL_DestroyRenderer(ren->renderer);
}
ren->renderer = SDL_CreateRenderer(ren->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
ren->renderer = SDL_CreateRenderer(ren->window, -1, 0);
ren->texture = SDL_CreateTexture(ren->renderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STREAMING, w, h);
ren->surface_scale = query_surface_scale(ren);
}
@ -128,4 +116,3 @@ void renwin_free(RenWindow *ren) {
SDL_FreeSurface(ren->surface);
#endif
}

4
subprojects/libagg.wrap Normal file
View File

@ -0,0 +1,4 @@
[wrap-git]
directory = libagg
url = https://github.com/franko/agg
revision = v2.4-lhelper4

4
subprojects/reproc.wrap Normal file
View File

@ -0,0 +1,4 @@
[wrap-git]
directory = reproc
url = https://github.com/franko/reproc
revision = v14.2.3-meson-1