Merge branch 'master' into amiga2.1
This commit is contained in:
commit
5c983f10b5
|
@ -110,6 +110,12 @@ jobs:
|
|||
run: |
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-macos-universal" >> "$GITHUB_ENV"
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Install dmgbuild
|
||||
run: pip install dmgbuild
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
|
@ -117,8 +123,6 @@ jobs:
|
|||
with:
|
||||
name: macOS DMG Images
|
||||
path: dmgs-original
|
||||
- name: Install appdmg
|
||||
run: cd ~; npm i appdmg; cd -
|
||||
- name: Make universal bundles
|
||||
run: |
|
||||
bash --version
|
||||
|
@ -200,13 +204,14 @@ jobs:
|
|||
run: |
|
||||
"INSTALL_NAME=lite-xl-$($env:GITHUB_REF -replace ".*/")-windows-msvc-${{ matrix.arch.name }}" >> $env:GITHUB_ENV
|
||||
"INSTALL_REF=$($env:GITHUB_REF -replace ".*/")" >> $env:GITHUB_ENV
|
||||
"LUA_SUBPROJECT_PATH=subprojects/lua-5.4.4" >> $env:GITHUB_ENV
|
||||
"LUA_SUBPROJECT_PATH=subprojects/$(awk -F ' *= *' '/directory/ { printf $2 }' subprojects/lua.wrap)" >> $env:GITHUB_ENV
|
||||
- name: Download and patch subprojects
|
||||
shell: bash
|
||||
run: |
|
||||
meson subprojects download
|
||||
cat resources/windows/001-lua-unicode.diff | patch -Np1 -d "$LUA_SUBPROJECT_PATH"
|
||||
- name: Configure
|
||||
run: |
|
||||
# Download the subprojects first so we can patch it before configuring.
|
||||
# This avoids reconfiguring the subprojects when compiling.
|
||||
meson subprojects download
|
||||
Get-Content -Path resources/windows/001-lua-unicode.diff -Raw | patch -d $env:LUA_SUBPROJECT_PATH -p1 --forward
|
||||
meson setup --wrap-mode=forcefallback build
|
||||
- name: Build
|
||||
run: |
|
||||
|
|
|
@ -185,8 +185,8 @@ jobs:
|
|||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install appdmg
|
||||
run: cd ~; npm i appdmg; cd -
|
||||
- name: Install dmgbuild
|
||||
run: pip install dmgbuild
|
||||
- name: Prepare DMG Images
|
||||
run: |
|
||||
mkdir -p dmgs-addons dmgs-normal
|
||||
|
|
|
@ -28,3 +28,4 @@ lite
|
|||
|
||||
!resources/windows/*.diff
|
||||
!resources/windows/*.exe.manifest.in
|
||||
!resources/macos/*.py
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2020-2021 Lite XL Team
|
||||
Copyright (c) 2020-present Lite XL Team
|
||||
|
||||
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
|
||||
|
|
81
README.md
81
README.md
|
@ -1,7 +1,7 @@
|
|||
# Lite XL
|
||||
|
||||
[![CI]](https://github.com/lite-xl/lite-xl/actions/workflows/build.yml)
|
||||
[![Discord Badge Image]](https://discord.gg/RWzqC3nx7K)
|
||||
[![Discord Badge Image]](https://discord.gg/UQKnzBhY5H)
|
||||
|
||||
![screenshot-dark]
|
||||
|
||||
|
@ -81,6 +81,39 @@ affects only the place where the application is actually installed.
|
|||
|
||||
Head over to [releases](https://github.com/lite-xl/lite-xl/releases) and download the version for your operating system.
|
||||
|
||||
### Windows
|
||||
|
||||
Lite XL comes with installers on Windows for typical installations.
|
||||
Alternatively, we provide ZIP archives that you can download and extract anywhere and run directly.
|
||||
|
||||
To make Lite XL portable (e.g. running Lite XL from a thumb drive),
|
||||
simply create a `user` folder where `lite-xl.exe` is located.
|
||||
Lite XL will load and store all your configurations and plugins in the folder.
|
||||
|
||||
### macOS
|
||||
|
||||
We provide DMG files for macOS. Simply drag the program into your Applications folder.
|
||||
|
||||
> **Important**
|
||||
> Newer versions of Lite XL are signed with a self-signed certificate,
|
||||
> so you'll have to follow these steps when running Lite XL for the first time.
|
||||
>
|
||||
> 1. Find Lite XL in Finder (do not open it in Launchpad).
|
||||
> 2. Control-click Lite XL, then choose `Open` from the shortcut menu.
|
||||
> 3. Click `Open` in the popup menu.
|
||||
>
|
||||
> The correct steps may vary between macOS versions, so you should refer to
|
||||
> the [macOS User Guide](https://support.apple.com/en-my/guide/mac-help/mh40616/mac).
|
||||
>
|
||||
> On an older version of Lite XL, you will need to run these commands instead:
|
||||
>
|
||||
> ```sh
|
||||
> # clears attributes from the directory
|
||||
> xattr -cr /Applications/Lite\ XL.app
|
||||
> ```
|
||||
>
|
||||
> Otherwise, macOS will display a **very misleading error** saying that the application is damaged.
|
||||
|
||||
### Linux
|
||||
|
||||
Unzip the file and `cd` into the `lite-xl` directory:
|
||||
|
@ -91,6 +124,7 @@ cd lite-xl
|
|||
```
|
||||
|
||||
To run lite-xl without installing:
|
||||
|
||||
```sh
|
||||
./lite-xl
|
||||
```
|
||||
|
@ -103,21 +137,59 @@ mkdir -p $HOME/.local/bin && cp lite-xl $HOME/.local/bin/
|
|||
mkdir -p $HOME/.local/share/lite-xl && cp -r data/* $HOME/.local/share/lite-xl/
|
||||
```
|
||||
|
||||
#### Add Lite XL to PATH
|
||||
|
||||
To run Lite XL from the command line, you must add it to PATH.
|
||||
|
||||
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:
|
||||
Alternatively on recent versions of GNOME and KDE Plasma,
|
||||
you can add `$HOME/.local/bin` to PATH via `~/.config/environment.d/envvars.conf`:
|
||||
|
||||
```ini
|
||||
PATH=$HOME/.local/bin:$PATH
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> Some systems might not load `.bashrc` when logging in.
|
||||
> This can cause problems with launching applications from the desktop / menu.
|
||||
|
||||
#### Add Lite XL to application launchers
|
||||
|
||||
To get the icon to show up in app launcher, you need to create a desktop
|
||||
entry and put it into `/usr/share/applications` or `~/.local/share/applications`.
|
||||
|
||||
Here is an example for a desktop entry in `~/.local/share/applications/com.lite_xl.LiteXL.desktop`,
|
||||
assuming Lite XL is in PATH:
|
||||
|
||||
```ini
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Lite XL
|
||||
Comment=A lightweight text editor written in Lua
|
||||
Exec=lite-xl %F
|
||||
Icon=lite-xl
|
||||
Terminal=false
|
||||
StartupWMClass=lite-xl
|
||||
Categories=Development;IDE;
|
||||
MimeType=text/plain;inode/directory;
|
||||
```
|
||||
|
||||
To get the icon to show up in app launcher immediately, run:
|
||||
|
||||
```sh
|
||||
xdg-desktop-menu forceupdate
|
||||
```
|
||||
|
||||
You may need to logout and login again to see icon in app launcher.
|
||||
Alternatively, you may log out and log in again.
|
||||
|
||||
To uninstall just run:
|
||||
#### Uninstall
|
||||
|
||||
To uninstall Lite XL, run:
|
||||
|
||||
```sh
|
||||
rm -f $HOME/.local/bin/lite-xl
|
||||
|
@ -127,7 +199,6 @@ rm -rf $HOME/.local/share/icons/hicolor/scalable/apps/lite-xl.svg \
|
|||
$HOME/.local/share/lite-xl
|
||||
```
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Any additional functionality that can be added through a plugin should be done
|
||||
|
|
|
@ -38,7 +38,7 @@ show_help() {
|
|||
echo "-v --version VERSION Sets the version on the package name."
|
||||
echo "-A --appimage Create an AppImage (Linux only)."
|
||||
echo "-D --dmg Create a DMG disk image (macOS only)."
|
||||
echo " Requires NPM and AppDMG."
|
||||
echo " Requires dmgbuild."
|
||||
echo "-I --innosetup Create an InnoSetup installer (Windows only)."
|
||||
echo "-r --release Compile in release mode."
|
||||
echo "-S --source Create a source code package,"
|
||||
|
|
|
@ -43,7 +43,7 @@ local function save(filename)
|
|||
core.log("Saved \"%s\"", saved_filename)
|
||||
else
|
||||
core.error(err)
|
||||
core.nag_view:show("Saving failed", string.format("Could not save \"%s\" do you want to save to another location?", doc().filename), {
|
||||
core.nag_view:show("Saving failed", string.format("Couldn't save file \"%s\". Do you want to save to another location?", doc().filename), {
|
||||
{ text = "No", default_no = true },
|
||||
{ text = "Yes", default_yes = true }
|
||||
}, function(item)
|
||||
|
@ -340,10 +340,11 @@ local commands = {
|
|||
local text = dv.doc:get_text(line1, 1, line1, col1)
|
||||
if #text >= indent_size and text:find("^ *$") then
|
||||
dv.doc:delete_to_cursor(idx, 0, -indent_size)
|
||||
return
|
||||
goto continue
|
||||
end
|
||||
end
|
||||
dv.doc:delete_to_cursor(idx, translate.previous_char)
|
||||
::continue::
|
||||
end
|
||||
end,
|
||||
|
||||
|
@ -544,6 +545,11 @@ local commands = {
|
|||
dv.doc.crlf = not dv.doc.crlf
|
||||
end,
|
||||
|
||||
["doc:toggle-overwrite"] = function(dv)
|
||||
dv.doc.overwrite = not dv.doc.overwrite
|
||||
core.blink_reset() -- to show the cursor has changed edit modes
|
||||
end,
|
||||
|
||||
["doc:save-as"] = function(dv)
|
||||
local last_doc = core.last_active_view and core.last_active_view.doc
|
||||
local text
|
||||
|
|
|
@ -164,13 +164,16 @@ local function is_in_any_selection(line, col)
|
|||
end
|
||||
|
||||
local function select_add_next(all)
|
||||
local il1, ic1 = doc():get_selection(true)
|
||||
for idx, l1, c1, l2, c2 in doc():get_selections(true, true) do
|
||||
local il1, ic1
|
||||
for _, l1, c1, l2, c2 in doc():get_selections(true, true) do
|
||||
if not il1 then
|
||||
il1, ic1 = l1, c1
|
||||
end
|
||||
local text = doc():get_text(l1, c1, l2, c2)
|
||||
repeat
|
||||
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||
if l1 == il1 and c1 == ic1 then break end
|
||||
if l2 and (all or not is_in_any_selection(l2, c2)) then
|
||||
if l2 and not is_in_any_selection(l2, c2) then
|
||||
doc():add_selection(l2, c2, l1, c1)
|
||||
if not all then
|
||||
core.active_view:scroll_to_make_visible(l2, c2)
|
||||
|
@ -266,7 +269,7 @@ command.add(valid_for_finding, {
|
|||
core.error("No find to continue from")
|
||||
else
|
||||
local sl1, sc1, sl2, sc2 = dv.doc:get_selection(true)
|
||||
local line1, col1, line2, col2 = last_fn(dv.doc, sl1, sc2, last_text, case_sensitive, find_regex, false)
|
||||
local line1, col1, line2, col2 = last_fn(dv.doc, sl2, sc2, last_text, case_sensitive, find_regex, false)
|
||||
if line1 then
|
||||
dv.doc:set_selection(line2, col2, line1, col1)
|
||||
dv:scroll_to_line(line2, true)
|
||||
|
|
|
@ -226,7 +226,7 @@ function common.path_suggest(text, root)
|
|||
if root and root:sub(-1) ~= PATHSEP then
|
||||
root = root .. PATHSEP
|
||||
end
|
||||
local path, name = text:match("^(.-)([^:/\\]*)$")
|
||||
local path, name = text:match("^(.-)([^"..PATHSEP.."]*)$")
|
||||
local clean_dotslash = false
|
||||
-- ignore root if path is absolute
|
||||
local is_absolute = common.is_absolute_path(text)
|
||||
|
@ -279,7 +279,7 @@ end
|
|||
---@param text string The input path.
|
||||
---@return string[]
|
||||
function common.dir_path_suggest(text)
|
||||
local path, name = text:match("^(.-)([^:/\\]*)$")
|
||||
local path, name = text:match("^(.-)([^"..PATHSEP.."]*)$")
|
||||
local files = system.list_dir(path == "" and "." or path) or {}
|
||||
local res = {}
|
||||
for _, file in ipairs(files) do
|
||||
|
@ -298,7 +298,7 @@ end
|
|||
---@param dir_list string[] A list of paths to filter.
|
||||
---@return string[]
|
||||
function common.dir_list_suggest(text, dir_list)
|
||||
local path, name = text:match("^(.-)([^:/\\]*)$")
|
||||
local path, name = text:match("^(.-)([^"..PATHSEP.."]*)$")
|
||||
local res = {}
|
||||
for _, dir_path in ipairs(dir_list) do
|
||||
if dir_path:lower():find(text:lower(), nil, true) == 1 then
|
||||
|
@ -378,12 +378,15 @@ function common.bench(name, fn, ...)
|
|||
return res
|
||||
end
|
||||
|
||||
-- From gvx/Ser
|
||||
local oddvals = {[tostring(1/0)] = "1/0", [tostring(-1/0)] = "-1/0", [tostring(-(0/0))] = "-(0/0)", [tostring(0/0)] = "0/0"}
|
||||
|
||||
local function serialize(val, pretty, indent_str, escape, sort, limit, level)
|
||||
local space = pretty and " " or ""
|
||||
local indent = pretty and string.rep(indent_str, level) or ""
|
||||
local newline = pretty and "\n" or ""
|
||||
if type(val) == "string" then
|
||||
local ty = type(val)
|
||||
if ty == "string" then
|
||||
local out = string.format("%q", val)
|
||||
if escape then
|
||||
out = string.gsub(out, "\\\n", "\\n")
|
||||
|
@ -395,7 +398,7 @@ local function serialize(val, pretty, indent_str, escape, sort, limit, level)
|
|||
out = string.gsub(out, "\\13", "\\r")
|
||||
end
|
||||
return out
|
||||
elseif type(val) == "table" then
|
||||
elseif ty == "table" then
|
||||
-- early exit
|
||||
if level >= limit then return tostring(val) end
|
||||
local next_indent = pretty and (indent .. indent_str) or ""
|
||||
|
@ -410,6 +413,12 @@ local function serialize(val, pretty, indent_str, escape, sort, limit, level)
|
|||
if sort then table.sort(t) end
|
||||
return "{" .. newline .. table.concat(t, "," .. newline) .. newline .. indent .. "}"
|
||||
end
|
||||
if ty == "number" then
|
||||
-- tostring is locale-dependent, so we need to replace an eventual `,` with `.`
|
||||
local res, _ = tostring(val):gsub(",", ".")
|
||||
-- handle inf/nan
|
||||
return oddvals[res] or res
|
||||
end
|
||||
return tostring(val)
|
||||
end
|
||||
|
||||
|
@ -452,7 +461,7 @@ end
|
|||
function common.basename(path)
|
||||
-- a path should never end by / or \ except if it is '/' (unix root) or
|
||||
-- 'X:\' (windows drive)
|
||||
return path:match("[^\\/]+$") or path
|
||||
return path:match("[^"..PATHSEP.."]+$") or path
|
||||
end
|
||||
|
||||
|
||||
|
@ -461,7 +470,7 @@ end
|
|||
---@param path string
|
||||
---@return string|nil
|
||||
function common.dirname(path)
|
||||
return path:match("(.+)[:\\/][^\\/]+$")
|
||||
return path:match("(.+)["..PATHSEP.."][^"..PATHSEP.."]+$")
|
||||
end
|
||||
|
||||
|
||||
|
@ -507,10 +516,10 @@ end
|
|||
|
||||
local function split_on_slash(s, sep_pattern)
|
||||
local t = {}
|
||||
if s:match("^[/\\]") then
|
||||
if s:match("^["..PATHSEP.."]") then
|
||||
t[#t + 1] = ""
|
||||
end
|
||||
for fragment in string.gmatch(s, "([^/\\]+)") do
|
||||
for fragment in string.gmatch(s, "([^"..PATHSEP.."]+)") do
|
||||
t[#t + 1] = fragment
|
||||
end
|
||||
return t
|
||||
|
@ -643,7 +652,7 @@ function common.mkdirp(path)
|
|||
while path and path ~= "" do
|
||||
local success_mkdir = system.mkdir(path)
|
||||
if success_mkdir then break end
|
||||
local updir, basedir = path:match("(.*)[/\\](.+)$")
|
||||
local updir, basedir = path:match("(.*)["..PATHSEP.."](.+)$")
|
||||
table.insert(subdirs, 1, basedir or path)
|
||||
path = updir
|
||||
end
|
||||
|
|
|
@ -2,15 +2,71 @@ local common = require "core.common"
|
|||
|
||||
local config = {}
|
||||
|
||||
---The frame rate of Lite XL.
|
||||
---Note that setting this value to the screen's refresh rate
|
||||
---does not eliminate screen tearing.
|
||||
---
|
||||
---Defaults to 60.
|
||||
---@type number
|
||||
config.fps = 60
|
||||
|
||||
---Maximum number of log items that will be stored.
|
||||
---When the number of log items exceed this value, old items will be discarded.
|
||||
---
|
||||
---Defaults to 800.
|
||||
---@type number
|
||||
config.max_log_items = 800
|
||||
|
||||
---The timeout, in seconds, before a message dissapears from StatusView.
|
||||
---
|
||||
---Defaults to 5.
|
||||
---@type number
|
||||
config.message_timeout = 5
|
||||
|
||||
---The number of pixels scrolled per-step.
|
||||
---
|
||||
---Defaults to 50 * SCALE.
|
||||
---@type number
|
||||
config.mouse_wheel_scroll = 50 * SCALE
|
||||
|
||||
---Enables/disables transitions when scrolling with the scrollbar.
|
||||
---When enabled, the scrollbar will have inertia and slowly move towards the cursor.
|
||||
---Otherwise, the scrollbar will immediately follow the cursor.
|
||||
---
|
||||
---Defaults to false.
|
||||
---@type boolean
|
||||
config.animate_drag_scroll = false
|
||||
|
||||
---Enables/disables scrolling past the end of a document.
|
||||
---
|
||||
---Defaults to true.
|
||||
---@type boolean
|
||||
config.scroll_past_end = true
|
||||
---@type "expanded" | "contracted" | false @Force the scrollbar status of the DocView
|
||||
|
||||
---@alias config.scrollbartype
|
||||
---| "expanded" # A thicker scrollbar is shown at all times.
|
||||
---| "contracted" # A thinner scrollbar is shown at all times.
|
||||
---| false # The scrollbar expands when the cursor hovers over it.
|
||||
|
||||
---Controls whether the DocView scrollbar is always shown or hidden.
|
||||
---This option does not affect other View's scrollbars.
|
||||
---
|
||||
---Defaults to false.
|
||||
---@type config.scrollbartype
|
||||
config.force_scrollbar_status = false
|
||||
|
||||
---The file size limit, in megabytes.
|
||||
---Files larger than this size will not be shown in the file picker.
|
||||
---
|
||||
---Defaults to 10.
|
||||
---@type number
|
||||
config.file_size_limit = 10
|
||||
|
||||
---A list of files and directories to ignore.
|
||||
---Each element is a Lua pattern, where patterns ending with a forward slash
|
||||
---are recognized as directories while patterns ending with an anchor ("$") are
|
||||
---recognized as files.
|
||||
---@type string[]
|
||||
config.ignore_files = {
|
||||
-- folders
|
||||
"^%.svn/", "^%.git/", "^%.hg/", "^CVS/", "^%.Trash/", "^%.Trash%-.*/",
|
||||
|
@ -21,46 +77,194 @@ config.ignore_files = {
|
|||
"%.suo$", "%.pdb$", "%.idb$", "%.class$", "%.psd$", "%.db$",
|
||||
"^desktop%.ini$", "^%.DS_Store$", "^%.directory$",
|
||||
}
|
||||
|
||||
---Lua pattern used to find symbols when advanced syntax highlighting
|
||||
---is not available.
|
||||
---This pattern is also used for navigation, e.g. move to next word.
|
||||
---
|
||||
---The default pattern matches all letters, followed by any number
|
||||
---of letters and digits.
|
||||
---@type string
|
||||
config.symbol_pattern = "[%a_][%w_]*"
|
||||
|
||||
---A list of characters that delimits a word.
|
||||
---
|
||||
---The default is ``" \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"``
|
||||
---@type string
|
||||
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||
|
||||
---The timeout, in seconds, before several consecutive actions
|
||||
---are merged as a single undo step.
|
||||
---
|
||||
---The default is 0.3 seconds.
|
||||
---@type number
|
||||
config.undo_merge_timeout = 0.3
|
||||
|
||||
---The maximum number of undo steps per-document.
|
||||
---
|
||||
---The default is 10000.
|
||||
---@type number
|
||||
config.max_undos = 10000
|
||||
|
||||
---The maximum number of tabs shown at a time.
|
||||
---
|
||||
---The default is 8.
|
||||
---@type number
|
||||
config.max_tabs = 8
|
||||
|
||||
---Shows/hides the tab bar when there is only one tab open.
|
||||
---
|
||||
---The tab bar is always shown by default.
|
||||
---@type boolean
|
||||
config.always_show_tabs = true
|
||||
-- Possible values: false, true, "no_selection"
|
||||
|
||||
---@alias config.highlightlinetype
|
||||
---| true # Always highlight the current line.
|
||||
---| false # Never highlight the current line.
|
||||
---| "no_selection" # Highlight the current line if no text is selected.
|
||||
|
||||
---Highlights the current line.
|
||||
---
|
||||
---The default is true.
|
||||
---@type config.highlightlinetype
|
||||
config.highlight_current_line = true
|
||||
|
||||
---The spacing between each line of text.
|
||||
---
|
||||
---The default is 120% of the height of the text (1.2).
|
||||
---@type number
|
||||
config.line_height = 1.2
|
||||
|
||||
---The number of spaces each level of indentation represents.
|
||||
---
|
||||
---The default is 2.
|
||||
---@type number
|
||||
config.indent_size = 2
|
||||
|
||||
---The type of indentation.
|
||||
---
|
||||
---The default is "soft" (spaces).
|
||||
---@type "soft" | "hard"
|
||||
config.tab_type = "soft"
|
||||
|
||||
---Do not remove whitespaces when advancing to the next line.
|
||||
---
|
||||
---Defaults to false.
|
||||
---@type boolean
|
||||
config.keep_newline_whitespace = false
|
||||
|
||||
---Default line endings for new files.
|
||||
---
|
||||
---Defaults to `crlf` (`\r\n`) on Windows and `lf` (`\n`) on everything else.
|
||||
---@type "crlf" | "lf"
|
||||
config.line_endings = PLATFORM == "Windows" and "crlf" or "lf"
|
||||
|
||||
---Maximum number of characters per-line for the line guide.
|
||||
---
|
||||
---Defaults to 80.
|
||||
---@type number
|
||||
config.line_limit = 80
|
||||
|
||||
---Maximum number of project files to keep track of.
|
||||
---If the number of files in the project exceeds this number,
|
||||
---Lite XL will not be able to keep track of them.
|
||||
---They will be not be searched when searching for files or text.
|
||||
---
|
||||
---Defaults to 2000.
|
||||
---@type number
|
||||
config.max_project_files = 2000
|
||||
|
||||
---Enables/disables all transitions.
|
||||
---
|
||||
---Defaults to true.
|
||||
---@type boolean
|
||||
config.transitions = true
|
||||
|
||||
---Enable/disable individual transitions.
|
||||
---These values are overriden by `config.transitions`.
|
||||
config.disabled_transitions = {
|
||||
---Disables scrolling transitions.
|
||||
scroll = false,
|
||||
---Disables transitions for CommandView's suggestions list.
|
||||
commandview = false,
|
||||
---Disables transitions for showing/hiding the context menu.
|
||||
contextmenu = false,
|
||||
---Disables transitions when clicking on log items in LogView.
|
||||
logview = false,
|
||||
---Disables transitions for showing/hiding the Nagbar.
|
||||
nagbar = false,
|
||||
---Disables transitions when scrolling the tab bar.
|
||||
tabs = false,
|
||||
---Disables transitions when a tab is being dragged.
|
||||
tab_drag = false,
|
||||
---Disables transitions when a notification is shown.
|
||||
statusbar = false,
|
||||
}
|
||||
|
||||
---The rate of all transitions.
|
||||
---
|
||||
---Defaults to 1.
|
||||
---@type number
|
||||
config.animation_rate = 1.0
|
||||
|
||||
---The caret's blinking period, in seconds.
|
||||
---
|
||||
---Defaults to 0.8.
|
||||
---@type number
|
||||
config.blink_period = 0.8
|
||||
|
||||
---Disables caret blinking.
|
||||
---
|
||||
---Defaults to false.
|
||||
---@type boolean
|
||||
config.disable_blink = false
|
||||
|
||||
---Draws whitespaces as dots.
|
||||
---This option is deprecated.
|
||||
---Please use the drawwhitespace plugin instead.
|
||||
---@deprecated
|
||||
config.draw_whitespace = false
|
||||
|
||||
---Disables system-drawn window borders.
|
||||
---
|
||||
---When set to true, Lite XL draws its own window decorations,
|
||||
---which can be useful for certain setups.
|
||||
---
|
||||
---Defaults to false.
|
||||
---@type boolean
|
||||
config.borderless = false
|
||||
|
||||
---Shows/hides the close buttons on tabs.
|
||||
---When hidden, users can close tabs via keyboard shortcuts or commands.
|
||||
---
|
||||
---Defaults to true.
|
||||
---@type boolean
|
||||
config.tab_close_button = true
|
||||
|
||||
---Maximum number of clicks recognized by Lite XL.
|
||||
---
|
||||
---Defaults to 3.
|
||||
---@type number
|
||||
config.max_clicks = 3
|
||||
|
||||
-- set as true to be able to test non supported plugins
|
||||
---Disables plugin version checking.
|
||||
---Do not change this unless you know what you are doing.
|
||||
---
|
||||
---Defaults to false.
|
||||
---@type boolean
|
||||
config.skip_plugins_version = false
|
||||
|
||||
-- holds the plugins real config table
|
||||
local plugins_config = {}
|
||||
|
||||
-- virtual representation of plugins config table
|
||||
---A table containing configuration for all the plugins.
|
||||
---
|
||||
---This is a metatable that automaticaly creates a minimal
|
||||
---configuration when a plugin is initially configured.
|
||||
---Each plugins will then call `common.merge()` to get the finalized
|
||||
---plugin config.
|
||||
---Do not use raw operations on this table.
|
||||
---@type table
|
||||
config.plugins = {}
|
||||
|
||||
-- allows virtual access to the plugins config table
|
||||
|
|
|
@ -12,11 +12,31 @@ local divider_width = 1
|
|||
local divider_padding = 5
|
||||
local DIVIDER = {}
|
||||
|
||||
---An item in the context menu.
|
||||
---@class core.contextmenu.item
|
||||
---@field text string
|
||||
---@field info string|nil If provided, this text is displayed on the right side of the menu.
|
||||
---@field command string|fun()
|
||||
|
||||
---A list of items with the same predicate.
|
||||
---@see core.command.predicate
|
||||
---@class core.contextmenu.itemset
|
||||
---@field predicate core.command.predicate
|
||||
---@field items core.contextmenu.item[]
|
||||
|
||||
---A context menu.
|
||||
---@class core.contextmenu : core.object
|
||||
---@field itemset core.contextmenu.itemset[]
|
||||
---@field show_context_menu boolean
|
||||
---@field selected number
|
||||
---@field position core.view.position
|
||||
---@field current_scale number
|
||||
local ContextMenu = Object:extend()
|
||||
|
||||
---A unique value representing the divider in a context menu.
|
||||
ContextMenu.DIVIDER = DIVIDER
|
||||
|
||||
---Creates a new context menu.
|
||||
function ContextMenu:new()
|
||||
self.itemset = {}
|
||||
self.show_context_menu = false
|
||||
|
@ -55,12 +75,19 @@ local function update_items_size(items, update_binding)
|
|||
items.width, items.height = width, height
|
||||
end
|
||||
|
||||
---Registers a list of items into the context menu with a predicate.
|
||||
---@param predicate core.command.predicate
|
||||
---@param items core.contextmenu.item[]
|
||||
function ContextMenu:register(predicate, items)
|
||||
predicate = command.generate_predicate(predicate)
|
||||
update_items_size(items, true)
|
||||
table.insert(self.itemset, { predicate = predicate, items = items })
|
||||
end
|
||||
|
||||
---Shows the context menu.
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@return boolean # If true, the context menu is shown.
|
||||
function ContextMenu:show(x, y)
|
||||
self.items = nil
|
||||
local items_list = { width = 0, height = 0 }
|
||||
|
@ -94,6 +121,7 @@ function ContextMenu:show(x, y)
|
|||
return false
|
||||
end
|
||||
|
||||
---Hides the context menu.
|
||||
function ContextMenu:hide()
|
||||
self.show_context_menu = false
|
||||
self.items = nil
|
||||
|
@ -102,6 +130,8 @@ function ContextMenu:hide()
|
|||
core.request_cursor(core.active_view.cursor)
|
||||
end
|
||||
|
||||
---Returns an iterator that iterates over each context menu item and their dimensions.
|
||||
---@return fun(): number, core.contextmenu.item, number, number, number, number
|
||||
function ContextMenu:each_item()
|
||||
local x, y, w = self.position.x, self.position.y, self.items.width
|
||||
local oy = y
|
||||
|
@ -115,8 +145,12 @@ function ContextMenu:each_item()
|
|||
end)
|
||||
end
|
||||
|
||||
---Event handler for mouse movements.
|
||||
---@param px any
|
||||
---@param py any
|
||||
---@return boolean # true if the event is caught.
|
||||
function ContextMenu:on_mouse_moved(px, py)
|
||||
if not self.show_context_menu then return end
|
||||
if not self.show_context_menu then return false end
|
||||
|
||||
self.selected = -1
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
|
@ -128,6 +162,8 @@ function ContextMenu:on_mouse_moved(px, py)
|
|||
return true
|
||||
end
|
||||
|
||||
---Event handler for when the selection is confirmed.
|
||||
---@param item core.contextmenu.item
|
||||
function ContextMenu:on_selected(item)
|
||||
if type(item.command) == "string" then
|
||||
command.perform(item.command)
|
||||
|
@ -140,6 +176,7 @@ local function change_value(value, change)
|
|||
return value + change
|
||||
end
|
||||
|
||||
---Selects the the previous item.
|
||||
function ContextMenu:focus_previous()
|
||||
self.selected = (self.selected == -1 or self.selected == 1) and #self.items or change_value(self.selected, -1)
|
||||
if self:get_item_selected() == DIVIDER then
|
||||
|
@ -147,6 +184,7 @@ function ContextMenu:focus_previous()
|
|||
end
|
||||
end
|
||||
|
||||
---Selects the next item.
|
||||
function ContextMenu:focus_next()
|
||||
self.selected = (self.selected == -1 or self.selected == #self.items) and 1 or change_value(self.selected, 1)
|
||||
if self:get_item_selected() == DIVIDER then
|
||||
|
@ -154,10 +192,13 @@ function ContextMenu:focus_next()
|
|||
end
|
||||
end
|
||||
|
||||
---Gets the currently selected item.
|
||||
---@return core.contextmenu.item|nil
|
||||
function ContextMenu:get_item_selected()
|
||||
return (self.items or {})[self.selected]
|
||||
end
|
||||
|
||||
---Hides the context menu and performs the command if an item is selected.
|
||||
function ContextMenu:call_selected_item()
|
||||
local selected = self:get_item_selected()
|
||||
self:hide()
|
||||
|
@ -166,6 +207,12 @@ function ContextMenu:call_selected_item()
|
|||
end
|
||||
end
|
||||
|
||||
---Event handler for mouse press.
|
||||
---@param button core.view.mousebutton
|
||||
---@param px number
|
||||
---@param py number
|
||||
---@param clicks number
|
||||
---@return boolean # true if the event is caught.
|
||||
function ContextMenu:on_mouse_pressed(button, px, py, clicks)
|
||||
local caught = false
|
||||
|
||||
|
@ -186,14 +233,20 @@ function ContextMenu:on_mouse_pressed(button, px, py, clicks)
|
|||
return caught
|
||||
end
|
||||
|
||||
---@type fun(self: table, k: string, dest: number, rate?: number, name?: string)
|
||||
ContextMenu.move_towards = View.move_towards
|
||||
|
||||
---Event handler for content update.
|
||||
function ContextMenu:update()
|
||||
if self.show_context_menu then
|
||||
self:move_towards("height", self.items.height, nil, "contextmenu")
|
||||
end
|
||||
end
|
||||
|
||||
---Draws the context menu.
|
||||
---
|
||||
---This wraps `ContextMenu:draw_context_menu()`.
|
||||
---@see core.contextmenu.draw_context_menu
|
||||
function ContextMenu:draw()
|
||||
if not self.show_context_menu then return end
|
||||
if self.current_scale ~= SCALE then
|
||||
|
@ -206,6 +259,7 @@ function ContextMenu:draw()
|
|||
core.root_view:defer_draw(self.draw_context_menu, self)
|
||||
end
|
||||
|
||||
---Draws the context menu.
|
||||
function ContextMenu:draw_context_menu()
|
||||
if not self.items then return end
|
||||
local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height
|
||||
|
|
|
@ -91,6 +91,7 @@ end
|
|||
-- designed to be run inside a coroutine.
|
||||
function dirwatch:check(change_callback, scan_time, wait_time)
|
||||
local had_change = false
|
||||
local last_error
|
||||
self.monitor:check(function(id)
|
||||
had_change = true
|
||||
if self.monitor:mode() == "single" then
|
||||
|
@ -102,7 +103,10 @@ function dirwatch:check(change_callback, scan_time, wait_time)
|
|||
elseif self.reverse_watched[id] then
|
||||
change_callback(self.reverse_watched[id])
|
||||
end
|
||||
end, function(err)
|
||||
last_error = err
|
||||
end)
|
||||
if last_error ~= nil then error(last_error) end
|
||||
local start_time = system.get_time()
|
||||
for directory, old_modified in pairs(self.scanned) do
|
||||
if old_modified then
|
||||
|
@ -186,47 +190,45 @@ end
|
|||
-- "root" will by an absolute path without trailing '/'
|
||||
-- "path" will be a path starting without '/' and without trailing '/'
|
||||
-- or the empty string.
|
||||
-- It will identifies a sub-path within "root.
|
||||
-- It identifies a sub-path within "root".
|
||||
-- The current path location will therefore always be: root .. path.
|
||||
-- When recursing "root" will always be the same, only "path" will change.
|
||||
-- When recursing, "root" will always be the same, only "path" will change.
|
||||
-- Returns a list of file "items". In each item the "filename" will be the
|
||||
-- complete file path relative to "root" *without* the trailing '/', and without the starting '/'.
|
||||
function dirwatch.get_directory_files(dir, root, path, t, entries_count, recurse_pred)
|
||||
function dirwatch.get_directory_files(dir, root, path, entries_count, recurse_pred)
|
||||
local t = {}
|
||||
local t0 = system.get_time()
|
||||
local t_elapsed = system.get_time() - t0
|
||||
local dirs, files = {}, {}
|
||||
local ignore_compiled = compile_ignore_files()
|
||||
|
||||
|
||||
local all = system.list_dir(root .. PATHSEP .. path)
|
||||
if not all then return nil end
|
||||
|
||||
for _, file in ipairs(all or {}) do
|
||||
local entries = { }
|
||||
for _, file in ipairs(all) do
|
||||
local info = get_project_file_info(root, (path ~= "" and (path .. PATHSEP) or "") .. file, ignore_compiled)
|
||||
if info then
|
||||
table.insert(info.type == "dir" and dirs or files, info)
|
||||
entries_count = entries_count + 1
|
||||
table.insert(entries, info)
|
||||
end
|
||||
end
|
||||
table.sort(entries, compare_file)
|
||||
|
||||
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 = dirwatch.get_directory_files(dir, root, f.filename, t, entries_count, recurse_pred)
|
||||
for _, info in ipairs(entries) do
|
||||
table.insert(t, info)
|
||||
entries_count = entries_count + 1
|
||||
if info.type == "dir" then
|
||||
if recurse_pred(dir, info.filename, entries_count, system.get_time() - t0) then
|
||||
local t_rec, complete, n = dirwatch.get_directory_files(dir, root, info.filename, entries_count, recurse_pred)
|
||||
recurse_complete = recurse_complete and complete
|
||||
if n ~= nil then
|
||||
entries_count = n
|
||||
for _, info_rec in ipairs(t_rec) do
|
||||
table.insert(t, info_rec)
|
||||
end
|
||||
end
|
||||
else
|
||||
recurse_complete = false
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(files, compare_file)
|
||||
for _, f in ipairs(files) do
|
||||
table.insert(t, f)
|
||||
end
|
||||
|
||||
return t, recurse_complete, entries_count
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
local Object = require "core.object"
|
||||
local Highlighter = require "core.doc.highlighter"
|
||||
local translate = require "core.doc.translate"
|
||||
local core = require "core"
|
||||
local syntax = require "core.syntax"
|
||||
local config = require "core.config"
|
||||
|
@ -27,8 +28,10 @@ function Doc:new(filename, abs_filename, new_file)
|
|||
self:load(filename)
|
||||
end
|
||||
end
|
||||
if new_file then
|
||||
self.crlf = config.line_endings == "crlf"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:reset()
|
||||
self.lines = { "\n" }
|
||||
|
@ -38,10 +41,10 @@ function Doc:reset()
|
|||
self.redo_stack = { idx = 1 }
|
||||
self.clean_change_id = 1
|
||||
self.highlighter = Highlighter(self)
|
||||
self.overwrite = false
|
||||
self:reset_syntax()
|
||||
end
|
||||
|
||||
|
||||
function Doc:reset_syntax()
|
||||
local header = self:get_text(1, 1, self:position_offset(1, 1, 128))
|
||||
local path = self.abs_filename
|
||||
|
@ -56,14 +59,12 @@ function Doc:reset_syntax()
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:set_filename(filename, abs_filename)
|
||||
self.filename = filename
|
||||
self.abs_filename = abs_filename
|
||||
self:reset_syntax()
|
||||
end
|
||||
|
||||
|
||||
function Doc:load(filename)
|
||||
local fp = assert(io.open(filename, "rb"))
|
||||
self:reset()
|
||||
|
@ -85,7 +86,6 @@ function Doc:load(filename)
|
|||
self:reset_syntax()
|
||||
end
|
||||
|
||||
|
||||
function Doc:reload()
|
||||
if self.filename then
|
||||
local sel = { self:get_selection() }
|
||||
|
@ -95,7 +95,6 @@ function Doc:reload()
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:save(filename, abs_filename)
|
||||
if not filename then
|
||||
assert(self.filename, "no filename set to default to")
|
||||
|
@ -115,26 +114,23 @@ function Doc:save(filename, abs_filename)
|
|||
self:clean()
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_name()
|
||||
return self.filename or "unsaved"
|
||||
end
|
||||
|
||||
|
||||
function Doc:is_dirty()
|
||||
if self.new_file then
|
||||
if self.filename then return true end
|
||||
return #self.lines > 1 or #self.lines[1] > 1
|
||||
else
|
||||
return self.clean_change_id ~= self:get_change_id()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:clean()
|
||||
self.clean_change_id = self:get_change_id()
|
||||
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,
|
||||
|
@ -142,7 +138,6 @@ function Doc:get_indent_info()
|
|||
self.indent_info.confirmed
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_change_id()
|
||||
return self.undo_stack.idx
|
||||
end
|
||||
|
@ -166,13 +161,14 @@ function Doc:get_selection(sort)
|
|||
return line1, col1, line2, col2, swap
|
||||
end
|
||||
|
||||
|
||||
---Get the selection specified by `idx`
|
||||
---@param idx integer @the index of the selection to retrieve
|
||||
---@param sort? boolean @whether to sort the selection returned
|
||||
---@return integer,integer,integer,integer,boolean? @line1, col1, line2, col2, was the selection sorted
|
||||
function Doc:get_selection_idx(idx, sort)
|
||||
local line1, col1, line2, col2 = self.selections[idx*4-3], self.selections[idx*4-2], self.selections[idx*4-1], self.selections[idx*4]
|
||||
local line1, col1, line2, col2 = self.selections[idx * 4 - 3], self.selections[idx * 4 - 2],
|
||||
self.selections[idx * 4 - 1],
|
||||
self.selections[idx * 4]
|
||||
if line1 and sort then
|
||||
return sort_positions(line1, col1, line2, col2)
|
||||
else
|
||||
|
@ -232,7 +228,6 @@ function Doc:add_selection(line1, col1, line2, col2, swap)
|
|||
self.last_selection = target
|
||||
end
|
||||
|
||||
|
||||
function Doc:remove_selection(idx)
|
||||
if self.last_selection >= idx then
|
||||
self.last_selection = self.last_selection - 1
|
||||
|
@ -240,7 +235,6 @@ function Doc:remove_selection(idx)
|
|||
common.splice(self.selections, (idx - 1) * 4 + 1, 4)
|
||||
end
|
||||
|
||||
|
||||
function Doc:set_selection(line1, col1, line2, col2, swap)
|
||||
self.selections = {}
|
||||
self:set_selections(1, line1, col1, line2, col2, swap)
|
||||
|
@ -278,6 +272,7 @@ function Doc:get_selections(sort_intra, idx_reverse)
|
|||
return selection_iterator, { self.selections, sort_intra, idx_reverse },
|
||||
idx_reverse == true and ((#self.selections / 4) + 1) or ((idx_reverse or -1) + 1)
|
||||
end
|
||||
|
||||
-- End of cursor seciton.
|
||||
|
||||
function Doc:sanitize_position(line, col)
|
||||
|
@ -290,7 +285,6 @@ function Doc:sanitize_position(line, col)
|
|||
return line, common.clamp(col, 1, #self.lines[line])
|
||||
end
|
||||
|
||||
|
||||
local function position_offset_func(self, line, col, fn, ...)
|
||||
line, col = self:sanitize_position(line, col)
|
||||
return fn(self, line, col, ...)
|
||||
|
@ -329,7 +323,6 @@ function Doc:position_offset(line, col, ...)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_text(line1, col1, line2, col2)
|
||||
line1, col1 = self:sanitize_position(line1, col1)
|
||||
line2, col2 = self:sanitize_position(line2, col2)
|
||||
|
@ -345,13 +338,11 @@ function Doc:get_text(line1, col1, line2, col2)
|
|||
return table.concat(lines)
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_char(line, col)
|
||||
line, col = self:sanitize_position(line, col)
|
||||
return self.lines[line]:sub(col, col)
|
||||
end
|
||||
|
||||
|
||||
local function push_undo(undo_stack, time, type, ...)
|
||||
undo_stack[undo_stack.idx] = { type = type, time = time, ... }
|
||||
undo_stack[undo_stack.idx - config.max_undos] = nil
|
||||
|
@ -412,7 +403,8 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
|
|||
if cline1 < line then break end
|
||||
local line_addition = (line < cline1 or col < ccol1) and #lines - 1 or 0
|
||||
local column_addition = line == cline1 and ccol1 > col and len or 0
|
||||
self:set_selections(idx, cline1 + line_addition, ccol1 + column_addition, cline2 + line_addition, ccol2 + column_addition)
|
||||
self:set_selections(idx, cline1 + line_addition, ccol1 + column_addition, cline2 + line_addition,
|
||||
ccol2 + column_addition)
|
||||
end
|
||||
|
||||
-- push undo
|
||||
|
@ -425,7 +417,6 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
|
|||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
|
||||
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||
-- push undo
|
||||
local text = self:get_text(line1, col1, line2, col2)
|
||||
|
@ -484,15 +475,17 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
|||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
|
||||
function Doc:insert(line, col, text)
|
||||
self.redo_stack = { idx = 1 }
|
||||
-- Reset the clean id when we're pushing something new before it
|
||||
if self:get_change_id() < self.clean_change_id then
|
||||
self.clean_change_id = -1
|
||||
end
|
||||
line, col = self:sanitize_position(line, col)
|
||||
self:raw_insert(line, col, text, self.undo_stack, system.get_time())
|
||||
self:on_text_change("insert")
|
||||
end
|
||||
|
||||
|
||||
function Doc:remove(line1, col1, line2, col2)
|
||||
self.redo_stack = { idx = 1 }
|
||||
line1, col1 = self:sanitize_position(line1, col1)
|
||||
|
@ -502,28 +495,34 @@ function Doc:remove(line1, col1, line2, col2)
|
|||
self:on_text_change("remove")
|
||||
end
|
||||
|
||||
|
||||
function Doc:undo()
|
||||
pop_undo(self, self.undo_stack, self.redo_stack, false)
|
||||
end
|
||||
|
||||
|
||||
function Doc:redo()
|
||||
pop_undo(self, self.redo_stack, self.undo_stack, false)
|
||||
end
|
||||
|
||||
|
||||
function Doc:text_input(text, idx)
|
||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do
|
||||
local had_selection = false
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
self:delete_to_cursor(sidx)
|
||||
had_selection = true
|
||||
end
|
||||
|
||||
if self.overwrite
|
||||
and not had_selection
|
||||
and col1 < #self.lines[line1]
|
||||
and text:ulen() == 1 then
|
||||
self:remove(line1, col1, translate.next_char(self, line1, col1))
|
||||
end
|
||||
|
||||
self:insert(line1, col1, text)
|
||||
self:move_to_cursor(sidx, #text)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:ime_text_editing(text, start, length, idx)
|
||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
|
@ -534,7 +533,6 @@ function Doc:ime_text_editing(text, start, length, idx)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
|
||||
local old_text = self:get_text(line1, col1, line2, col2)
|
||||
local new_text, res = fn(old_text)
|
||||
|
@ -564,7 +562,6 @@ function Doc:replace(fn)
|
|||
return results
|
||||
end
|
||||
|
||||
|
||||
function Doc:delete_to_cursor(idx, ...)
|
||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
|
@ -578,6 +575,7 @@ function Doc:delete_to_cursor(idx, ...)
|
|||
end
|
||||
self:merge_cursors(idx)
|
||||
end
|
||||
|
||||
function Doc:delete_to(...) return self:delete_to_cursor(nil, ...) end
|
||||
|
||||
function Doc:move_to_cursor(idx, ...)
|
||||
|
@ -586,8 +584,8 @@ function Doc:move_to_cursor(idx, ...)
|
|||
end
|
||||
self:merge_cursors(idx)
|
||||
end
|
||||
function Doc:move_to(...) return self:move_to_cursor(nil, ...) end
|
||||
|
||||
function Doc:move_to(...) return self:move_to_cursor(nil, ...) end
|
||||
|
||||
function Doc:select_to_cursor(idx, ...)
|
||||
for sidx, line, col, line2, col2 in self:get_selections(false, idx) do
|
||||
|
@ -596,8 +594,8 @@ function Doc:select_to_cursor(idx, ...)
|
|||
end
|
||||
self:merge_cursors(idx)
|
||||
end
|
||||
function Doc:select_to(...) return self:select_to_cursor(nil, ...) 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()
|
||||
|
@ -669,5 +667,4 @@ function Doc:on_close()
|
|||
core.log_quiet("Closed doc \"%s\"", self:get_name())
|
||||
end
|
||||
|
||||
|
||||
return Doc
|
||||
|
|
|
@ -66,7 +66,18 @@ function search.find(doc, line, col, text, opt)
|
|||
s, e = search_func(line_text, pattern, col, plain)
|
||||
end
|
||||
if s then
|
||||
return line, s, line, e + 1
|
||||
local line2 = line
|
||||
-- If we've matched the newline too,
|
||||
-- return until the initial character of the next line.
|
||||
if e >= #doc.lines[line] then
|
||||
line2 = line + 1
|
||||
e = 0
|
||||
end
|
||||
-- Avoid returning matches that go beyond the last line.
|
||||
-- This is needed to avoid selecting the "last" newline.
|
||||
if line2 <= #doc.lines then
|
||||
return line, s, line2, e + 1
|
||||
end
|
||||
end
|
||||
col = opt.reverse and -1 or 1
|
||||
end
|
||||
|
|
|
@ -460,6 +460,13 @@ function DocView:draw_line_text(line, x, y)
|
|||
return self:get_line_height()
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw_overwrite_caret(x, y, width)
|
||||
local lh = self:get_line_height()
|
||||
renderer.draw_rect(x, y + lh - style.caret_width, width, style.caret_width, style.caret)
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw_caret(x, y)
|
||||
local lh = self:get_line_height()
|
||||
renderer.draw_rect(x, y, style.caret_width, lh, style.caret)
|
||||
|
@ -559,7 +566,12 @@ function DocView:draw_overlay()
|
|||
else
|
||||
if config.disable_blink
|
||||
or (core.blink_timer - core.blink_start) % T < T / 2 then
|
||||
self:draw_caret(self:get_line_screen_position(line1, col1))
|
||||
local x, y = self:get_line_screen_position(line1, col1)
|
||||
if self.doc.overwrite then
|
||||
self:draw_overwrite_caret(x, y, self:get_font():get_width(self.doc:get_char(line1, col1)))
|
||||
else
|
||||
self:draw_caret(x, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -102,7 +102,7 @@ local function strip_leading_path(filename)
|
|||
end
|
||||
|
||||
local function strip_trailing_slash(filename)
|
||||
if filename:match("[^:][/\\]$") then
|
||||
if filename:match("[^:]["..PATHSEP.."]$") then
|
||||
return filename:sub(1, -2)
|
||||
end
|
||||
return filename
|
||||
|
@ -120,9 +120,7 @@ local function show_max_files_warning(dir)
|
|||
"Too many files in project directory: stopped reading at "..
|
||||
config.max_project_files.." files. For more information see "..
|
||||
"usage.md at https://github.com/lite-xl/lite-xl."
|
||||
if core.status_view then
|
||||
core.status_view:show_message("!", style.accent, message)
|
||||
end
|
||||
core.warn(message)
|
||||
end
|
||||
|
||||
|
||||
|
@ -184,7 +182,7 @@ local function refresh_directory(topdir, target)
|
|||
directory_start_idx = directory_start_idx + 1
|
||||
end
|
||||
|
||||
local files = dirwatch.get_directory_files(topdir, topdir.name, (target or ""), {}, 0, function() return false end)
|
||||
local files = dirwatch.get_directory_files(topdir, topdir.name, (target or ""), 0, function() return false end)
|
||||
local change = false
|
||||
|
||||
-- If this file doesn't exist, we should be calling this on our parent directory, assume we'll do that.
|
||||
|
@ -265,7 +263,7 @@ function core.add_project_directory(path)
|
|||
|
||||
local fstype = PLATFORM == "Linux" and system.get_fs_type(topdir.name) or "unknown"
|
||||
topdir.force_scans = (fstype == "nfs" or fstype == "fuse")
|
||||
local t, complete, entries_count = dirwatch.get_directory_files(topdir, topdir.name, "", {}, 0, timed_max_files_pred)
|
||||
local t, complete, entries_count = dirwatch.get_directory_files(topdir, topdir.name, "", 0, timed_max_files_pred)
|
||||
topdir.files = t
|
||||
if not complete then
|
||||
topdir.slow_filesystem = not complete and (entries_count <= config.max_project_files)
|
||||
|
@ -810,7 +808,11 @@ function core.init()
|
|||
end
|
||||
|
||||
if not plugins_success or got_user_error or got_project_error then
|
||||
-- defer LogView to after everything is initialized,
|
||||
-- so that EmptyView won't be added after LogView.
|
||||
core.add_thread(function()
|
||||
command.perform("core:open-log")
|
||||
end)
|
||||
end
|
||||
|
||||
core.configure_borderless_window()
|
||||
|
@ -1274,6 +1276,9 @@ function core.on_event(type, ...)
|
|||
elseif type == "textediting" then
|
||||
ime.on_text_editing(...)
|
||||
elseif type == "keypressed" then
|
||||
-- In some cases during IME composition input is still sent to us
|
||||
-- so we just ignore it.
|
||||
if ime.editing then return false end
|
||||
did_keymap = keymap.on_key_pressed(...)
|
||||
elseif type == "keyreleased" then
|
||||
keymap.on_key_released(...)
|
||||
|
@ -1418,11 +1423,11 @@ local run_threads = coroutine.wrap(function()
|
|||
|
||||
-- stop running threads if we're about to hit the end of frame
|
||||
if system.get_time() - core.frame_start > max_time then
|
||||
coroutine.yield(0)
|
||||
coroutine.yield(0, false)
|
||||
end
|
||||
end
|
||||
|
||||
coroutine.yield(minimal_time_to_wake)
|
||||
coroutine.yield(minimal_time_to_wake, true)
|
||||
end
|
||||
end)
|
||||
|
||||
|
@ -1430,10 +1435,15 @@ end)
|
|||
function core.run()
|
||||
local next_step
|
||||
local last_frame_time
|
||||
local run_threads_full = 0
|
||||
while true do
|
||||
core.frame_start = system.get_time()
|
||||
local time_to_wake = run_threads()
|
||||
local time_to_wake, threads_done = run_threads()
|
||||
if threads_done then
|
||||
run_threads_full = run_threads_full + 1
|
||||
end
|
||||
local did_redraw = false
|
||||
local did_step = false
|
||||
local force_draw = core.redraw and last_frame_time and core.frame_start - last_frame_time > (1 / config.fps)
|
||||
if force_draw or not next_step or system.get_time() >= next_step then
|
||||
if core.step() then
|
||||
|
@ -1441,11 +1451,12 @@ function core.run()
|
|||
last_frame_time = core.frame_start
|
||||
end
|
||||
next_step = nil
|
||||
did_step = true
|
||||
end
|
||||
if core.restart_request or core.quit_request then break end
|
||||
|
||||
if not did_redraw then
|
||||
if system.window_has_focus() then
|
||||
if system.window_has_focus() or not did_step or run_threads_full < 2 then
|
||||
local now = system.get_time()
|
||||
if not next_step then -- compute the time until the next blink
|
||||
local t = now - core.blink_start
|
||||
|
@ -1454,7 +1465,7 @@ function core.run()
|
|||
local cursor_time_to_wake = dt + 1 / config.fps
|
||||
next_step = now + cursor_time_to_wake
|
||||
end
|
||||
if time_to_wake > 0 and system.wait_event(math.min(next_step - now, time_to_wake)) then
|
||||
if system.wait_event(math.min(next_step - now, time_to_wake)) then
|
||||
next_step = nil -- if we've recevied an event, perform a step
|
||||
end
|
||||
else
|
||||
|
@ -1462,6 +1473,7 @@ function core.run()
|
|||
next_step = nil -- perform a step when we're not in focus if get we an event
|
||||
end
|
||||
else -- if we redrew, then make sure we only draw at most FPS/sec
|
||||
run_threads_full = 0
|
||||
local now = system.get_time()
|
||||
local elapsed = now - core.frame_start
|
||||
local next_frame = math.max(0, 1 / config.fps - elapsed)
|
||||
|
|
|
@ -42,15 +42,19 @@ local modkeys = modkeys_os.keys
|
|||
---@return string
|
||||
local function normalize_stroke(stroke)
|
||||
local stroke_table = {}
|
||||
for modkey in stroke:gmatch("(%w+)%+") do
|
||||
table.insert(stroke_table, modkey)
|
||||
for key in stroke:gmatch("[^+]+") do
|
||||
table.insert(stroke_table, key)
|
||||
end
|
||||
if not next(stroke_table) then
|
||||
return stroke
|
||||
table.sort(stroke_table, function(a, b)
|
||||
if a == b then return false end
|
||||
for _, mod in ipairs(modkeys) do
|
||||
if a == mod or b == mod then
|
||||
return a == mod
|
||||
end
|
||||
table.sort(stroke_table)
|
||||
local new_stroke = table.concat(stroke_table, "+") .. "+"
|
||||
return new_stroke .. stroke:sub(new_stroke:len() + 1)
|
||||
end
|
||||
return a < b
|
||||
end)
|
||||
return table.concat(stroke_table, "+")
|
||||
end
|
||||
|
||||
|
||||
|
@ -58,13 +62,13 @@ end
|
|||
---@param key string
|
||||
---@return string
|
||||
local function key_to_stroke(key)
|
||||
local stroke = ""
|
||||
local keys = { key }
|
||||
for _, mk in ipairs(modkeys) do
|
||||
if keymap.modkeys[mk] then
|
||||
stroke = stroke .. mk .. "+"
|
||||
table.insert(keys, mk)
|
||||
end
|
||||
end
|
||||
return normalize_stroke(stroke) .. key
|
||||
return normalize_stroke(table.concat(keys, "+"))
|
||||
end
|
||||
|
||||
---Remove the given value from an array associated to a key in a table.
|
||||
|
@ -92,12 +96,12 @@ end
|
|||
---@param map keymap.map
|
||||
local function remove_duplicates(map)
|
||||
for stroke, commands in pairs(map) do
|
||||
stroke = normalize_stroke(stroke)
|
||||
local normalized_stroke = normalize_stroke(stroke)
|
||||
if type(commands) == "string" or type(commands) == "function" then
|
||||
commands = { commands }
|
||||
end
|
||||
if keymap.map[stroke] then
|
||||
for _, registered_cmd in ipairs(keymap.map[stroke]) do
|
||||
if keymap.map[normalized_stroke] then
|
||||
for _, registered_cmd in ipairs(keymap.map[normalized_stroke]) do
|
||||
local j = 0
|
||||
for i=1, #commands do
|
||||
while commands[i + j] == registered_cmd do
|
||||
|
@ -120,7 +124,6 @@ end
|
|||
function keymap.add_direct(map)
|
||||
for stroke, commands in pairs(map) do
|
||||
stroke = normalize_stroke(stroke)
|
||||
|
||||
if type(commands) == "string" or type(commands) == "function" then
|
||||
commands = { commands }
|
||||
end
|
||||
|
@ -174,7 +177,8 @@ end
|
|||
---@param shortcut string
|
||||
---@param cmd string
|
||||
function keymap.unbind(shortcut, cmd)
|
||||
remove_only(keymap.map, normalize_stroke(shortcut), cmd)
|
||||
shortcut = normalize_stroke(shortcut)
|
||||
remove_only(keymap.map, shortcut, cmd)
|
||||
remove_only(keymap.reverse_map, cmd, shortcut)
|
||||
end
|
||||
|
||||
|
@ -199,10 +203,6 @@ end
|
|||
-- Events listening
|
||||
--------------------------------------------------------------------------------
|
||||
function keymap.on_key_pressed(k, ...)
|
||||
-- In MacOS and Windows during IME composition input is still sent to us
|
||||
-- so we just ignore it
|
||||
if PLATFORM ~= "Linux" and ime.editing then return false end
|
||||
|
||||
local mk = modkey_map[k]
|
||||
if mk then
|
||||
keymap.modkeys[mk] = true
|
||||
|
@ -341,6 +341,7 @@ keymap.add_direct {
|
|||
["ctrl+x"] = "doc:cut",
|
||||
["ctrl+c"] = "doc:copy",
|
||||
["ctrl+v"] = "doc:paste",
|
||||
["insert"] = "doc:toggle-overwrite",
|
||||
["ctrl+insert"] = "doc:copy",
|
||||
["shift+insert"] = "doc:paste",
|
||||
["escape"] = { "command:escape", "doc:select-none", "dialog:select-no" },
|
||||
|
|
|
@ -7,8 +7,12 @@ modkeys.map = {
|
|||
["right shift"] = "shift",
|
||||
["left alt"] = "alt",
|
||||
["right alt"] = "altgr",
|
||||
["left gui"] = "super",
|
||||
["left windows"] = "super",
|
||||
["right gui"] = "super",
|
||||
["right windows"] = "super"
|
||||
}
|
||||
|
||||
modkeys.keys = { "ctrl", "alt", "altgr", "shift" }
|
||||
modkeys.keys = { "ctrl", "shift", "alt", "altgr", "super" }
|
||||
|
||||
return modkeys
|
||||
|
|
|
@ -13,6 +13,6 @@ modkeys.map = {
|
|||
["right alt"] = "altgr",
|
||||
}
|
||||
|
||||
modkeys.keys = { "cmd", "ctrl", "alt", "option", "altgr", "shift" }
|
||||
modkeys.keys = { "ctrl", "alt", "option", "altgr", "shift", "cmd" }
|
||||
|
||||
return modkeys
|
||||
|
|
|
@ -24,6 +24,7 @@ function NagView:new()
|
|||
self.scrollable = true
|
||||
self.target_height = 0
|
||||
self.on_mouse_pressed_root = nil
|
||||
self.dim_alpha = 0
|
||||
end
|
||||
|
||||
function NagView:get_title()
|
||||
|
@ -68,7 +69,9 @@ function NagView:dim_window_content()
|
|||
oy = oy + self.show_height
|
||||
local w, h = core.root_view.size.x, core.root_view.size.y - oy
|
||||
core.root_view:defer_draw(function()
|
||||
renderer.draw_rect(ox, oy, w, h, style.nagbar_dim)
|
||||
local dim_color = { table.unpack(style.nagbar_dim) }
|
||||
dim_color[4] = style.nagbar_dim[4] * self.dim_alpha
|
||||
renderer.draw_rect(ox, oy, w, h, dim_color)
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -172,10 +175,13 @@ function NagView:update()
|
|||
NagView.super.update(self)
|
||||
|
||||
if self.visible and core.active_view == self and self.title then
|
||||
self:move_towards(self, "show_height", self:get_target_height(), nil, "nagbar")
|
||||
local target_height = self:get_target_height()
|
||||
self:move_towards(self, "show_height", target_height, nil, "nagbar")
|
||||
self:move_towards(self, "underline_progress", 1, nil, "nagbar")
|
||||
self:move_towards(self, "dim_alpha", self.show_height / target_height, nil, "nagbar")
|
||||
else
|
||||
self:move_towards(self, "show_height", 0, nil, "nagbar")
|
||||
self:move_towards(self, "dim_alpha", 0, nil, "nagbar")
|
||||
if self.show_height <= 0 then
|
||||
self.title = nil
|
||||
self.message = nil
|
||||
|
|
|
@ -177,8 +177,12 @@ function Node:add_view(view, idx)
|
|||
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)
|
||||
if idx and idx > 1 then
|
||||
idx = idx - 1
|
||||
end
|
||||
table.insert(self.views, idx or (#self.views + 1), view)
|
||||
end
|
||||
idx = common.clamp(idx or (#self.views + 1), 1, (#self.views + 1))
|
||||
table.insert(self.views, idx, view)
|
||||
self:set_active_view(view)
|
||||
end
|
||||
|
||||
|
|
|
@ -313,10 +313,10 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
|||
if self.dragged_divider then
|
||||
local node = self.dragged_divider
|
||||
if node.type == "hsplit" then
|
||||
x = common.clamp(x, 0, self.root_node.size.x * 0.95)
|
||||
x = common.clamp(x - node.position.x, 0, self.root_node.size.x * 0.95)
|
||||
resize_child_node(node, "x", x, dx)
|
||||
elseif node.type == "vsplit" then
|
||||
y = common.clamp(y, 0, self.root_node.size.y * 0.95)
|
||||
y = common.clamp(y - node.position.y, 0, self.root_node.size.y * 0.95)
|
||||
resize_child_node(node, "y", y, dy)
|
||||
end
|
||||
node.divider = common.clamp(node.divider, 0.01, 0.99)
|
||||
|
@ -406,10 +406,10 @@ function RootView:on_touch_moved(x, y, dx, dy, ...)
|
|||
if self.dragged_divider then
|
||||
local node = self.dragged_divider
|
||||
if node.type == "hsplit" then
|
||||
x = common.clamp(x, 0, self.root_node.size.x * 0.95)
|
||||
x = common.clamp(x - node.position.x, 0, self.root_node.size.x * 0.95)
|
||||
resize_child_node(node, "x", x, dx)
|
||||
elseif node.type == "vsplit" then
|
||||
y = common.clamp(y, 0, self.root_node.size.y * 0.95)
|
||||
y = common.clamp(y - node.position.y, 0, self.root_node.size.y * 0.95)
|
||||
resize_child_node(node, "y", y, dy)
|
||||
end
|
||||
node.divider = common.clamp(node.divider, 0.01, 0.99)
|
||||
|
|
|
@ -58,9 +58,9 @@ function Scrollbar:new(options)
|
|||
---@type "expanded" | "contracted" | false @Force the scrollbar status
|
||||
self.force_status = options.force_status
|
||||
self:set_forced_status(options.force_status)
|
||||
---@type number? @Override the default value specified by `style.expanded_scrollbar_size`
|
||||
self.contracted_size = options.contracted_size
|
||||
---@type number? @Override the default value specified by `style.scrollbar_size`
|
||||
self.contracted_size = options.contracted_size
|
||||
---@type number? @Override the default value specified by `style.expanded_scrollbar_size`
|
||||
self.expanded_size = options.expanded_size
|
||||
end
|
||||
|
||||
|
@ -121,7 +121,7 @@ function Scrollbar:_get_thumb_rect_normal()
|
|||
across_size = across_size + (expanded_scrollbar_size - scrollbar_size) * self.expand_percent
|
||||
return
|
||||
nr.across + nr.across_size - across_size,
|
||||
nr.along + self.percent * nr.scrollable * (nr.along_size - along_size) / (sz - nr.along_size),
|
||||
nr.along + self.percent * (nr.along_size - along_size),
|
||||
across_size,
|
||||
along_size
|
||||
end
|
||||
|
@ -189,8 +189,9 @@ function Scrollbar:_on_mouse_pressed_normal(button, x, y, clicks)
|
|||
self.drag_start_offset = along - y
|
||||
return true
|
||||
elseif overlaps == "track" then
|
||||
local nr = self.normal_rect
|
||||
self.drag_start_offset = - along_size / 2
|
||||
return (y - self.normal_rect.along - along_size / 2) / self.normal_rect.along_size
|
||||
return common.clamp((y - nr.along - along_size / 2) / (nr.along_size - along_size), 0, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -237,7 +238,8 @@ end
|
|||
function Scrollbar:_on_mouse_moved_normal(x, y, dx, dy)
|
||||
if self.dragging then
|
||||
local nr = self.normal_rect
|
||||
return common.clamp((y - nr.along + self.drag_start_offset) / nr.along_size, 0, 1)
|
||||
local _, _, _, along_size = self:_get_thumb_rect_normal()
|
||||
return common.clamp((y - nr.along + self.drag_start_offset) / (nr.along_size - along_size), 0, 1)
|
||||
end
|
||||
return self:_update_hover_status_normal(x, y)
|
||||
end
|
||||
|
@ -280,7 +282,7 @@ function Scrollbar:set_size(x, y, w, h, scrollable)
|
|||
end
|
||||
|
||||
---Updates the scrollbar location
|
||||
---@param percent number @number between 0 and 1 representing the position of the middle part of the thumb
|
||||
---@param percent number @number between 0 and 1 where 0 means thumb at the top and 1 at the bottom
|
||||
function Scrollbar:set_percent(percent)
|
||||
self.percent = percent
|
||||
end
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
-- this file is used by lite-xl to setup the Lua environment when starting
|
||||
VERSION = "2.1.1r3"
|
||||
VERSION = "2.1.2r1"
|
||||
MOD_VERSION_MAJOR = 3
|
||||
MOD_VERSION_MINOR = 0
|
||||
MOD_VERSION_PATCH = 0
|
||||
MOD_VERSION_STRING = string.format("%d.%d.%d", MOD_VERSION_MAJOR, MOD_VERSION_MINOR, MOD_VERSION_PATCH)
|
||||
|
||||
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 os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or 1
|
||||
PATHSEP = package.config:sub(1, 1)
|
||||
|
||||
EXEDIR = EXEFILE:match("^(.+)[/\\][^/\\]+$")
|
||||
|
|
|
@ -232,15 +232,27 @@ function StatusView:register_docview_items()
|
|||
return {
|
||||
style.text, line, ":",
|
||||
col > config.line_limit and style.accent or style.text, col,
|
||||
style.text,
|
||||
self.separator,
|
||||
string.format("%.f%%", line / #dv.doc.lines * 100)
|
||||
style.text
|
||||
}
|
||||
end,
|
||||
command = "doc:go-to-line",
|
||||
tooltip = "line : column"
|
||||
})
|
||||
|
||||
self:add_item({
|
||||
predicate = predicate_docview,
|
||||
name = "doc:position-percent",
|
||||
alignment = StatusView.Item.LEFT,
|
||||
get_item = function()
|
||||
local dv = core.active_view
|
||||
local line = dv.doc:get_selection()
|
||||
return {
|
||||
string.format("%.f%%", line / #dv.doc.lines * 100)
|
||||
}
|
||||
end,
|
||||
tooltip = "caret position"
|
||||
})
|
||||
|
||||
self:add_item({
|
||||
predicate = predicate_docview,
|
||||
name = "doc:selections",
|
||||
|
@ -307,6 +319,19 @@ function StatusView:register_docview_items()
|
|||
end,
|
||||
command = "doc:toggle-line-ending"
|
||||
})
|
||||
|
||||
self:add_item {
|
||||
predicate = predicate_docview,
|
||||
name = "doc:overwrite-mode",
|
||||
alignment = StatusView.Item.RIGHT,
|
||||
get_item = function()
|
||||
return {
|
||||
style.text, core.active_view.doc.overwrite and "OVR" or "INS"
|
||||
}
|
||||
end,
|
||||
command = "doc:toggle-overwrite",
|
||||
separator = StatusView.separator2
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ local common = require "core.common"
|
|||
local syntax = {}
|
||||
syntax.items = {}
|
||||
|
||||
local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} }
|
||||
syntax.plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} }
|
||||
|
||||
|
||||
function syntax.add(t)
|
||||
|
@ -46,7 +46,7 @@ end
|
|||
function syntax.get(filename, header)
|
||||
return (filename and find(filename, "files"))
|
||||
or (header and find(header, "headers"))
|
||||
or plain_text_syntax
|
||||
or syntax.plain_text_syntax
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -210,9 +210,11 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
|
|||
-- Remove '^' from the beginning of the pattern
|
||||
if type(target) == "table" then
|
||||
target[p_idx] = code:usub(2)
|
||||
code = target[p_idx]
|
||||
else
|
||||
p.pattern = p.pattern and code:usub(2)
|
||||
p.regex = p.regex and code:usub(2)
|
||||
code = p.pattern or p.regex
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -142,14 +142,14 @@ function View:on_mouse_pressed(button, x, y, clicks)
|
|||
local result = self.v_scrollbar:on_mouse_pressed(button, x, y, clicks)
|
||||
if result then
|
||||
if result ~= true then
|
||||
self.scroll.to.y = result * self:get_scrollable_size()
|
||||
self.scroll.to.y = result * (self:get_scrollable_size() - self.size.y)
|
||||
end
|
||||
return true
|
||||
end
|
||||
result = self.h_scrollbar:on_mouse_pressed(button, x, y, clicks)
|
||||
if result then
|
||||
if result ~= true then
|
||||
self.scroll.to.x = result * self:get_h_scrollable_size()
|
||||
self.scroll.to.x = result * (self:get_h_scrollable_size() - self.size.x)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
@ -177,7 +177,7 @@ function View:on_mouse_moved(x, y, dx, dy)
|
|||
result = self.v_scrollbar:on_mouse_moved(x, y, dx, dy)
|
||||
if result then
|
||||
if result ~= true then
|
||||
self.scroll.to.y = result * self:get_scrollable_size()
|
||||
self.scroll.to.y = result * (self:get_scrollable_size() - self.size.y)
|
||||
if not config.animate_drag_scroll then
|
||||
self:clamp_scroll_position()
|
||||
self.scroll.y = self.scroll.to.y
|
||||
|
@ -191,7 +191,7 @@ function View:on_mouse_moved(x, y, dx, dy)
|
|||
result = self.h_scrollbar:on_mouse_moved(x, y, dx, dy)
|
||||
if result then
|
||||
if result ~= true then
|
||||
self.scroll.to.x = result * self:get_h_scrollable_size()
|
||||
self.scroll.to.x = result * (self:get_h_scrollable_size() - self.size.x)
|
||||
if not config.animate_drag_scroll then
|
||||
self:clamp_scroll_position()
|
||||
self.scroll.x = self.scroll.to.x
|
||||
|
@ -287,12 +287,16 @@ end
|
|||
function View:update_scrollbar()
|
||||
local v_scrollable = self:get_scrollable_size()
|
||||
self.v_scrollbar:set_size(self.position.x, self.position.y, self.size.x, self.size.y, v_scrollable)
|
||||
self.v_scrollbar:set_percent(self.scroll.y/v_scrollable)
|
||||
local v_percent = self.scroll.y/(v_scrollable - self.size.y)
|
||||
-- Avoid setting nan percent
|
||||
self.v_scrollbar:set_percent(v_percent == v_percent and v_percent or 0)
|
||||
self.v_scrollbar:update()
|
||||
|
||||
local h_scrollable = self:get_h_scrollable_size()
|
||||
self.h_scrollbar:set_size(self.position.x, self.position.y, self.size.x, self.size.y, h_scrollable)
|
||||
self.h_scrollbar:set_percent(self.scroll.x/h_scrollable)
|
||||
local h_percent = self.scroll.x/(h_scrollable - self.size.x)
|
||||
-- Avoid setting nan percent
|
||||
self.h_scrollbar:set_percent(h_percent == h_percent and h_percent or 0)
|
||||
self.h_scrollbar:update()
|
||||
end
|
||||
|
||||
|
|
|
@ -10,6 +10,10 @@ local RootView = require "core.rootview"
|
|||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
---Symbols cache of all open documents
|
||||
---@type table<core.doc, table>
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
|
||||
config.plugins.autocomplete = common.merge({
|
||||
-- Amount of characters that need to be written for autocomplete
|
||||
min_len = 3,
|
||||
|
@ -19,8 +23,16 @@ config.plugins.autocomplete = common.merge({
|
|||
max_suggestions = 100,
|
||||
-- Maximum amount of symbols to cache per document
|
||||
max_symbols = 4000,
|
||||
-- Which symbols to show on the suggestions list: global, local, related, none
|
||||
suggestions_scope = "global",
|
||||
-- Font size of the description box
|
||||
desc_font_size = 12,
|
||||
-- Do not show the icons associated to the suggestions
|
||||
hide_icons = false,
|
||||
-- Position where icons will be displayed on the suggestions list
|
||||
icon_position = "left",
|
||||
-- Do not show the additional information related to a suggestion
|
||||
hide_info = false,
|
||||
-- The config specification used by gui generators
|
||||
config_spec = {
|
||||
name = "Autocomplete",
|
||||
|
@ -60,6 +72,26 @@ config.plugins.autocomplete = common.merge({
|
|||
min = 1000,
|
||||
max = 10000
|
||||
},
|
||||
{
|
||||
label = "Suggestions Scope",
|
||||
description = "Which symbols to show on the suggestions list.",
|
||||
path = "suggestions_scope",
|
||||
type = "selection",
|
||||
default = "global",
|
||||
values = {
|
||||
{"All Documents", "global"},
|
||||
{"Current Document", "local"},
|
||||
{"Related Documents", "related"},
|
||||
{"Known Symbols", "none"}
|
||||
},
|
||||
on_apply = function(value)
|
||||
if value == "global" then
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if cache[doc] then cache[doc] = nil end
|
||||
end
|
||||
end
|
||||
end
|
||||
},
|
||||
{
|
||||
label = "Description Font Size",
|
||||
description = "Font size of the description box.",
|
||||
|
@ -67,6 +99,31 @@ config.plugins.autocomplete = common.merge({
|
|||
type = "number",
|
||||
default = 12,
|
||||
min = 8
|
||||
},
|
||||
{
|
||||
label = "Hide Icons",
|
||||
description = "Do not show icons on the suggestions list.",
|
||||
path = "hide_icons",
|
||||
type = "toggle",
|
||||
default = false
|
||||
},
|
||||
{
|
||||
label = "Icons Position",
|
||||
description = "Position to display icons on the suggestions list.",
|
||||
path = "icon_position",
|
||||
type = "selection",
|
||||
default = "left",
|
||||
values = {
|
||||
{"Left", "left"},
|
||||
{"Right", "Right"}
|
||||
}
|
||||
},
|
||||
{
|
||||
label = "Hide Items Info",
|
||||
description = "Do not show the additional info related to each suggestion.",
|
||||
path = "hide_info",
|
||||
type = "toggle",
|
||||
default = false
|
||||
}
|
||||
}
|
||||
}, config.plugins.autocomplete)
|
||||
|
@ -76,6 +133,7 @@ local autocomplete = {}
|
|||
autocomplete.map = {}
|
||||
autocomplete.map_manually = {}
|
||||
autocomplete.on_close = nil
|
||||
autocomplete.icons = {}
|
||||
|
||||
-- Flag that indicates if the autocomplete box was manually triggered
|
||||
-- with the autocomplete.complete() function to prevent the suggestions
|
||||
|
@ -95,6 +153,7 @@ function autocomplete.add(t, manually_triggered)
|
|||
{
|
||||
text = text,
|
||||
info = info.info,
|
||||
icon = info.icon, -- Name of icon to show
|
||||
desc = info.desc, -- Description shown on item selected
|
||||
onhover = info.onhover, -- A callback called once when item is hovered
|
||||
onselect = info.onselect, -- A callback called when item is selected
|
||||
|
@ -119,28 +178,35 @@ end
|
|||
--
|
||||
-- Thread that scans open document symbols and cache them
|
||||
--
|
||||
local max_symbols = config.plugins.autocomplete.max_symbols
|
||||
local global_symbols = {}
|
||||
|
||||
core.add_thread(function()
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
|
||||
local function get_syntax_symbols(symbols, doc)
|
||||
if doc.syntax then
|
||||
for sym in pairs(doc.syntax.symbols) do
|
||||
symbols[sym] = true
|
||||
local function load_syntax_symbols(doc)
|
||||
if doc.syntax and not autocomplete.map["language_"..doc.syntax.name] then
|
||||
local symbols = {
|
||||
name = "language_"..doc.syntax.name,
|
||||
files = doc.syntax.files,
|
||||
items = {}
|
||||
}
|
||||
for name, type in pairs(doc.syntax.symbols) do
|
||||
symbols.items[name] = type
|
||||
end
|
||||
autocomplete.add(symbols)
|
||||
return symbols.items
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
local function get_symbols(doc)
|
||||
local s = {}
|
||||
get_syntax_symbols(s, doc)
|
||||
local syntax_symbols = load_syntax_symbols(doc)
|
||||
local max_symbols = config.plugins.autocomplete.max_symbols
|
||||
if doc.disable_symbols then return s end
|
||||
local i = 1
|
||||
local symbols_count = 0
|
||||
while i <= #doc.lines do
|
||||
for sym in doc.lines[i]:gmatch(config.symbol_pattern) do
|
||||
if not s[sym] then
|
||||
if not s[sym] and not syntax_symbols[sym] then
|
||||
symbols_count = symbols_count + 1
|
||||
if symbols_count > max_symbols then
|
||||
s = nil
|
||||
|
@ -186,14 +252,18 @@ core.add_thread(function()
|
|||
}
|
||||
end
|
||||
-- update symbol set with doc's symbol set
|
||||
if config.plugins.autocomplete.suggestions_scope == "global" then
|
||||
for sym in pairs(cache[doc].symbols) do
|
||||
symbols[sym] = true
|
||||
end
|
||||
end
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
-- update symbols list
|
||||
autocomplete.add { name = "open-docs", items = symbols }
|
||||
-- update global symbols list
|
||||
if config.plugins.autocomplete.suggestions_scope == "global" then
|
||||
global_symbols = symbols
|
||||
end
|
||||
|
||||
-- wait for next scan
|
||||
local valid = true
|
||||
|
@ -240,12 +310,50 @@ local function update_suggestions()
|
|||
map = autocomplete.map_manually
|
||||
end
|
||||
|
||||
local assigned_sym = {}
|
||||
|
||||
-- get all relevant suggestions for given filename
|
||||
local items = {}
|
||||
for _, v in pairs(map) do
|
||||
if common.match_pattern(filename, v.files) then
|
||||
for _, item in pairs(v.items) do
|
||||
table.insert(items, item)
|
||||
assigned_sym[item.text] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Append the global, local or related text symbols if applicable
|
||||
local scope = config.plugins.autocomplete.suggestions_scope
|
||||
|
||||
if not triggered_manually then
|
||||
local text_symbols = nil
|
||||
|
||||
if scope == "global" then
|
||||
text_symbols = global_symbols
|
||||
elseif scope == "local" and cache[doc] and cache[doc].symbols then
|
||||
text_symbols = cache[doc].symbols
|
||||
elseif scope == "related" then
|
||||
for _, d in ipairs(core.docs) do
|
||||
if doc.syntax == d.syntax then
|
||||
if cache[d].symbols then
|
||||
for name in pairs(cache[d].symbols) do
|
||||
if not assigned_sym[name] then
|
||||
table.insert(items, setmetatable(
|
||||
{text = name, info = "normal"}, mt
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if text_symbols then
|
||||
for name in pairs(text_symbols) do
|
||||
if not assigned_sym[name] then
|
||||
table.insert(items, setmetatable({text = name, info = "normal"}, mt))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -286,13 +394,23 @@ local function get_suggestions_rect(av)
|
|||
y = y + av:get_line_height() + style.padding.y
|
||||
local font = av:get_font()
|
||||
local th = font:get_height()
|
||||
local has_icons = false
|
||||
local hide_info = config.plugins.autocomplete.hide_info
|
||||
local hide_icons = config.plugins.autocomplete.hide_icons
|
||||
|
||||
local max_width = 0
|
||||
for _, s in ipairs(suggestions) do
|
||||
local w = font:get_width(s.text)
|
||||
if s.info then
|
||||
if s.info and not hide_info then
|
||||
w = w + style.font:get_width(s.info) + style.padding.x
|
||||
end
|
||||
local icon = s.icon or s.info
|
||||
if not hide_icons and icon and autocomplete.icons[icon] then
|
||||
w = w + autocomplete.icons[icon].font:get_width(
|
||||
autocomplete.icons[icon].char
|
||||
) + (style.padding.x / 2)
|
||||
has_icons = true
|
||||
end
|
||||
max_width = math.max(max_width, w)
|
||||
end
|
||||
|
||||
|
@ -319,7 +437,8 @@ local function get_suggestions_rect(av)
|
|||
x - style.padding.x,
|
||||
y - style.padding.y,
|
||||
max_width + style.padding.x * 2,
|
||||
max_items * (th + style.padding.y) + style.padding.y
|
||||
max_items * (th + style.padding.y) + style.padding.y,
|
||||
has_icons
|
||||
end
|
||||
|
||||
local function wrap_line(line, max_chars)
|
||||
|
@ -439,7 +558,7 @@ local function draw_suggestions_box(av)
|
|||
local ah = config.plugins.autocomplete.max_height
|
||||
|
||||
-- draw background rect
|
||||
local rx, ry, rw, rh = get_suggestions_rect(av)
|
||||
local rx, ry, rw, rh, has_icons = get_suggestions_rect(av)
|
||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||
|
||||
-- draw text
|
||||
|
@ -448,17 +567,52 @@ local function draw_suggestions_box(av)
|
|||
local y = ry + style.padding.y / 2
|
||||
local show_count = #suggestions <= ah and #suggestions or ah
|
||||
local start_index = suggestions_idx > ah and (suggestions_idx-(ah-1)) or 1
|
||||
local hide_info = config.plugins.autocomplete.hide_info
|
||||
|
||||
for i=start_index, start_index+show_count-1, 1 do
|
||||
if not suggestions[i] then
|
||||
break
|
||||
end
|
||||
local s = suggestions[i]
|
||||
|
||||
local icon_l_padding, icon_r_padding = 0, 0
|
||||
|
||||
if has_icons then
|
||||
local icon = s.icon or s.info
|
||||
if icon and autocomplete.icons[icon] then
|
||||
local ifont = autocomplete.icons[icon].font
|
||||
local itext = autocomplete.icons[icon].char
|
||||
local icolor = autocomplete.icons[icon].color
|
||||
if i == suggestions_idx then
|
||||
icolor = style.accent
|
||||
elseif type(icolor) == "string" then
|
||||
icolor = style.syntax[icolor]
|
||||
end
|
||||
if config.plugins.autocomplete.icon_position == "left" then
|
||||
common.draw_text(
|
||||
ifont, icolor, itext, "left", rx + style.padding.x, y, rw, lh
|
||||
)
|
||||
icon_l_padding = ifont:get_width(itext) + (style.padding.x / 2)
|
||||
else
|
||||
common.draw_text(
|
||||
ifont, icolor, itext, "right", rx, y, rw - style.padding.x, lh
|
||||
)
|
||||
icon_r_padding = ifont:get_width(itext) + (style.padding.x / 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local color = (i == suggestions_idx) and style.accent or style.text
|
||||
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
||||
if s.info then
|
||||
common.draw_text(
|
||||
font, color, s.text, "left",
|
||||
rx + icon_l_padding + style.padding.x, y, rw, lh
|
||||
)
|
||||
if s.info and not hide_info then
|
||||
color = (i == suggestions_idx) and style.text or style.dim
|
||||
common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh)
|
||||
common.draw_text(
|
||||
style.font, color, s.info, "right",
|
||||
rx, y, rw - icon_r_padding - style.padding.x, lh
|
||||
)
|
||||
end
|
||||
y = y + lh
|
||||
if suggestions_idx == i then
|
||||
|
@ -619,6 +773,31 @@ function autocomplete.can_complete()
|
|||
return false
|
||||
end
|
||||
|
||||
---Register a font icon that can be assigned to completion items.
|
||||
---@param name string
|
||||
---@param character string
|
||||
---@param font? renderer.font
|
||||
---@param color? string | renderer.color A style.syntax[] name or specific color
|
||||
function autocomplete.add_icon(name, character, font, color)
|
||||
local color_type = type(color)
|
||||
assert(
|
||||
not color or color_type == "table"
|
||||
or (color_type == "string" and style.syntax[color]),
|
||||
"invalid icon color given"
|
||||
)
|
||||
autocomplete.icons[name] = {
|
||||
char = character,
|
||||
font = font or style.code_font,
|
||||
color = color or "keyword"
|
||||
}
|
||||
end
|
||||
|
||||
--
|
||||
-- Register built-in syntax symbol types icon
|
||||
--
|
||||
for name, _ in pairs(style.syntax) do
|
||||
autocomplete.add_icon(name, "M", style.icon_font, name)
|
||||
end
|
||||
|
||||
--
|
||||
-- Commands
|
||||
|
@ -632,7 +811,6 @@ command.add(predicate, {
|
|||
["autocomplete:complete"] = function(dv)
|
||||
local doc = dv.doc
|
||||
local item = suggestions[suggestions_idx]
|
||||
local text = item.text
|
||||
local inserted = false
|
||||
if item.onselect then
|
||||
inserted = item.onselect(suggestions_idx, item)
|
||||
|
|
|
@ -266,7 +266,7 @@ local function detect_indent_stat(doc)
|
|||
local max_lines = auto_detect_max_lines
|
||||
for i, text in get_non_empty_lines(doc.syntax, doc.lines) do
|
||||
local spaces = text:match("^ +")
|
||||
if spaces then table.insert(stat, spaces:len()) end
|
||||
if spaces and #spaces > 1 then table.insert(stat, #spaces) end
|
||||
local tabs = text:match("^\t+")
|
||||
if tabs then tab_count = tab_count + 1 end
|
||||
-- if nothing found for first lines try at least 4 more times
|
||||
|
|
|
@ -347,7 +347,7 @@ end
|
|||
|
||||
command.add(nil, {
|
||||
["draw-whitespace:toggle"] = function()
|
||||
config.plugins.drawwhitespace.enabled = not config.drawwhitespace.enabled
|
||||
config.plugins.drawwhitespace.enabled = not config.plugins.drawwhitespace.enabled
|
||||
end,
|
||||
|
||||
["draw-whitespace:disable"] = function()
|
||||
|
|
|
@ -14,9 +14,9 @@ syntax.add {
|
|||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "0x%x+", type = "number" },
|
||||
{ pattern = "0x%x+[%x']*", type = "number" },
|
||||
{ pattern = "%d+[%d%.'eE]*f?", type = "number" },
|
||||
{ pattern = "%.?%d+f?", type = "number" },
|
||||
{ pattern = "%.?%d+[%d']*f?", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|:&]", type = "operator" },
|
||||
{ pattern = "##", type = "operator" },
|
||||
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
|
|
|
@ -1,6 +1,56 @@
|
|||
-- mod-version:3
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
-- Regex pattern explanation:
|
||||
-- This will match / and will look ahead for something that looks like a regex.
|
||||
--
|
||||
-- (?!/) Don't match empty regexes.
|
||||
--
|
||||
-- (?>...) this is using an atomic group to minimize backtracking, as that'd
|
||||
-- cause "Catastrophic Backtracking" in some cases.
|
||||
--
|
||||
-- [^\\[\/]++ will match anything that's isn't an escape, a start of character
|
||||
-- class or an end of pattern, without backtracking (the second +).
|
||||
--
|
||||
-- \\. will match anything that's escaped.
|
||||
--
|
||||
-- \[(?:[^\\\]++]|\\.)*+\] will match character classes.
|
||||
--
|
||||
-- /[gmiyuvsd]*\s*[\n,;\)\]\}\.]) will match the end of pattern delimiter, optionally
|
||||
-- followed by pattern options, and anything that can
|
||||
-- be after a pattern.
|
||||
--
|
||||
-- Demo with some unit tests (click on the Unit Tests entry): https://regex101.com/r/R0w8Qw/1
|
||||
-- Note that it has a couple of changes to make it work on that platform.
|
||||
local regex_pattern = {
|
||||
[=[/(?=(?!/)(?:(?>[^\\[\/]++|\\.|\[(?:[^\\\]]++|\\.)*+\])*+)++/[gmiyuvsd]*\s*[\n,;\)\]\}\.])()]=],
|
||||
"/()[gmiyuvsd]*", "\\"
|
||||
}
|
||||
|
||||
-- For the moment let's not actually differentiate the insides of the regex,
|
||||
-- as this will need new token types...
|
||||
local inner_regex_syntax = {
|
||||
patterns = {
|
||||
{ pattern = "%(()%?[:!=><]", type = { "string", "string" } },
|
||||
{ pattern = "[.?+*%(%)|]", type = "string" },
|
||||
{ pattern = "{%d*,?%d*}", type = "string" },
|
||||
{ regex = { [=[\[()\^?]=], [=[(?:\]|(?=\n))()]=], "\\" },
|
||||
type = { "string", "string" },
|
||||
syntax = { -- Inside character class
|
||||
patterns = {
|
||||
{ pattern = "\\\\", type = "string" },
|
||||
{ pattern = "\\%]", type = "string" },
|
||||
{ pattern = "[^%]\n]", type = "string" }
|
||||
},
|
||||
symbols = {}
|
||||
}
|
||||
},
|
||||
{ regex = "\\/", type = "string" },
|
||||
{ regex = "[^/\n]", type = "string" },
|
||||
},
|
||||
symbols = {}
|
||||
}
|
||||
|
||||
syntax.add {
|
||||
name = "JavaScript",
|
||||
files = { "%.js$", "%.json$", "%.cson$", "%.mjs$", "%.cjs$" },
|
||||
|
@ -9,16 +59,16 @@ syntax.add {
|
|||
patterns = {
|
||||
{ pattern = "//.*", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '/[^= ]', '/', '\\' },type = "string" },
|
||||
{ regex = regex_pattern, syntax = inner_regex_syntax, type = {"string", "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 = "-?%.?%d+", type = "number" },
|
||||
{ pattern = "0x[%da-fA-F_]+n?()%s*()/?", type = {"number", "normal", "operator"} },
|
||||
{ pattern = "-?%d+[%d%.eE_n]*()%s*()/?", type = {"number", "normal", "operator"} },
|
||||
{ pattern = "-?%.?%d+()%s*()/?", type = {"number", "normal", "operator"} },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "[%a_][%w_]*()%s*()/?", type = {"symbol", "normal", "operator"} },
|
||||
},
|
||||
symbols = {
|
||||
["async"] = "keyword",
|
||||
|
|
|
@ -3,25 +3,6 @@ local syntax = require "core.syntax"
|
|||
local style = require "core.style"
|
||||
local core = require "core"
|
||||
|
||||
local initial_color = style.syntax["keyword2"]
|
||||
|
||||
-- Add 3 type of font styles for use on markdown files
|
||||
for _, attr in pairs({"bold", "italic", "bold_italic"}) do
|
||||
local attributes = {}
|
||||
if attr ~= "bold_italic" then
|
||||
attributes[attr] = true
|
||||
else
|
||||
attributes["bold"] = true
|
||||
attributes["italic"] = true
|
||||
end
|
||||
style.syntax_fonts["markdown_"..attr] = style.code_font:copy(
|
||||
style.code_font:get_size(),
|
||||
attributes
|
||||
)
|
||||
-- also add a color for it
|
||||
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
|
||||
end
|
||||
|
||||
local in_squares_match = "^%[%]"
|
||||
local in_parenthesis_match = "^%(%)"
|
||||
|
||||
|
@ -225,12 +206,63 @@ syntax.add {
|
|||
|
||||
-- Adjust the color on theme changes
|
||||
core.add_thread(function()
|
||||
while true do
|
||||
if initial_color ~= style.syntax["keyword2"] then
|
||||
for _, attr in pairs({"bold", "italic", "bold_italic"}) do
|
||||
local custom_fonts = { bold = {font = nil, color = nil}, italic = {}, bold_italic = {} }
|
||||
local initial_color
|
||||
local last_code_font
|
||||
|
||||
local function set_font(attr)
|
||||
local attributes = {}
|
||||
if attr ~= "bold_italic" then
|
||||
attributes[attr] = true
|
||||
else
|
||||
attributes["bold"] = true
|
||||
attributes["italic"] = true
|
||||
end
|
||||
local font = style.code_font:copy(
|
||||
style.code_font:get_size(),
|
||||
attributes
|
||||
)
|
||||
custom_fonts[attr].font = font
|
||||
style.syntax_fonts["markdown_"..attr] = font
|
||||
end
|
||||
|
||||
local function set_color(attr)
|
||||
custom_fonts[attr].color = style.syntax["keyword2"]
|
||||
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
|
||||
end
|
||||
|
||||
-- Add 3 type of font styles for use on markdown files
|
||||
for attr, _ in pairs(custom_fonts) do
|
||||
-- Only set it if the font wasn't manually customized
|
||||
if not style.syntax_fonts["markdown_"..attr] then
|
||||
set_font(attr)
|
||||
end
|
||||
|
||||
-- Only set it if the color wasn't manually customized
|
||||
if not style.syntax["markdown_"..attr] then
|
||||
set_color(attr)
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
if last_code_font ~= style.code_font then
|
||||
last_code_font = style.code_font
|
||||
for attr, _ in pairs(custom_fonts) do
|
||||
-- Only set it if the font wasn't manually customized
|
||||
if style.syntax_fonts["markdown_"..attr] == custom_fonts[attr].font then
|
||||
set_font(attr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if initial_color ~= style.syntax["keyword2"] then
|
||||
initial_color = style.syntax["keyword2"]
|
||||
for attr, _ in pairs(custom_fonts) do
|
||||
-- Only set it if the color wasn't manually customized
|
||||
if style.syntax["markdown_"..attr] == custom_fonts[attr].color then
|
||||
set_color(attr)
|
||||
end
|
||||
end
|
||||
end
|
||||
coroutine.yield(1)
|
||||
end
|
||||
|
|
|
@ -219,7 +219,7 @@ function LineWrapping.draw_guide(docview)
|
|||
end
|
||||
|
||||
function LineWrapping.update_docview_breaks(docview)
|
||||
local x,y,w,h = docview.v_scrollbar:get_thumb_rect()
|
||||
local w = docview.v_scrollbar.expanded_size or style.expanded_scrollbar_size
|
||||
local width = (type(config.plugins.linewrapping.width_override) == "function" and config.plugins.linewrapping.width_override(docview))
|
||||
or config.plugins.linewrapping.width_override or (docview.size.x - docview:get_gutter_width() - w)
|
||||
if (not docview.wrapped_settings or docview.wrapped_settings.width == nil or width ~= docview.wrapped_settings.width) then
|
||||
|
|
|
@ -29,7 +29,7 @@ local tooltip_alpha_rate = 1
|
|||
|
||||
local function get_depth(filename)
|
||||
local n = 1
|
||||
for sep in filename:gmatch("[\\/]") do
|
||||
for _ in filename:gmatch(PATHSEP) do
|
||||
n = n + 1
|
||||
end
|
||||
return n
|
||||
|
|
|
@ -83,7 +83,8 @@ local function save_view(view)
|
|||
filename = view.doc.filename,
|
||||
selection = { view.doc:get_selection() },
|
||||
scroll = { x = view.scroll.to.x, y = view.scroll.to.y },
|
||||
text = not view.doc.filename and view.doc:get_text(1, 1, math.huge, math.huge)
|
||||
crlf = view.doc.crlf,
|
||||
text = view.doc.new_file and view.doc:get_text(1, 1, math.huge, math.huge)
|
||||
}
|
||||
end
|
||||
if mt == LogView then return end
|
||||
|
@ -106,7 +107,6 @@ local function load_view(t)
|
|||
if not t.filename then
|
||||
-- document not associated to a file
|
||||
dv = DocView(core.open_doc())
|
||||
if t.text then dv.doc:insert(1, 1, t.text) end
|
||||
else
|
||||
-- we have a filename, try to read the file
|
||||
local ok, doc = pcall(core.open_doc, t.filename)
|
||||
|
@ -114,9 +114,11 @@ local function load_view(t)
|
|||
dv = DocView(doc)
|
||||
end
|
||||
end
|
||||
-- doc view "dv" can be nil here if the filename associated to the document
|
||||
-- cannot be read.
|
||||
if dv and dv.doc then
|
||||
if dv.doc.new_file and t.text then
|
||||
dv.doc:insert(1, 1, t.text)
|
||||
dv.doc.crlf = t.crlf
|
||||
end
|
||||
dv.doc:set_selection(table.unpack(t.selection))
|
||||
dv.last_line1, dv.last_col1, dv.last_line2, dv.last_col2 = dv.doc:get_selection()
|
||||
dv.scroll.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
|
||||
|
|
|
@ -45,10 +45,14 @@ function dirmonitor:unwatch(fd_or_path) end
|
|||
---edited, removed or added. A file descriptor will be passed to the
|
||||
---callback in "multiple" mode or a path in "single" mode.
|
||||
---
|
||||
---If an error occurred during the callback execution, the error callback will be called with the error object.
|
||||
---This callback should not manipulate coroutines to avoid deadlocks.
|
||||
---
|
||||
---@param callback dirmonitor.callback
|
||||
---@param error_callback fun(error: any): nil
|
||||
---
|
||||
---@return boolean? changes True when changes were detected.
|
||||
function dirmonitor:check(callback) end
|
||||
function dirmonitor:check(callback, error_callback) end
|
||||
|
||||
---
|
||||
---Get the working mode for the current file system monitoring backend.
|
||||
|
|
|
@ -61,10 +61,10 @@ function system.poll_event() end
|
|||
---
|
||||
---Wait until an event is triggered.
|
||||
---
|
||||
---@param timeout number Amount of seconds, also supports fractions
|
||||
---of a second, eg: 0.01
|
||||
---@param timeout? number Amount of seconds, also supports fractions
|
||||
---of a second, eg: 0.01. If not provided, waits forever.
|
||||
---
|
||||
---@return boolean status True on success or false if there was an error.
|
||||
---@return boolean status True on success or false if there was an error or if no event was received.
|
||||
function system.wait_event(timeout) end
|
||||
|
||||
---
|
||||
|
|
28
meson.build
28
meson.build
|
@ -4,8 +4,7 @@ project('lite-xl',
|
|||
license : 'MIT',
|
||||
meson_version : '>= 0.56',
|
||||
default_options : [
|
||||
'c_std=gnu11',
|
||||
'wrap_mode=nofallback'
|
||||
'c_std=gnu11'
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -84,11 +83,10 @@ if not get_option('source-only')
|
|||
'lua', # Fedora
|
||||
]
|
||||
|
||||
if get_option('use_system_lua')
|
||||
foreach lua : lua_names
|
||||
last_lua = (lua == lua_names[-1] or get_option('wrap_mode') == 'forcefallback')
|
||||
lua_dep = dependency(lua, fallback: last_lua ? ['lua', 'lua_dep'] : [], required : false,
|
||||
version: '>= 5.4',
|
||||
default_options: default_fallback_options + ['default_library=static', 'line_editing=false', 'interpreter=false']
|
||||
lua_dep = dependency(lua, required : false,
|
||||
)
|
||||
if lua_dep.found()
|
||||
break
|
||||
|
@ -101,6 +99,11 @@ if not get_option('source-only')
|
|||
lua_dep = cc.find_library('lua', required : true)
|
||||
endif
|
||||
endforeach
|
||||
else
|
||||
lua_dep = dependency('', fallback: ['lua', 'lua_dep'], required : true,
|
||||
default_options: default_fallback_options + ['default_library=static', 'line_editing=disabled', 'interpreter=false']
|
||||
)
|
||||
endif
|
||||
|
||||
pcre2_dep = dependency('libpcre2-8', fallback: ['pcre2', 'libpcre2_8'],
|
||||
default_options: default_fallback_options + ['default_library=static', 'grep=false', 'test=false']
|
||||
|
@ -120,6 +123,7 @@ if not get_option('source-only')
|
|||
sdl_options += 'use_atomic=enabled'
|
||||
sdl_options += 'use_threads=enabled'
|
||||
sdl_options += 'use_timers=enabled'
|
||||
sdl_options += 'with_main=true'
|
||||
# investigate if this is truly needed
|
||||
# Do not remove before https://github.com/libsdl-org/SDL/issues/5413 is released
|
||||
sdl_options += 'use_events=enabled'
|
||||
|
@ -152,12 +156,24 @@ if not get_option('source-only')
|
|||
sdl_options += 'use_video_vulkan=disabled'
|
||||
sdl_options += 'use_video_offscreen=disabled'
|
||||
sdl_options += 'use_power=disabled'
|
||||
sdl_options += 'system_iconv=disabled'
|
||||
|
||||
sdl_dep = dependency('sdl2', fallback: ['sdl2', 'sdl2_dep'],
|
||||
default_options: default_fallback_options + sdl_options
|
||||
)
|
||||
|
||||
lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl]
|
||||
if host_machine.system() == 'windows'
|
||||
if sdl_dep.type_name() == 'internal'
|
||||
sdlmain_dep = dependency('sdl2main', fallback: ['sdl2main_dep'])
|
||||
else
|
||||
sdlmain_dep = cc.find_library('SDL2main')
|
||||
endif
|
||||
else
|
||||
sdlmain_dep = dependency('', required: false)
|
||||
assert(not sdlmain_dep.found(), 'checking if fake dependency has been found')
|
||||
endif
|
||||
|
||||
lite_deps = [lua_dep, sdl_dep, sdlmain_dep, freetype_dep, pcre2_dep, libm, libdl]
|
||||
endif
|
||||
#===============================================================================
|
||||
# Install Configuration
|
||||
|
|
|
@ -4,3 +4,4 @@ option('portable', type : 'boolean', value : false, description: 'Portable insta
|
|||
option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer')
|
||||
option('dirmonitor_backend', type : 'combo', value : '', choices : ['', 'inotify', 'fsevents', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use')
|
||||
option('arch_tuple', type : 'string', value : '', description: 'Specify a custom architecture tuple')
|
||||
option('use_system_lua', type : 'boolean', value : false, description: 'Prefer System Lua over a the meson wrap')
|
||||
|
|
|
@ -11,8 +11,9 @@ This folder contains resources that is used for building or packaging the projec
|
|||
- `icons/icon.{icns,ico,inl,rc,svg}`: lite-xl icon in various formats.
|
||||
- `linux/com.lite_xl.LiteXL.appdata.xml`: AppStream metadata.
|
||||
- `linux/com.lite_xl.LiteXL.desktop`: Desktop file for Linux desktops.
|
||||
- `macos/appdmg.png`: Background image for packaging MacOS DMGs.
|
||||
- `macos/Info.plist.in`: Template for generating `info.plist` on MacOS. See `macos/macos-retina-display.md` for details.
|
||||
- `macos/dmg-cover.png`: Background image for packaging macOS DMGs.
|
||||
- `macos/Info.plist.in`: Template for generating `info.plist` on macOS. See `macos/macos-retina-display.md` for details.
|
||||
- `macos/lite-xl-dmg.py`: Configuration options for dmgbuild for packaging macOS DMGs.
|
||||
- `windows/001-lua-unicode.diff`: Patch for allowing Lua to load files with UTF-8 filenames on Windows.
|
||||
|
||||
### Development
|
||||
|
|
|
@ -31,9 +31,14 @@
|
|||
* An example command would be: gcc -shared -o xxxxx.so xxxxx.c
|
||||
* You must not link to ANY lua library to avoid symbol collision.
|
||||
*
|
||||
* This file contains stock configuration for a typical installation of Lua 5.4.
|
||||
* This file contains stock configuration for a typical installation of Lua 5.4.6.
|
||||
* DO NOT MODIFY ANYTHING. MODIFYING STUFFS IN HERE WILL BREAK
|
||||
* COMPATIBILITY WITH LITE XL AND CAUSE UNDEBUGGABLE BUGS.
|
||||
*
|
||||
* For reference, here are a list of permalinks to previous version of this file that targets an older version of Lua.
|
||||
* If you don't need functionalities offered by the new version, use the OLDEST FILE for backwards compatibility.
|
||||
*
|
||||
* - Lua 5.4.4: https://github.com/lite-xl/lite-xl/blob/397973067f14420b26e3b20a238a50016c0b75e2/resources/include/lite_xl_plugin_api.h
|
||||
**/
|
||||
#ifndef LITE_XL_PLUGIN_API
|
||||
#define LITE_XL_PLUGIN_API
|
||||
|
@ -1028,6 +1033,7 @@ extern const char lua_ident[];
|
|||
SYMBOL_DECLARE(lua_State *, lua_newstate, lua_Alloc f, void *ud)
|
||||
SYMBOL_DECLARE(void, lua_close, lua_State *L)
|
||||
SYMBOL_DECLARE(lua_State *, lua_newthread, lua_State *L)
|
||||
SYMBOL_DECLARE(int, lua_closethread, lua_State *L, lua_State *from)
|
||||
SYMBOL_DECLARE(int, lua_resetthread, lua_State *L)
|
||||
|
||||
SYMBOL_DECLARE(lua_CFunction, lua_atpanic, lua_State *L, lua_CFunction panicf)
|
||||
|
@ -1739,6 +1745,9 @@ SYMBOL_WRAP_DECL(void, lua_close, lua_State *L) {
|
|||
SYMBOL_WRAP_DECL(lua_State *, lua_newthread, lua_State *L) {
|
||||
return SYMBOL_WRAP_CALL(lua_newthread, L);
|
||||
}
|
||||
SYMBOL_WRAP_DECL(int, lua_closethread, lua_State *L, lua_State *from) {
|
||||
return SYMBOL_WRAP_CALL(lua_closethread, L, from);
|
||||
}
|
||||
SYMBOL_WRAP_DECL(int, lua_resetthread, lua_State *L) {
|
||||
return SYMBOL_WRAP_CALL(lua_resetthread, L);
|
||||
}
|
||||
|
@ -2351,6 +2360,7 @@ void lite_xl_plugin_init(void *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_closethread, int, lua_State *L, lua_State *from);
|
||||
IMPORT_SYMBOL(lua_resetthread, int, lua_State *L);
|
||||
IMPORT_SYMBOL(lua_atpanic, lua_CFunction, lua_State *L, lua_CFunction panicf);
|
||||
IMPORT_SYMBOL(lua_version, lua_Number, lua_State *L);
|
||||
|
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
@ -0,0 +1,28 @@
|
|||
# configuration for dmgbuild
|
||||
|
||||
import os.path
|
||||
|
||||
app_path = "Lite XL.app"
|
||||
app_name = os.path.basename(app_path)
|
||||
|
||||
# Image options
|
||||
format = defines.get("format", "UDZO")
|
||||
|
||||
# Content options
|
||||
files = [(app_path, app_name)]
|
||||
symlinks = { "Applications": "/Applications" }
|
||||
icon = "resources/icons/icon.icns"
|
||||
icon_locations = {
|
||||
app_name: (144, 248),
|
||||
"Applications": (336, 248)
|
||||
}
|
||||
|
||||
# Window options
|
||||
background = "resources/macos/dmg-cover.png"
|
||||
window_rect = ((360, 360), (480, 380))
|
||||
default_view = "coverflow"
|
||||
include_icon_view_settings = True
|
||||
|
||||
# Icon view options
|
||||
icon_size = 80
|
||||
text_size = 11.0
|
|
@ -1,6 +1,6 @@
|
|||
diff -ruN lua-5.4.4\meson.build lua-5.4.4-patched\meson.build
|
||||
--- lua-5.4.4\meson.build Wed Feb 22 18:16:56 2023
|
||||
+++ lua-5.4.4-patched\meson.build Wed Feb 22 04:10:01 2023
|
||||
diff -ruN lua-5.4.4/meson.build lua-5.4.4-patched/meson.build
|
||||
--- lua-5.4.4/meson.build Wed Feb 22 18:16:56 2023
|
||||
+++ lua-5.4.4-patched/meson.build Wed Feb 22 04:10:01 2023
|
||||
@@ -85,6 +85,7 @@
|
||||
'src/lutf8lib.c',
|
||||
'src/lvm.c',
|
||||
|
@ -9,9 +9,9 @@ diff -ruN lua-5.4.4\meson.build lua-5.4.4-patched\meson.build
|
|||
dependencies: lua_lib_deps,
|
||||
version: meson.project_version(),
|
||||
soversion: lua_versions[0] + '.' + lua_versions[1],
|
||||
diff -ruN lua-5.4.4\src\luaconf.h lua-5.4.4-patched\src\luaconf.h
|
||||
--- lua-5.4.4\src\luaconf.h Thu Jan 13 19:24:43 2022
|
||||
+++ lua-5.4.4-patched\src\luaconf.h Wed Feb 22 04:10:02 2023
|
||||
diff -ruN lua-5.4.4/src/luaconf.h lua-5.4.4-patched/src/luaconf.h
|
||||
--- lua-5.4.4/src/luaconf.h Thu Jan 13 19:24:43 2022
|
||||
+++ lua-5.4.4-patched/src/luaconf.h Wed Feb 22 04:10:02 2023
|
||||
@@ -782,5 +782,15 @@
|
||||
|
||||
|
||||
|
@ -28,9 +28,9 @@ diff -ruN lua-5.4.4\src\luaconf.h lua-5.4.4-patched\src\luaconf.h
|
|||
+
|
||||
#endif
|
||||
|
||||
diff -ruN lua-5.4.4\src\Makefile lua-5.4.4-patched\src\Makefile
|
||||
--- lua-5.4.4\src\Makefile Thu Jul 15 22:01:52 2021
|
||||
+++ lua-5.4.4-patched\src\Makefile Wed Feb 22 04:10:02 2023
|
||||
diff -ruN lua-5.4.4/src/Makefile lua-5.4.4-patched/src/Makefile
|
||||
--- lua-5.4.4/src/Makefile Thu Jul 15 22:01:52 2021
|
||||
+++ lua-5.4.4-patched/src/Makefile Wed Feb 22 04:10:02 2023
|
||||
@@ -33,7 +33,7 @@
|
||||
PLATS= guess aix bsd c89 freebsd generic linux linux-readline macosx mingw posix solaris
|
||||
|
||||
|
@ -40,9 +40,9 @@ diff -ruN lua-5.4.4\src\Makefile lua-5.4.4-patched\src\Makefile
|
|||
LIB_O= lauxlib.o lbaselib.o lcorolib.o ldblib.o liolib.o lmathlib.o loadlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o linit.o
|
||||
BASE_O= $(CORE_O) $(LIB_O) $(MYOBJS)
|
||||
|
||||
diff -ruN lua-5.4.4\src\utf8_wrappers.c lua-5.4.4-patched\src\utf8_wrappers.c
|
||||
--- lua-5.4.4\src\utf8_wrappers.c Thu Jan 01 08:00:00 1970
|
||||
+++ lua-5.4.4-patched\src\utf8_wrappers.c Wed Feb 22 18:13:45 2023
|
||||
diff -ruN lua-5.4.4/src/utf8_wrappers.c lua-5.4.4-patched/src/utf8_wrappers.c
|
||||
--- lua-5.4.4/src/utf8_wrappers.c Thu Jan 01 08:00:00 1970
|
||||
+++ lua-5.4.4-patched/src/utf8_wrappers.c Wed Feb 22 18:13:45 2023
|
||||
@@ -0,0 +1,129 @@
|
||||
+/**
|
||||
+ * Wrappers to provide Unicode (UTF-8) support on Windows.
|
||||
|
@ -173,9 +173,9 @@ diff -ruN lua-5.4.4\src\utf8_wrappers.c lua-5.4.4-patched\src\utf8_wrappers.c
|
|||
+ return env_value;
|
||||
+}
|
||||
+#endif
|
||||
diff -ruN lua-5.4.4\src\utf8_wrappers.h lua-5.4.4-patched\src\utf8_wrappers.h
|
||||
--- lua-5.4.4\src\utf8_wrappers.h Thu Jan 01 08:00:00 1970
|
||||
+++ lua-5.4.4-patched\src\utf8_wrappers.h Wed Feb 22 18:09:48 2023
|
||||
diff -ruN lua-5.4.4/src/utf8_wrappers.h lua-5.4.4-patched/src/utf8_wrappers.h
|
||||
--- lua-5.4.4/src/utf8_wrappers.h Thu Jan 01 08:00:00 1970
|
||||
+++ lua-5.4.4-patched/src/utf8_wrappers.h Wed Feb 22 18:09:48 2023
|
||||
@@ -0,0 +1,46 @@
|
||||
+/**
|
||||
+ * Wrappers to provide Unicode (UTF-8) support on Windows.
|
||||
|
|
|
@ -10,7 +10,7 @@ Various scripts and configurations used to configure, build, and package Lite XL
|
|||
|
||||
### Package
|
||||
|
||||
- **appdmg.sh**: Create a macOS DMG image using [AppDMG][1].
|
||||
- **appdmg.sh**: Create a macOS DMG image using [dmgbuild][1].
|
||||
- **appimage.sh**: [AppImage][2] builder.
|
||||
- **innosetup.sh**: Creates a 32/64 bit [InnoSetup][3] installer package.
|
||||
- **package.sh**: Creates all binary / DMG image / installer / source packages.
|
||||
|
@ -25,6 +25,6 @@ Various scripts and configurations used to configure, build, and package Lite XL
|
|||
- **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
|
||||
[1]: https://github.com/dmgbuild/dmgbuild
|
||||
[2]: https://docs.appimage.org/
|
||||
[3]: https://jrsoftware.org/isinfo.php
|
||||
|
|
|
@ -6,25 +6,4 @@ if [ ! -e "src/api/api.h" ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
cat > lite-xl-dmg.json << EOF
|
||||
{
|
||||
"title": "Lite XL",
|
||||
"icon": "$(pwd)/resources/icons/icon.icns",
|
||||
"background": "$(pwd)/resources/macos/appdmg.png",
|
||||
"window": {
|
||||
"position": {
|
||||
"x": 360,
|
||||
"y": 360
|
||||
},
|
||||
"size": {
|
||||
"width": 480,
|
||||
"height": 360
|
||||
}
|
||||
},
|
||||
"contents": [
|
||||
{ "x": 144, "y": 248, "type": "file", "path": "$(pwd)/Lite XL.app" },
|
||||
{ "x": 336, "y": 248, "type": "link", "path": "/Applications" }
|
||||
]
|
||||
}
|
||||
EOF
|
||||
~/node_modules/appdmg/bin/appdmg.js lite-xl-dmg.json "$(pwd)/$1.dmg"
|
||||
dmgbuild -s resources/macos/lite-xl-dmg.py "Lite XL" "$1.dmg"
|
||||
|
|
|
@ -181,7 +181,7 @@ main() {
|
|||
# download the subprojects so we can start patching before configure.
|
||||
# this will prevent reconfiguring the project.
|
||||
meson subprojects download
|
||||
lua_subproject_path=$(echo subprojects/lua-*/)
|
||||
lua_subproject_path="subprojects/$(awk -F ' *= *' '/directory/ { printf $2 }' subprojects/lua.wrap)"
|
||||
if [[ -d $lua_subproject_path ]]; then
|
||||
patch -d $lua_subproject_path -p1 --forward < resources/windows/001-lua-unicode.diff
|
||||
fi
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#define MyAppName "Lite XL"
|
||||
#define MyAppVersion "@PROJECT_VERSION@"
|
||||
#define MyAppPublisher "Lite XL Team"
|
||||
#define MyAppURL "https://lite-xl.github.io"
|
||||
#define MyAppURL "https://lite-xl.com"
|
||||
#define MyAppExeName "lite-xl.exe"
|
||||
#define BuildDir "@PROJECT_BUILD_DIR@"
|
||||
#define SourceDir "@PROJECT_SOURCE_DIR@"
|
||||
|
@ -57,9 +57,13 @@ OutputBaseFilename=LiteXL-{#MyAppVersion}-{#ArchInternal}-setup
|
|||
|
||||
LicenseFile={#SourceDir}/LICENSE
|
||||
SetupIconFile={#SourceDir}/resources/icons/icon.ico
|
||||
UninstallDisplayIcon={app}\{#MyAppExeName}, 0
|
||||
WizardImageFile="{#SourceDir}/scripts/innosetup/wizard-modern-image.bmp"
|
||||
WizardSmallImageFile="{#SourceDir}/scripts/innosetup/litexl-55px.bmp"
|
||||
|
||||
; Required for the add to path option to refresh environment
|
||||
ChangesEnvironment=yes
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
|
@ -67,11 +71,10 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
|||
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
|
||||
Name: "envPath"; Description: "Add lite-xl to the PATH variable, allowing it to be run from a command line."
|
||||
|
||||
[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
|
||||
Source: "{#SourceDir}/lite-xl/*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
|
@ -81,8 +84,78 @@ Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}";
|
|||
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon; Check: not WizardIsTaskSelected('portablemode')
|
||||
; Name: "{usersendto}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||
|
||||
[Registry]
|
||||
Root: "HKA"; Subkey: "Software\Classes\*\shell\{#MyAppName}"; ValueType: string; ValueName: ""; ValueData: "Open with {#MyAppName}"; Flags: uninsdeletekey
|
||||
Root: "HKA"; Subkey: "Software\Classes\*\shell\{#MyAppName}"; ValueType: string; ValueName: "Icon"; ValueData: "{app}\{#MyAppExeName}, 0"; Flags: uninsdeletekey
|
||||
Root: "HKA"; Subkey: "Software\Classes\*\shell\{#MyAppName}\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExename}"" ""%1"""; Flags: uninsdeletekey
|
||||
Root: "HKA"; Subkey: "Software\Classes\directory\shell\{#MyAppName}"; ValueType: string; ValueName: ""; ValueData: "Open with {#MyAppName}"; Flags: uninsdeletekey
|
||||
Root: "HKA"; Subkey: "Software\Classes\directory\shell\{#MyAppName}"; ValueType: string; ValueName: "Icon"; ValueData: "{app}\{#MyAppExeName}, 0"; Flags: uninsdeletekey
|
||||
Root: "HKA"; Subkey: "Software\Classes\directory\shell\{#MyAppName}\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExename}"" ""%1"""; Flags: uninsdeletekey
|
||||
Root: "HKA"; Subkey: "Software\Classes\directory\background\shell\{#MyAppName}"; ValueType: string; ValueName: ""; ValueData: "Open with {#MyAppName}"; Flags: uninsdeletekey
|
||||
Root: "HKA"; Subkey: "Software\Classes\directory\background\shell\{#MyAppName}"; ValueType: string; ValueName: "Icon"; ValueData: "{app}\{#MyAppExeName}, 0"; Flags: uninsdeletekey
|
||||
Root: "HKA"; Subkey: "Software\Classes\directory\background\shell\{#MyAppName}\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExename}"" ""%V"""; Flags: uninsdeletekey
|
||||
|
||||
[Run]
|
||||
Filename: "{app}/{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[Setup]
|
||||
Uninstallable=not WizardIsTaskSelected('portablemode')
|
||||
|
||||
; Code to add installation path to environment taken from:
|
||||
; https://stackoverflow.com/a/46609047
|
||||
[Code]
|
||||
const EnvironmentKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
|
||||
|
||||
procedure EnvAddPath(Path: string);
|
||||
var
|
||||
Paths: string;
|
||||
begin
|
||||
{ Retrieve current path (use empty string if entry not exists) }
|
||||
if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths)
|
||||
then Paths := '';
|
||||
|
||||
{ Skip if string already found in path }
|
||||
if Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';') > 0 then exit;
|
||||
|
||||
{ App string to the end of the path variable }
|
||||
Paths := Paths + ';'+ Path +';'
|
||||
|
||||
{ Overwrite (or create if missing) path environment variable }
|
||||
if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths)
|
||||
then Log(Format('The [%s] added to PATH: [%s]', [Path, Paths]))
|
||||
else Log(Format('Error while adding the [%s] to PATH: [%s]', [Path, Paths]));
|
||||
end;
|
||||
|
||||
procedure EnvRemovePath(Path: string);
|
||||
var
|
||||
Paths: string;
|
||||
P: Integer;
|
||||
begin
|
||||
{ Skip if registry entry not exists }
|
||||
if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then
|
||||
exit;
|
||||
|
||||
{ Skip if string not found in path }
|
||||
P := Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';');
|
||||
if P = 0 then exit;
|
||||
|
||||
{ Update path variable }
|
||||
Delete(Paths, P - 1, Length(Path) + 1);
|
||||
|
||||
{ Overwrite path environment variable }
|
||||
if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths)
|
||||
then Log(Format('The [%s] removed from PATH: [%s]', [Path, Paths]))
|
||||
else Log(Format('Error while removing the [%s] from PATH: [%s]', [Path, Paths]));
|
||||
end;
|
||||
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
if (CurStep = ssPostInstall) and WizardIsTaskSelected('envPath')
|
||||
then EnvAddPath(ExpandConstant('{app}'));
|
||||
end;
|
||||
|
||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||
begin
|
||||
if CurUninstallStep = usPostUninstall
|
||||
then EnvRemovePath(ExpandConstant('{app}'));
|
||||
end;
|
||||
|
|
|
@ -57,9 +57,7 @@ main() {
|
|||
else
|
||||
brew install bash ninja sdl2
|
||||
fi
|
||||
pip3 install meson
|
||||
cd ~; npm install appdmg; cd -
|
||||
~/node_modules/appdmg/bin/appdmg.js --version
|
||||
pip3 install meson dmgbuild
|
||||
elif [[ "$OSTYPE" == "msys" ]]; then
|
||||
if [[ $lhelper == true ]]; then
|
||||
pacman --noconfirm -S \
|
||||
|
|
|
@ -25,7 +25,7 @@ show_help() {
|
|||
echo "-A --appimage Create an AppImage (Linux only)."
|
||||
echo "-B --binary Create a normal / portable package or macOS bundle,"
|
||||
echo " depending on how the build was configured. (Default.)"
|
||||
echo "-D --dmg Create a DMG disk image with AppDMG (macOS only)."
|
||||
echo "-D --dmg Create a DMG disk image with dmgbuild (macOS only)."
|
||||
echo "-I --innosetup Create a InnoSetup package (Windows only)."
|
||||
echo "-r --release Strip debugging symbols."
|
||||
echo "-S --source Create a source code package,"
|
||||
|
@ -264,6 +264,11 @@ main() {
|
|||
$stripcmd "${exe_file}"
|
||||
fi
|
||||
|
||||
if [[ $bundle == true ]]; then
|
||||
# https://eclecticlight.co/2019/01/17/code-signing-for-the-concerned-3-signing-an-app/
|
||||
codesign --force --deep -s - "${dest_dir}"
|
||||
fi
|
||||
|
||||
echo "Creating a compressed archive ${package_name}"
|
||||
if [[ $binary == true ]]; then
|
||||
rm -f "${package_name}".tar.gz
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "api.h"
|
||||
#include "lua.h"
|
||||
#include <SDL.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
@ -25,13 +26,16 @@ int get_mode_dirmonitor();
|
|||
|
||||
|
||||
static int f_check_dir_callback(int watch_id, const char* path, void* L) {
|
||||
lua_pushvalue(L, -1);
|
||||
// using absolute indices from f_dirmonitor_check (2: callback, 3: error_callback)
|
||||
lua_pushvalue(L, 2);
|
||||
if (path)
|
||||
lua_pushlstring(L, path, watch_id);
|
||||
else
|
||||
lua_pushnumber(L, watch_id);
|
||||
lua_call(L, 1, 1);
|
||||
int result = lua_toboolean(L, -1);
|
||||
|
||||
int result = 0;
|
||||
if (lua_pcall(L, 1, 1, 3) == LUA_OK)
|
||||
result = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return !result;
|
||||
}
|
||||
|
@ -95,8 +99,20 @@ static int f_dirmonitor_unwatch(lua_State *L) {
|
|||
}
|
||||
|
||||
|
||||
static int f_noop(lua_State *L) { return 0; }
|
||||
|
||||
|
||||
static int f_dirmonitor_check(lua_State* L) {
|
||||
struct dirmonitor* monitor = luaL_checkudata(L, 1, API_TYPE_DIRMONITOR);
|
||||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||
if (!lua_isnoneornil(L, 3)) {
|
||||
luaL_checktype(L, 3, LUA_TFUNCTION);
|
||||
} else {
|
||||
lua_settop(L, 2);
|
||||
lua_pushcfunction(L, f_noop);
|
||||
}
|
||||
lua_settop(L, 3);
|
||||
|
||||
SDL_LockMutex(monitor->mutex);
|
||||
if (monitor->length < 0)
|
||||
lua_pushnil(L);
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
typedef DWORD process_error_t;
|
||||
typedef HANDLE process_stream_t;
|
||||
typedef HANDLE process_handle_t;
|
||||
typedef wchar_t process_arglist_t[32767];
|
||||
typedef wchar_t *process_env_t;
|
||||
|
||||
#define HANDLE_INVALID (INVALID_HANDLE_VALUE)
|
||||
#define PROCESS_GET_HANDLE(P) ((P)->process_information.hProcess)
|
||||
|
@ -45,12 +47,20 @@ static volatile long PipeSerialNumber;
|
|||
typedef int process_error_t;
|
||||
typedef int process_stream_t;
|
||||
typedef pid_t process_handle_t;
|
||||
typedef char **process_arglist_t;
|
||||
typedef char **process_env_t;
|
||||
|
||||
#define HANDLE_INVALID (0)
|
||||
#define PROCESS_GET_HANDLE(P) ((P)->pid)
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define UNUSED __attribute__((__unused__))
|
||||
#else
|
||||
#define UNUSED
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
bool running, detached;
|
||||
int returncode, deadline;
|
||||
|
@ -342,14 +352,248 @@ static bool signal_process(process_t* proc, signal_e sig) {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
static UNUSED char *xstrdup(const char *str) {
|
||||
char *result = str ? malloc(strlen(str) + 1) : NULL;
|
||||
if (result) strcpy(result, str);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static int process_arglist_init(process_arglist_t *list, size_t *list_len, size_t nargs) {
|
||||
*list_len = 0;
|
||||
#ifdef _WIN32
|
||||
memset(*list, 0, sizeof(process_arglist_t));
|
||||
#else
|
||||
*list = calloc(sizeof(char *), nargs + 1);
|
||||
if (!*list) return ENOMEM;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int process_arglist_add(process_arglist_t *list, size_t *list_len, const char *arg, bool escape) {
|
||||
size_t len = *list_len;
|
||||
#ifdef _WIN32
|
||||
int arg_len;
|
||||
wchar_t *cmdline = *list;
|
||||
wchar_t arg_w[32767];
|
||||
// this length includes the null terminator!
|
||||
if (!(arg_len = MultiByteToWideChar(CP_UTF8, 0, arg, -1, arg_w, 32767)))
|
||||
return GetLastError();
|
||||
if (arg_len + len > 32767)
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
|
||||
if (!escape) {
|
||||
// replace the current null terminator with a space
|
||||
if (len > 0) cmdline[len-1] = ' ';
|
||||
memcpy(cmdline + len, arg_w, arg_len * sizeof(wchar_t));
|
||||
len += arg_len;
|
||||
} else {
|
||||
// if the string contains spaces, then we must quote it
|
||||
bool quote = wcspbrk(arg_w, L" \t\v\r\n");
|
||||
int backslash = 0, escaped_len = quote ? 2 : 0;
|
||||
for (int i = 0; i < arg_len; i++) {
|
||||
if (arg_w[i] == L'\\') {
|
||||
backslash++;
|
||||
} else if (arg_w[i] == L'"') {
|
||||
escaped_len += backslash + 1;
|
||||
backslash = 0;
|
||||
} else {
|
||||
backslash = 0;
|
||||
}
|
||||
escaped_len++;
|
||||
}
|
||||
// escape_len contains NUL terminator
|
||||
if (escaped_len + len > 32767)
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
// replace our previous NUL terminator with space
|
||||
if (len > 0) cmdline[len-1] = L' ';
|
||||
if (quote) cmdline[len++] = L'"';
|
||||
// we are not going to iterate over NUL terminator
|
||||
for (int i = 0;arg_w[i]; i++) {
|
||||
if (arg_w[i] == L'\\') {
|
||||
backslash++;
|
||||
} else if (arg_w[i] == L'"') {
|
||||
// add backslash + 1 backslashes
|
||||
for (int j = 0; j < backslash; j++)
|
||||
cmdline[len++] = L'\\';
|
||||
cmdline[len++] = L'\\';
|
||||
backslash = 0;
|
||||
} else {
|
||||
backslash = 0;
|
||||
}
|
||||
cmdline[len++] = arg_w[i];
|
||||
}
|
||||
if (quote) cmdline[len++] = L'"';
|
||||
cmdline[len++] = L'\0';
|
||||
}
|
||||
#else
|
||||
char **cmd = *list;
|
||||
cmd[len] = xstrdup(arg);
|
||||
if (!cmd[len]) return ENOMEM;
|
||||
len++;
|
||||
#endif
|
||||
*list_len = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void process_arglist_free(process_arglist_t *list) {
|
||||
#ifndef _WIN32
|
||||
char **cmd = *list;
|
||||
for (int i = 0; cmd[i]; i++)
|
||||
free(cmd[i]);
|
||||
free(cmd);
|
||||
*list = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static int process_env_init(process_env_t *env_list, size_t *env_len, size_t nenv) {
|
||||
*env_len = 0;
|
||||
#ifdef _WIN32
|
||||
*env_list = NULL;
|
||||
#else
|
||||
*env_list = calloc(sizeof(char *), nenv * 2);
|
||||
if (!*env_list) return ENOMEM;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
static int cmp_name(wchar_t *a, wchar_t *b) {
|
||||
wchar_t _A[32767], _B[32767], *A = _A, *B = _B, *a_eq, *b_eq;
|
||||
int na, nb, r;
|
||||
a_eq = wcschr(a, L'=');
|
||||
b_eq = wcschr(b, L'=');
|
||||
assert(a_eq);
|
||||
assert(b_eq);
|
||||
na = a_eq - a;
|
||||
nb = b_eq - b;
|
||||
r = LCMapStringW(LOCALE_INVARIANT, LCMAP_UPPERCASE, a, na, A, na);
|
||||
assert(r == na);
|
||||
A[na] = L'\0';
|
||||
r = LCMapStringW(LOCALE_INVARIANT, LCMAP_UPPERCASE, b, nb, B, nb);
|
||||
assert(r == nb);
|
||||
B[nb] = L'\0';
|
||||
|
||||
for (;;) {
|
||||
wchar_t AA = *A++, BB = *B++;
|
||||
if (AA > BB)
|
||||
return 1;
|
||||
else if (AA < BB)
|
||||
return -1;
|
||||
else if (!AA && !BB)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int process_env_add_variable(process_env_t *env_list, size_t *env_list_len, wchar_t *var, size_t var_len) {
|
||||
wchar_t *list, *list_p;
|
||||
size_t block_var_len, list_len;
|
||||
list = list_p = *env_list;
|
||||
list_len = *env_list_len;
|
||||
if (list_len) {
|
||||
// check if it is already in the block
|
||||
while ((block_var_len = wcslen(list_p))) {
|
||||
if (cmp_name(list_p, var) == 0)
|
||||
return -1; // already installed
|
||||
list_p += block_var_len + 1;
|
||||
}
|
||||
}
|
||||
// allocate list + 1 characters for the block terminator
|
||||
list = realloc(list, (list_len + var_len + 1) * sizeof(wchar_t));
|
||||
if (!list) return ERROR_NOT_ENOUGH_MEMORY;
|
||||
// copy the env variable to the block
|
||||
memcpy(list + list_len, var, var_len * sizeof(wchar_t));
|
||||
// terminate the block again
|
||||
list[list_len + var_len] = L'\0';
|
||||
*env_list = list;
|
||||
*env_list_len = (list_len + var_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int process_env_add_system(process_env_t *env_list, size_t *env_list_len) {
|
||||
int retval = 0;
|
||||
wchar_t *proc_env_block, *proc_env_block_p;
|
||||
int proc_env_len;
|
||||
|
||||
proc_env_block = proc_env_block_p = GetEnvironmentStringsW();
|
||||
while ((proc_env_len = wcslen(proc_env_block_p))) {
|
||||
// try to add it to the list
|
||||
if ((retval = process_env_add_variable(env_list, env_list_len, proc_env_block_p, proc_env_len + 1)) > 0)
|
||||
goto cleanup;
|
||||
proc_env_block_p += proc_env_len + 1;
|
||||
}
|
||||
retval = 0;
|
||||
|
||||
cleanup:
|
||||
if (proc_env_block) FreeEnvironmentStringsW(proc_env_block);
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static int process_env_add(process_env_t *env_list, size_t *env_len, const char *key, const char *value) {
|
||||
#ifdef _WIN32
|
||||
wchar_t env_var[32767];
|
||||
int r, var_len = 0;
|
||||
if (!(r = MultiByteToWideChar(CP_UTF8, 0, key, -1, env_var, 32767)))
|
||||
return GetLastError();
|
||||
var_len += r;
|
||||
env_var[var_len-1] = L'=';
|
||||
if (!(r = MultiByteToWideChar(CP_UTF8, 0, value, -1, env_var + var_len, 32767 - var_len)))
|
||||
return GetLastError();
|
||||
var_len += r;
|
||||
return process_env_add_variable(env_list, env_len, env_var, var_len);
|
||||
#else
|
||||
(*env_list)[*env_len] = xstrdup(key);
|
||||
if (!(*env_list)[*env_len])
|
||||
return ENOMEM;
|
||||
(*env_list)[*env_len + 1] = xstrdup(value);
|
||||
if (!(*env_list)[*env_len + 1])
|
||||
return ENOMEM;
|
||||
*env_len += 2;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void process_env_free(process_env_t *list) {
|
||||
if (!*list) return;
|
||||
#ifdef _WIN32
|
||||
free(*list);
|
||||
#else
|
||||
for (size_t i = 0; (*list)[i]; i++) free((*list)[i]);
|
||||
free(*list);
|
||||
#endif
|
||||
*list = NULL;
|
||||
}
|
||||
|
||||
|
||||
static int process_start(lua_State* L) {
|
||||
int retval = 1;
|
||||
size_t env_len = 0, key_len, val_len;
|
||||
const char *cmd[256] = { NULL }, *env_names[256] = { NULL }, *env_values[256] = { NULL }, *cwd = NULL;
|
||||
bool detach = false, literal = false;
|
||||
int r, retval = 1;
|
||||
size_t env_len = 0, cmd_len = 0, arglist_len = 0, env_vars_len = 0;
|
||||
process_arglist_t arglist;
|
||||
process_env_t env_vars = NULL;
|
||||
const char *cwd = NULL;
|
||||
bool detach = false, escape = true;
|
||||
int deadline = 10, new_fds[3] = { STDIN_FD, STDOUT_FD, STDERR_FD };
|
||||
size_t arg_len = lua_gettop(L), cmd_len;
|
||||
if (lua_type(L, 1) == LUA_TTABLE) {
|
||||
|
||||
if (lua_isstring(L, 1)) {
|
||||
escape = false;
|
||||
// create a table that contains the string as the value
|
||||
lua_createtable(L, 1, 0);
|
||||
lua_pushvalue(L, 1);
|
||||
lua_rawseti(L, -2, 1);
|
||||
lua_replace(L, 1);
|
||||
}
|
||||
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
#if LUA_VERSION_NUM > 501
|
||||
lua_len(L, 1);
|
||||
#else
|
||||
|
@ -357,36 +601,15 @@ static int process_start(lua_State* L) {
|
|||
#endif
|
||||
cmd_len = luaL_checknumber(L, -1); lua_pop(L, 1);
|
||||
if (!cmd_len)
|
||||
// we have not allocated anything here yet, so we can skip cleanup code
|
||||
// don't do this anywhere else!
|
||||
return luaL_argerror(L, 1, "table cannot be empty");
|
||||
// check if each arguments is a string
|
||||
for (size_t i = 1; i <= cmd_len; ++i) {
|
||||
lua_pushinteger(L, i);
|
||||
lua_rawget(L, 1);
|
||||
cmd[i-1] = luaL_checkstring(L, -1);
|
||||
}
|
||||
} else {
|
||||
literal = true;
|
||||
cmd[0] = luaL_checkstring(L, 1);
|
||||
cmd_len = 1;
|
||||
lua_rawgeti(L, 1, i);
|
||||
luaL_checkstring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
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_names[env_len] = malloc(key_len+1);
|
||||
strcpy((char*)env_names[env_len], key);
|
||||
env_values[env_len] = malloc(val_len+1);
|
||||
strcpy((char*)env_values[env_len], val);
|
||||
lua_pop(L, 1);
|
||||
++env_len;
|
||||
}
|
||||
} else
|
||||
lua_pop(L, 1);
|
||||
if (lua_istable(L, 2)) {
|
||||
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);
|
||||
|
@ -394,12 +617,55 @@ static int process_start(lua_State* L) {
|
|||
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) {
|
||||
lua_pushfstring(L, "error: redirect to handles, FILE* and paths are not supported");
|
||||
if (new_fds[stream] > STDERR_FD || new_fds[stream] < REDIRECT_PARENT)
|
||||
return luaL_error(L, "error: redirect to handles, FILE* and paths are not supported");
|
||||
}
|
||||
lua_pop(L, 6); // pop all the values above
|
||||
|
||||
luaL_getsubtable(L, 2, "env");
|
||||
// count environment variobles
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2) != 0) {
|
||||
luaL_checkstring(L, -2);
|
||||
luaL_checkstring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
env_len++;
|
||||
}
|
||||
|
||||
if (env_len) {
|
||||
if ((r = process_env_init(&env_vars, &env_vars_len, env_len)) != 0) {
|
||||
retval = -1;
|
||||
push_error(L, "cannot allocate environment list", r);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2) != 0) {
|
||||
if ((r = process_env_add(&env_vars, &env_vars_len, lua_tostring(L, -2), lua_tostring(L, -1))) != 0) {
|
||||
retval = -1;
|
||||
push_error(L, "cannot copy environment variable", r);
|
||||
goto cleanup;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
env_len++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allocate and copy commands
|
||||
if ((r = process_arglist_init(&arglist, &arglist_len, cmd_len)) != 0) {
|
||||
retval = -1;
|
||||
push_error(L, "cannot create argument list", r);
|
||||
goto cleanup;
|
||||
}
|
||||
for (size_t i = 1; i <= cmd_len; i++) {
|
||||
lua_rawgeti(L, 1, i);
|
||||
if ((r = process_arglist_add(&arglist, &arglist_len, lua_tostring(L, -1), escape)) != 0) {
|
||||
retval = -1;
|
||||
push_error(L, "cannot add argument", r);
|
||||
goto cleanup;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
process_t* self = lua_newuserdata(L, sizeof(process_t));
|
||||
|
@ -408,6 +674,13 @@ static int process_start(lua_State* L) {
|
|||
self->deadline = deadline;
|
||||
self->detached = detach;
|
||||
#if _WIN32
|
||||
if (env_vars) {
|
||||
if ((r = process_env_add_system(&env_vars, &env_vars_len)) != 0) {
|
||||
retval = -1;
|
||||
push_error(L, "cannot add environment variable", r);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
switch (new_fds[i]) {
|
||||
case REDIRECT_PARENT:
|
||||
|
@ -458,7 +731,7 @@ static int process_start(lua_State* L) {
|
|||
self->child_pipes[i][1] = self->child_pipes[new_fds[i]][1];
|
||||
}
|
||||
}
|
||||
STARTUPINFO siStartInfo;
|
||||
STARTUPINFOW siStartInfo;
|
||||
memset(&self->process_information, 0, sizeof(self->process_information));
|
||||
memset(&siStartInfo, 0, sizeof(siStartInfo));
|
||||
siStartInfo.cb = sizeof(siStartInfo);
|
||||
|
@ -466,48 +739,10 @@ static int process_start(lua_State* L) {
|
|||
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], wideEnvironmentBlock[32767*2];
|
||||
int offset = 0;
|
||||
if (!literal) {
|
||||
for (size_t i = 0; i < cmd_len; ++i) {
|
||||
size_t len = strlen(cmd[i]);
|
||||
if (offset + len + 2 >= sizeof(commandLine)) break;
|
||||
if (i > 0)
|
||||
commandLine[offset++] = ' ';
|
||||
commandLine[offset++] = '"';
|
||||
int backslashCount = 0; // Yes, this is necessary.
|
||||
for (size_t j = 0; j < len && offset + 2 + backslashCount < sizeof(commandLine); ++j) {
|
||||
if (cmd[i][j] == '\\')
|
||||
++backslashCount;
|
||||
else if (cmd[i][j] == '"') {
|
||||
for (size_t k = 0; k < backslashCount; ++k)
|
||||
commandLine[offset++] = '\\';
|
||||
commandLine[offset++] = '\\';
|
||||
backslashCount = 0;
|
||||
} else
|
||||
backslashCount = 0;
|
||||
commandLine[offset++] = cmd[i][j];
|
||||
}
|
||||
if (offset + 1 + backslashCount >= sizeof(commandLine)) break;
|
||||
for (size_t k = 0; k < backslashCount; ++k)
|
||||
commandLine[offset++] = '\\';
|
||||
commandLine[offset++] = '"';
|
||||
}
|
||||
commandLine[offset] = 0;
|
||||
} else {
|
||||
strncpy(commandLine, cmd[0], sizeof(commandLine));
|
||||
}
|
||||
offset = 0;
|
||||
for (size_t i = 0; i < env_len; ++i) {
|
||||
if (offset + strlen(env_values[i]) + strlen(env_names[i]) + 1 >= sizeof(environmentBlock))
|
||||
break;
|
||||
offset += snprintf(&environmentBlock[offset], sizeof(environmentBlock) - offset, "%s=%s", env_names[i], env_values[i]);
|
||||
environmentBlock[offset++] = 0;
|
||||
}
|
||||
environmentBlock[offset++] = 0;
|
||||
if (env_len > 0)
|
||||
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, environmentBlock, offset, (LPWSTR)wideEnvironmentBlock, sizeof(wideEnvironmentBlock));
|
||||
if (!CreateProcess(NULL, commandLine, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_len > 0 ? wideEnvironmentBlock : NULL, cwd, &siStartInfo, &self->process_information)) {
|
||||
wchar_t cwd_w[MAX_PATH];
|
||||
if (cwd) // TODO: error handling
|
||||
MultiByteToWideChar(CP_UTF8, 0, cwd, -1, cwd_w, MAX_PATH);
|
||||
if (!CreateProcessW(NULL, arglist, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_vars, cwd ? cwd_w : NULL, &siStartInfo, &self->process_information)) {
|
||||
push_error(L, NULL, GetLastError());
|
||||
retval = -1;
|
||||
goto cleanup;
|
||||
|
@ -555,9 +790,9 @@ static int process_start(lua_State* L) {
|
|||
close(self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]);
|
||||
}
|
||||
size_t set;
|
||||
for (set = 0; set < env_len && setenv(env_names[set], env_values[set], 1) == 0; ++set);
|
||||
if (set == env_len && (!detach || setsid() != -1) && (!cwd || chdir(cwd) != -1))
|
||||
execvp(cmd[0], (char** const)cmd);
|
||||
for (set = 0; set < env_vars_len && setenv(env_vars[set], env_vars[set+1], 1) == 0; set += 2);
|
||||
if (set == env_vars_len && (!detach || setsid() != -1) && (!cwd || chdir(cwd) != -1))
|
||||
execvp(arglist[0], (char** const)arglist);
|
||||
write(control_pipe[1], &errno, sizeof(errno));
|
||||
_exit(-1);
|
||||
}
|
||||
|
@ -591,16 +826,15 @@ static int process_start(lua_State* L) {
|
|||
if (control_pipe[0]) close(control_pipe[0]);
|
||||
if (control_pipe[1]) close(control_pipe[1]);
|
||||
#endif
|
||||
for (size_t i = 0; i < env_len; ++i) {
|
||||
free((char*)env_names[i]);
|
||||
free((char*)env_values[i]);
|
||||
}
|
||||
for (int stream = 0; stream < 3; ++stream) {
|
||||
process_stream_t* pipe = &self->child_pipes[stream][stream == STDIN_FD ? 0 : 1];
|
||||
if (*pipe) {
|
||||
close_fd(pipe);
|
||||
}
|
||||
}
|
||||
process_arglist_free(&arglist);
|
||||
process_env_free(&env_vars);
|
||||
|
||||
if (retval == -1)
|
||||
return lua_error(L);
|
||||
|
||||
|
@ -741,6 +975,10 @@ static int self_signal(lua_State* L, signal_e sig) {
|
|||
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); }
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
>>>>>>> master
|
||||
static int f_gc(lua_State* L) {
|
||||
process_kill_list_t *list = NULL;
|
||||
process_kill_t *p = NULL;
|
||||
|
|
|
@ -90,7 +90,7 @@ static int f_font_load(lua_State *L) {
|
|||
return ret_code;
|
||||
|
||||
RenFont** font = lua_newuserdata(L, sizeof(RenFont*));
|
||||
*font = ren_font_load(&window_renderer, filename, size, antialiasing, hinting, style);
|
||||
*font = ren_font_load(window_renderer, filename, size, antialiasing, hinting, style);
|
||||
if (!*font)
|
||||
return luaL_error(L, "failed to load font");
|
||||
luaL_setmetatable(L, API_TYPE_FONT);
|
||||
|
@ -130,7 +130,7 @@ static int f_font_copy(lua_State *L) {
|
|||
}
|
||||
for (int i = 0; i < FONT_FALLBACK_MAX && fonts[i]; ++i) {
|
||||
RenFont** font = lua_newuserdata(L, sizeof(RenFont*));
|
||||
*font = ren_font_copy(&window_renderer, fonts[i], size, antialiasing, hinting, style);
|
||||
*font = ren_font_copy(window_renderer, fonts[i], size, antialiasing, hinting, style);
|
||||
if (!*font)
|
||||
return luaL_error(L, "failed to copy font");
|
||||
luaL_setmetatable(L, API_TYPE_FONT);
|
||||
|
@ -198,7 +198,7 @@ static int f_font_get_width(lua_State *L) {
|
|||
size_t len;
|
||||
const char *text = luaL_checklstring(L, 2, &len);
|
||||
|
||||
lua_pushnumber(L, ren_font_group_get_width(&window_renderer, fonts, text, len));
|
||||
lua_pushnumber(L, ren_font_group_get_width(window_renderer, fonts, text, len, NULL));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -217,7 +217,7 @@ static int f_font_get_size(lua_State *L) {
|
|||
static int f_font_set_size(lua_State *L) {
|
||||
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
|
||||
float size = luaL_checknumber(L, 2);
|
||||
ren_font_group_set_size(&window_renderer, fonts, size);
|
||||
ren_font_group_set_size(window_renderer, fonts, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -276,7 +276,7 @@ static int f_show_debug(lua_State *L) {
|
|||
|
||||
static int f_get_size(lua_State *L) {
|
||||
int w, h;
|
||||
ren_get_size(&window_renderer, &w, &h);
|
||||
ren_get_size(window_renderer, &w, &h);
|
||||
lua_pushnumber(L, w);
|
||||
lua_pushnumber(L, h);
|
||||
return 2;
|
||||
|
@ -284,13 +284,13 @@ static int f_get_size(lua_State *L) {
|
|||
|
||||
|
||||
static int f_begin_frame(UNUSED lua_State *L) {
|
||||
rencache_begin_frame(&window_renderer);
|
||||
rencache_begin_frame(window_renderer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int f_end_frame(UNUSED lua_State *L) {
|
||||
rencache_end_frame(&window_renderer);
|
||||
rencache_end_frame(window_renderer);
|
||||
// clear the font reference table
|
||||
lua_newtable(L);
|
||||
lua_rawseti(L, LUA_REGISTRYINDEX, RENDERER_FONT_REF);
|
||||
|
@ -311,7 +311,7 @@ static int f_set_clip_rect(lua_State *L) {
|
|||
lua_Number w = luaL_checknumber(L, 3);
|
||||
lua_Number h = luaL_checknumber(L, 4);
|
||||
RenRect rect = rect_to_grid(x, y, w, h);
|
||||
rencache_set_clip_rect(&window_renderer, rect);
|
||||
rencache_set_clip_rect(window_renderer, rect);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -323,7 +323,7 @@ static int f_draw_rect(lua_State *L) {
|
|||
lua_Number h = luaL_checknumber(L, 4);
|
||||
RenRect rect = rect_to_grid(x, y, w, h);
|
||||
RenColor color = checkcolor(L, 5, 255);
|
||||
rencache_draw_rect(&window_renderer, rect, color);
|
||||
rencache_draw_rect(window_renderer, rect, color);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -348,7 +348,7 @@ static int f_draw_text(lua_State *L) {
|
|||
double x = luaL_checknumber(L, 3);
|
||||
int y = luaL_checknumber(L, 4);
|
||||
RenColor color = checkcolor(L, 5, 255);
|
||||
x = rencache_draw_text(&window_renderer, fonts, text, len, x, y, color);
|
||||
x = rencache_draw_text(window_renderer, fonts, text, len, x, y, color);
|
||||
lua_pushnumber(L, x);
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ static SDL_HitTestResult SDLCALL hit_test(SDL_Window *window, const SDL_Point *p
|
|||
const int controls_width = hit_info->controls_width;
|
||||
int w, h;
|
||||
|
||||
SDL_GetWindowSize(window_renderer.window, &w, &h);
|
||||
SDL_GetWindowSize(window_renderer->window, &w, &h);
|
||||
|
||||
if (pt->y < hit_info->title_height &&
|
||||
#if RESIZE_FROM_TOP
|
||||
|
@ -197,7 +197,7 @@ top:
|
|||
|
||||
case SDL_WINDOWEVENT:
|
||||
if (e.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
ren_resize_window(&window_renderer);
|
||||
ren_resize_window(window_renderer);
|
||||
lua_pushstring(L, "resized");
|
||||
/* The size below will be in points. */
|
||||
lua_pushinteger(L, e.window.data1);
|
||||
|
@ -236,8 +236,8 @@ top:
|
|||
SDL_GetMouseState(&mx, &my);
|
||||
lua_pushstring(L, "filedropped");
|
||||
lua_pushstring(L, e.drop.file);
|
||||
lua_pushinteger(L, mx);
|
||||
lua_pushinteger(L, my);
|
||||
lua_pushinteger(L, mx * window_renderer->scale_x);
|
||||
lua_pushinteger(L, my * window_renderer->scale_y);
|
||||
SDL_free(e.drop.file);
|
||||
return 4;
|
||||
|
||||
|
@ -294,8 +294,8 @@ top:
|
|||
if (e.button.button == 1) { SDL_CaptureMouse(1); }
|
||||
lua_pushstring(L, "mousepressed");
|
||||
lua_pushstring(L, button_name(e.button.button));
|
||||
lua_pushinteger(L, e.button.x);
|
||||
lua_pushinteger(L, e.button.y);
|
||||
lua_pushinteger(L, e.button.x * window_renderer->scale_x);
|
||||
lua_pushinteger(L, e.button.y * window_renderer->scale_y);
|
||||
lua_pushinteger(L, e.button.clicks);
|
||||
return 5;
|
||||
|
||||
|
@ -303,8 +303,8 @@ top:
|
|||
if (e.button.button == 1) { SDL_CaptureMouse(0); }
|
||||
lua_pushstring(L, "mousereleased");
|
||||
lua_pushstring(L, button_name(e.button.button));
|
||||
lua_pushinteger(L, e.button.x);
|
||||
lua_pushinteger(L, e.button.y);
|
||||
lua_pushinteger(L, e.button.x * window_renderer->scale_x);
|
||||
lua_pushinteger(L, e.button.y * window_renderer->scale_y);
|
||||
return 4;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
|
@ -316,10 +316,10 @@ top:
|
|||
e.motion.yrel += event_plus.motion.yrel;
|
||||
}
|
||||
lua_pushstring(L, "mousemoved");
|
||||
lua_pushinteger(L, e.motion.x);
|
||||
lua_pushinteger(L, e.motion.y);
|
||||
lua_pushinteger(L, e.motion.xrel);
|
||||
lua_pushinteger(L, e.motion.yrel);
|
||||
lua_pushinteger(L, e.motion.x * window_renderer->scale_x);
|
||||
lua_pushinteger(L, e.motion.y * window_renderer->scale_y);
|
||||
lua_pushinteger(L, e.motion.xrel * window_renderer->scale_x);
|
||||
lua_pushinteger(L, e.motion.yrel * window_renderer->scale_y);
|
||||
return 5;
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
|
@ -335,7 +335,7 @@ top:
|
|||
return 3;
|
||||
|
||||
case SDL_FINGERDOWN:
|
||||
SDL_GetWindowSize(window_renderer.window, &w, &h);
|
||||
SDL_GetWindowSize(window_renderer->window, &w, &h);
|
||||
|
||||
lua_pushstring(L, "touchpressed");
|
||||
lua_pushinteger(L, (lua_Integer)(e.tfinger.x * w));
|
||||
|
@ -344,7 +344,7 @@ top:
|
|||
return 4;
|
||||
|
||||
case SDL_FINGERUP:
|
||||
SDL_GetWindowSize(window_renderer.window, &w, &h);
|
||||
SDL_GetWindowSize(window_renderer->window, &w, &h);
|
||||
|
||||
lua_pushstring(L, "touchreleased");
|
||||
lua_pushinteger(L, (lua_Integer)(e.tfinger.x * w));
|
||||
|
@ -360,7 +360,7 @@ top:
|
|||
e.tfinger.dx += event_plus.tfinger.dx;
|
||||
e.tfinger.dy += event_plus.tfinger.dy;
|
||||
}
|
||||
SDL_GetWindowSize(window_renderer.window, &w, &h);
|
||||
SDL_GetWindowSize(window_renderer->window, &w, &h);
|
||||
|
||||
lua_pushstring(L, "touchmoved");
|
||||
lua_pushinteger(L, (lua_Integer)(e.tfinger.x * w));
|
||||
|
@ -374,7 +374,7 @@ top:
|
|||
#ifdef LITE_USE_SDL_RENDERER
|
||||
rencache_invalidate();
|
||||
#else
|
||||
SDL_UpdateWindowSurface(window_renderer.window);
|
||||
SDL_UpdateWindowSurface(window_renderer->window);
|
||||
#endif
|
||||
lua_pushstring(L, e.type == SDL_APP_WILLENTERFOREGROUND ? "enteringforeground" : "enteredforeground");
|
||||
return 1;
|
||||
|
@ -397,6 +397,7 @@ static int f_wait_event(lua_State *L) {
|
|||
int nargs = lua_gettop(L);
|
||||
if (nargs >= 1) {
|
||||
double n = luaL_checknumber(L, 1);
|
||||
if (n < 0) n = 0;
|
||||
lua_pushboolean(L, SDL_WaitEventTimeout(NULL, n * 1000));
|
||||
} else {
|
||||
lua_pushboolean(L, SDL_WaitEvent(NULL));
|
||||
|
@ -439,7 +440,7 @@ static int f_set_cursor(lua_State *L) {
|
|||
|
||||
static int f_set_window_title(lua_State *L) {
|
||||
const char *title = luaL_checkstring(L, 1);
|
||||
SDL_SetWindowTitle(window_renderer.window, title);
|
||||
SDL_SetWindowTitle(window_renderer->window, title);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -449,39 +450,39 @@ enum { WIN_NORMAL, WIN_MINIMIZED, WIN_MAXIMIZED, WIN_FULLSCREEN };
|
|||
|
||||
static int f_set_window_mode(lua_State *L) {
|
||||
int n = luaL_checkoption(L, 1, "normal", window_opts);
|
||||
SDL_SetWindowFullscreen(window_renderer.window,
|
||||
SDL_SetWindowFullscreen(window_renderer->window,
|
||||
n == WIN_FULLSCREEN ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
if (n == WIN_NORMAL) { SDL_RestoreWindow(window_renderer.window); }
|
||||
if (n == WIN_MAXIMIZED) { SDL_MaximizeWindow(window_renderer.window); }
|
||||
if (n == WIN_MINIMIZED) { SDL_MinimizeWindow(window_renderer.window); }
|
||||
if (n == WIN_NORMAL) { SDL_RestoreWindow(window_renderer->window); }
|
||||
if (n == WIN_MAXIMIZED) { SDL_MaximizeWindow(window_renderer->window); }
|
||||
if (n == WIN_MINIMIZED) { SDL_MinimizeWindow(window_renderer->window); }
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int f_set_window_bordered(lua_State *L) {
|
||||
int bordered = lua_toboolean(L, 1);
|
||||
SDL_SetWindowBordered(window_renderer.window, bordered);
|
||||
SDL_SetWindowBordered(window_renderer->window, bordered);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int f_set_window_hit_test(lua_State *L) {
|
||||
if (lua_gettop(L) == 0) {
|
||||
SDL_SetWindowHitTest(window_renderer.window, NULL, NULL);
|
||||
SDL_SetWindowHitTest(window_renderer->window, NULL, NULL);
|
||||
return 0;
|
||||
}
|
||||
window_hit_info->title_height = luaL_checknumber(L, 1);
|
||||
window_hit_info->controls_width = luaL_checknumber(L, 2);
|
||||
window_hit_info->resize_border = luaL_checknumber(L, 3);
|
||||
SDL_SetWindowHitTest(window_renderer.window, hit_test, window_hit_info);
|
||||
SDL_SetWindowHitTest(window_renderer->window, hit_test, window_hit_info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int f_get_window_size(lua_State *L) {
|
||||
int x, y, w, h;
|
||||
SDL_GetWindowSize(window_renderer.window, &w, &h);
|
||||
SDL_GetWindowPosition(window_renderer.window, &x, &y);
|
||||
SDL_GetWindowSize(window_renderer->window, &w, &h);
|
||||
SDL_GetWindowPosition(window_renderer->window, &x, &y);
|
||||
lua_pushinteger(L, w);
|
||||
lua_pushinteger(L, h);
|
||||
lua_pushinteger(L, x);
|
||||
|
@ -495,22 +496,22 @@ static int f_set_window_size(lua_State *L) {
|
|||
double h = luaL_checknumber(L, 2);
|
||||
double x = luaL_checknumber(L, 3);
|
||||
double y = luaL_checknumber(L, 4);
|
||||
SDL_SetWindowSize(window_renderer.window, w, h);
|
||||
SDL_SetWindowPosition(window_renderer.window, x, y);
|
||||
ren_resize_window(&window_renderer);
|
||||
SDL_SetWindowSize(window_renderer->window, w, h);
|
||||
SDL_SetWindowPosition(window_renderer->window, x, y);
|
||||
ren_resize_window(window_renderer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int f_window_has_focus(lua_State *L) {
|
||||
unsigned flags = SDL_GetWindowFlags(window_renderer.window);
|
||||
unsigned flags = SDL_GetWindowFlags(window_renderer->window);
|
||||
lua_pushboolean(L, flags & SDL_WINDOW_INPUT_FOCUS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int f_get_window_mode(lua_State *L) {
|
||||
unsigned flags = SDL_GetWindowFlags(window_renderer.window);
|
||||
unsigned flags = SDL_GetWindowFlags(window_renderer->window);
|
||||
if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) {
|
||||
lua_pushstring(L, "fullscreen");
|
||||
} else if (flags & SDL_WINDOW_MINIMIZED) {
|
||||
|
@ -548,8 +549,8 @@ static int f_raise_window(lua_State *L) {
|
|||
to allow the window to be focused. Also on wayland the raise window event
|
||||
may not always be obeyed.
|
||||
*/
|
||||
SDL_SetWindowInputFocus(window_renderer.window);
|
||||
SDL_RaiseWindow(window_renderer.window);
|
||||
SDL_SetWindowInputFocus(window_renderer->window);
|
||||
SDL_RaiseWindow(window_renderer->window);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -876,6 +877,7 @@ static int f_get_time(lua_State *L) {
|
|||
|
||||
static int f_sleep(lua_State *L) {
|
||||
double n = luaL_checknumber(L, 1);
|
||||
if (n < 0) n = 0;
|
||||
SDL_Delay(n * 1000);
|
||||
return 0;
|
||||
}
|
||||
|
@ -929,7 +931,7 @@ static int f_fuzzy_match(lua_State *L) {
|
|||
|
||||
static int f_set_window_opacity(lua_State *L) {
|
||||
double n = luaL_checknumber(L, 1);
|
||||
int r = SDL_SetWindowOpacity(window_renderer.window, n);
|
||||
int r = SDL_SetWindowOpacity(window_renderer->window, n);
|
||||
lua_pushboolean(L, r > -1);
|
||||
return 1;
|
||||
}
|
||||
|
@ -1071,7 +1073,7 @@ static int f_load_native_plugin(lua_State *L) {
|
|||
#endif
|
||||
|
||||
/* Special purpose filepath compare function. Corresponds to the
|
||||
order used in the TreeView view of the project's files. Returns true iff
|
||||
order used in the TreeView view of the project's files. Returns true if
|
||||
path1 < path2 in the TreeView order. */
|
||||
static int f_path_compare(lua_State *L) {
|
||||
size_t len1, len2;
|
||||
|
|
19
src/main.c
19
src/main.c
|
@ -35,16 +35,6 @@
|
|||
|
||||
static SDL_Window *window;
|
||||
|
||||
static double get_scale(void) {
|
||||
#ifndef __APPLE__
|
||||
float dpi;
|
||||
if (SDL_GetDisplayDPI(0, NULL, &dpi, NULL) == 0)
|
||||
return dpi / 96.0;
|
||||
#endif
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
|
||||
static void get_exe_filename(char *buf, int sz) {
|
||||
#if _WIN32
|
||||
int len;
|
||||
|
@ -204,6 +194,8 @@ int main(int argc, char **argv) {
|
|||
SDL_SetHint("SDL_MOUSE_DOUBLE_CLICK_RADIUS", "4");
|
||||
#endif
|
||||
|
||||
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
|
||||
|
||||
SDL_DisplayMode dm;
|
||||
SDL_GetCurrentDisplayMode(0, &dm);
|
||||
|
||||
|
@ -217,7 +209,7 @@ int main(int argc, char **argv) {
|
|||
fprintf(stderr, "Error creating lite-xl window: %s", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
ren_init(window);
|
||||
window_renderer = ren_init(window);
|
||||
|
||||
lua_State *L;
|
||||
init_lua:
|
||||
|
@ -239,9 +231,6 @@ init_lua:
|
|||
lua_pushstring(L, LITE_ARCH_TUPLE);
|
||||
lua_setglobal(L, "ARCH");
|
||||
|
||||
lua_pushnumber(L, get_scale());
|
||||
lua_setglobal(L, "SCALE");
|
||||
|
||||
char exename[2048];
|
||||
get_exe_filename(exename, sizeof(exename));
|
||||
if (*exename) {
|
||||
|
@ -314,7 +303,7 @@ init_lua:
|
|||
|
||||
// This allows the window to be destroyed before lite-xl is done with
|
||||
// reaping child processes
|
||||
ren_free_window_resources(&window_renderer);
|
||||
ren_free(window_renderer);
|
||||
lua_close(L);
|
||||
|
||||
#if defined(__amigaos4__)
|
||||
|
|
|
@ -191,8 +191,9 @@ void rencache_draw_rect(RenWindow *window_renderer, RenRect rect, RenColor color
|
|||
|
||||
double rencache_draw_text(RenWindow *window_renderer, RenFont **fonts, const char *text, size_t len, double x, int y, RenColor color)
|
||||
{
|
||||
double width = ren_font_group_get_width(window_renderer, fonts, text, len);
|
||||
RenRect rect = { x, y, (int)width, ren_font_group_get_height(fonts) };
|
||||
int x_offset;
|
||||
double width = ren_font_group_get_width(window_renderer, fonts, text, len, &x_offset);
|
||||
RenRect rect = { x + x_offset, y, (int)(width - x_offset), ren_font_group_get_height(fonts) };
|
||||
if (rects_overlap(last_clip_rect, rect)) {
|
||||
int sz = len + 1;
|
||||
DrawTextCommand *cmd = push_command(window_renderer, DRAW_TEXT, sizeof(DrawTextCommand) + sz);
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#define MAX_LOADABLE_GLYPHSETS (MAX_UNICODE / GLYPHSET_SIZE)
|
||||
#define SUBPIXEL_BITMAPS_CACHED 3
|
||||
|
||||
RenWindow window_renderer = {0};
|
||||
RenWindow* window_renderer = NULL;
|
||||
static FT_Library library;
|
||||
|
||||
// draw_rect_surface is used as a 1x1 surface to simplify ren_draw_rect with blending
|
||||
|
@ -167,7 +167,7 @@ static void font_load_glyphset(RenFont* font, int idx) {
|
|||
for (unsigned 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;
|
||||
pixels[++target_offset] = ((source_pixel >> (7 - (column % 8))) & 0x1) * 0xFF;
|
||||
}
|
||||
} else
|
||||
memcpy(&pixels[target_offset], &slot->bitmap.buffer[source_offset], slot->bitmap.width);
|
||||
|
@ -348,10 +348,11 @@ int ren_font_group_get_height(RenFont **fonts) {
|
|||
return fonts[0]->height;
|
||||
}
|
||||
|
||||
double ren_font_group_get_width(RenWindow *window_renderer, RenFont **fonts, const char *text, size_t len) {
|
||||
double ren_font_group_get_width(RenWindow *window_renderer, RenFont **fonts, const char *text, size_t len, int *x_offset) {
|
||||
double width = 0;
|
||||
const char* end = text + len;
|
||||
GlyphMetric* metric = NULL; GlyphSet* set = NULL;
|
||||
bool set_x_offset = x_offset == NULL;
|
||||
while (text < end) {
|
||||
unsigned int codepoint;
|
||||
text = utf8_to_codepoint(text, &codepoint);
|
||||
|
@ -359,8 +360,15 @@ double ren_font_group_get_width(RenWindow *window_renderer, RenFont **fonts, con
|
|||
if (!metric)
|
||||
break;
|
||||
width += (!font || metric->xadvance) ? metric->xadvance : fonts[0]->space_advance;
|
||||
if (!set_x_offset) {
|
||||
set_x_offset = true;
|
||||
*x_offset = metric->bitmap_left; // TODO: should this be scaled by the surface scale?
|
||||
}
|
||||
}
|
||||
const int surface_scale = renwin_get_surface(window_renderer).scale;
|
||||
if (!set_x_offset) {
|
||||
*x_offset = 0;
|
||||
}
|
||||
return width / surface_scale;
|
||||
}
|
||||
|
||||
|
@ -493,33 +501,38 @@ void ren_draw_rect(RenSurface *rs, RenRect rect, RenColor color) {
|
|||
}
|
||||
|
||||
/*************** Window Management ****************/
|
||||
void ren_free_window_resources(RenWindow *window_renderer) {
|
||||
RenWindow* 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 NULL;
|
||||
}
|
||||
RenWindow* window_renderer = malloc(sizeof(RenWindow));
|
||||
|
||||
window_renderer->window = win;
|
||||
renwin_init_surface(window_renderer);
|
||||
renwin_init_command_buf(window_renderer);
|
||||
renwin_clip_to_surface(window_renderer);
|
||||
draw_rect_surface = SDL_CreateRGBSurface(0, 1, 1, 32,
|
||||
0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF);
|
||||
|
||||
return window_renderer;
|
||||
}
|
||||
|
||||
void ren_free(RenWindow* window_renderer) {
|
||||
assert(window_renderer);
|
||||
renwin_free(window_renderer);
|
||||
SDL_FreeSurface(draw_rect_surface);
|
||||
free(window_renderer->command_buf);
|
||||
window_renderer->command_buf = NULL;
|
||||
window_renderer->command_buf_size = 0;
|
||||
free(window_renderer);
|
||||
}
|
||||
|
||||
// TODO remove global and return RenWindow*
|
||||
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_init_command_buf(&window_renderer);
|
||||
renwin_clip_to_surface(&window_renderer);
|
||||
draw_rect_surface = SDL_CreateRGBSurface(0, 1, 1, 32,
|
||||
0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF);
|
||||
}
|
||||
|
||||
|
||||
void ren_resize_window(RenWindow *window_renderer) {
|
||||
renwin_resize_surface(window_renderer);
|
||||
renwin_update_scale(window_renderer);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ typedef struct { SDL_Surface *surface; int scale; } RenSurface;
|
|||
|
||||
struct RenWindow;
|
||||
typedef struct RenWindow RenWindow;
|
||||
extern RenWindow window_renderer;
|
||||
extern RenWindow* window_renderer;
|
||||
|
||||
RenFont* ren_font_load(RenWindow *window_renderer, const char *filename, float size, ERenFontAntialiasing antialiasing, ERenFontHinting hinting, unsigned char style);
|
||||
RenFont* ren_font_copy(RenWindow *window_renderer, RenFont* font, float size, ERenFontAntialiasing antialiasing, ERenFontHinting hinting, int style);
|
||||
|
@ -34,17 +34,17 @@ int ren_font_group_get_height(RenFont **font);
|
|||
float ren_font_group_get_size(RenFont **font);
|
||||
void ren_font_group_set_size(RenWindow *window_renderer, RenFont **font, float size);
|
||||
void ren_font_group_set_tab_size(RenFont **font, int n);
|
||||
double ren_font_group_get_width(RenWindow *window_renderer, RenFont **font, const char *text, size_t len);
|
||||
double ren_font_group_get_width(RenWindow *window_renderer, RenFont **font, const char *text, size_t len, int *x_offset);
|
||||
double ren_draw_text(RenSurface *rs, RenFont **font, const char *text, size_t len, float x, int y, RenColor color);
|
||||
|
||||
void ren_draw_rect(RenSurface *rs, RenRect rect, RenColor color);
|
||||
|
||||
void ren_init(SDL_Window *win);
|
||||
RenWindow* ren_init(SDL_Window *win);
|
||||
void ren_free(RenWindow* window_renderer);
|
||||
void ren_resize_window(RenWindow *window_renderer);
|
||||
void ren_update_rects(RenWindow *window_renderer, RenRect *rects, int count);
|
||||
void ren_set_clip_rect(RenWindow *window_renderer, RenRect rect);
|
||||
void ren_get_size(RenWindow *window_renderer, int *x, int *y); /* Reports the size in points. */
|
||||
void ren_free_window_resources(RenWindow *window_renderer);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -41,7 +41,8 @@ static void setup_renderer(RenWindow *ren, int w, int h) {
|
|||
#endif
|
||||
|
||||
|
||||
void renwin_init_surface(UNUSED RenWindow *ren) {
|
||||
void renwin_init_surface(RenWindow *ren) {
|
||||
ren->scale_x = ren->scale_y = 1;
|
||||
#ifdef LITE_USE_SDL_RENDERER
|
||||
if (ren->rensurface.surface) {
|
||||
SDL_FreeSurface(ren->rensurface.surface);
|
||||
|
@ -107,6 +108,16 @@ void renwin_resize_surface(UNUSED RenWindow *ren) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void renwin_update_scale(RenWindow *ren) {
|
||||
#ifndef LITE_USE_SDL_RENDERER
|
||||
SDL_Surface *surface = SDL_GetWindowSurface(ren->window);
|
||||
int window_w = surface->w, window_h = surface->h;
|
||||
SDL_GetWindowSize(ren->window, &window_w, &window_h);
|
||||
ren->scale_x = (float)surface->w / window_w;
|
||||
ren->scale_y = (float)surface->h / window_h;
|
||||
#endif
|
||||
}
|
||||
|
||||
void renwin_show_window(RenWindow *ren) {
|
||||
SDL_ShowWindow(ren->window);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ struct RenWindow {
|
|||
uint8_t *command_buf;
|
||||
size_t command_buf_idx;
|
||||
size_t command_buf_size;
|
||||
float scale_x;
|
||||
float scale_y;
|
||||
#ifdef LITE_USE_SDL_RENDERER
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
|
@ -19,6 +21,7 @@ void renwin_init_command_buf(RenWindow *ren);
|
|||
void renwin_clip_to_surface(RenWindow *ren);
|
||||
void renwin_set_clip_rect(RenWindow *ren, RenRect rect);
|
||||
void renwin_resize_surface(RenWindow *ren);
|
||||
void renwin_update_scale(RenWindow *ren);
|
||||
void renwin_show_window(RenWindow *ren);
|
||||
void renwin_update_rects(RenWindow *ren, RenRect *rects, int count);
|
||||
void renwin_free(RenWindow *ren);
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[wrap-file]
|
||||
directory = freetype-2.12.1
|
||||
source_url = https://download.savannah.gnu.org/releases/freetype/freetype-2.12.1.tar.xz
|
||||
source_filename = freetype-2.12.1.tar.xz
|
||||
source_hash = 4766f20157cc4cf0cd292f80bf917f92d1c439b243ac3018debf6b9140c41a7f
|
||||
wrapdb_version = 2.12.1-2
|
||||
directory = freetype-2.13.2
|
||||
source_url = https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.xz
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/freetype2_2.13.2-1/freetype-2.13.2.tar.xz
|
||||
source_filename = freetype-2.13.2.tar.xz
|
||||
source_hash = 12991c4e55c506dd7f9b765933e62fd2be2e06d421505d7950a132e4f1bb484d
|
||||
wrapdb_version = 2.13.2-1
|
||||
|
||||
[provide]
|
||||
freetype2 = freetype_dep
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
[wrap-file]
|
||||
directory = lua-5.4.4
|
||||
source_url = https://www.lua.org/ftp/lua-5.4.4.tar.gz
|
||||
source_filename = lua-5.4.4.tar.gz
|
||||
source_hash = 164c7849653b80ae67bec4b7473b884bf5cc8d2dca05653475ec2ed27b9ebf61
|
||||
patch_filename = lua_5.4.4-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/lua_5.4.4-1/get_patch
|
||||
patch_hash = e61cd965c629d6543176f41a9f1cb9050edfd1566cf00ce768ff211086e40bdc
|
||||
directory = lua-5.4.6
|
||||
source_url = https://www.lua.org/ftp/lua-5.4.6.tar.gz
|
||||
source_filename = lua-5.4.6.tar.gz
|
||||
source_hash = 7d5ea1b9cb6aa0b59ca3dde1c6adcb57ef83a1ba8e5432c0ecd06bf439b3ad88
|
||||
patch_filename = lua_5.4.6-3_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/lua_5.4.6-3/get_patch
|
||||
patch_hash = 9b72a95422fd47f79f969d9abdb589ee95712d5512a5246f94e7e4f63d2cb7b7
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/lua_5.4.6-3/lua-5.4.6.tar.gz
|
||||
wrapdb_version = 5.4.6-3
|
||||
|
||||
[provide]
|
||||
lua-5.4 = lua_dep
|
||||
|
||||
lua = lua_dep
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
[wrap-file]
|
||||
directory = pcre2-10.42
|
||||
source_url = https://github.com/PhilipHazel/pcre2/releases/download/pcre2-10.42/pcre2-10.42.tar.bz2
|
||||
source_url = https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.42/pcre2-10.42.tar.bz2
|
||||
source_filename = pcre2-10.42.tar.bz2
|
||||
source_hash = 8d36cd8cb6ea2a4c2bb358ff6411b0c788633a2a45dabbf1aeb4b701d1b5e840
|
||||
patch_filename = pcre2_10.42-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/pcre2_10.42-1/get_patch
|
||||
patch_hash = 06969e916dfee663c189810df57d98574f15e0754a44cd93f3f0bc7234b05d89
|
||||
wrapdb_version = 10.42-1
|
||||
patch_filename = pcre2_10.42-5_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/pcre2_10.42-5/get_patch
|
||||
patch_hash = 7ba1730a3786c46f41735658a9884b09bc592af3840716e0ccc552e7ddf5630c
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/pcre2_10.42-5/pcre2-10.42.tar.bz2
|
||||
wrapdb_version = 10.42-5
|
||||
|
||||
[provide]
|
||||
libpcre2-8 = libpcre2_8
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
[wrap-file]
|
||||
directory = SDL2-2.26.0
|
||||
source_url = https://github.com/libsdl-org/SDL/releases/download/release-2.26.0/SDL2-2.26.0.tar.gz
|
||||
source_filename = SDL2-2.26.0.tar.gz
|
||||
source_hash = 8000d7169febce93c84b6bdf376631f8179132fd69f7015d4dadb8b9c2bdb295
|
||||
patch_filename = sdl2_2.26.0-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/sdl2_2.26.0-1/get_patch
|
||||
patch_hash = 6fcfd727d71cf7837332723518d5e47ffd64f1e7630681cf4b50e99f2bf7676f
|
||||
wrapdb_version = 2.26.0-1
|
||||
directory = SDL2-2.28.1
|
||||
source_url = https://github.com/libsdl-org/SDL/releases/download/release-2.28.1/SDL2-2.28.1.tar.gz
|
||||
source_filename = SDL2-2.28.1.tar.gz
|
||||
source_hash = 4977ceba5c0054dbe6c2f114641aced43ce3bf2b41ea64b6a372d6ba129cb15d
|
||||
patch_filename = sdl2_2.28.1-2_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/sdl2_2.28.1-2/get_patch
|
||||
patch_hash = 2dd332226ba2a4373c6d4eb29fa915e9d5414cf7bb9fa2e4a5ef3b16a06e2736
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/sdl2_2.28.1-2/SDL2-2.28.1.tar.gz
|
||||
wrapdb_version = 2.28.1-2
|
||||
|
||||
[provide]
|
||||
sdl2 = sdl2_dep
|
||||
sdl2main = sdl2main_dep
|
||||
sdl2_test = sdl2_test_dep
|
||||
|
|
Loading…
Reference in New Issue