Merge branch 'master' into amiga2.1

This commit is contained in:
George Sokianos 2023-01-08 21:04:04 +00:00
commit 65d95c7f40
83 changed files with 3237 additions and 843 deletions

View File

@ -131,3 +131,40 @@ jobs:
with: with:
name: Windows Artifacts name: Windows Artifacts
path: ${{ env.INSTALL_NAME }}.zip path: ${{ env.INSTALL_NAME }}.zip
build_windows_msvc:
name: Windows (MSVC)
runs-on: windows-2019
strategy:
matrix:
arch: [amd64, amd64_x86]
steps:
- uses: actions/checkout@v2
- uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.arch }}
- uses: actions/setup-python@v1
with:
python-version: '3.x'
- name: Install meson and ninja
run: pip install meson ninja
- name: Set up environment variables
run: |
"INSTALL_NAME=lite-xl-$($env:GITHUB_REF -replace ".*/")-windows-msvc-${{ matrix.arch }}" >> $env:GITHUB_ENV
"INSTALL_REF=$($env:GITHUB_REF -replace ".*/")" >> $env:GITHUB_ENV
"LUA_SUBPROJECT_PATH=subprojects/lua-5.4.4" >> $env:GITHUB_ENV
- name: Configure
run: |
meson setup --wrap-mode=forcefallback build
Get-Content -Path resources/windows/001-lua-unicode.diff -Raw | patch -d $env:LUA_SUBPROJECT_PATH -p1 --forward
- name: Build
run: meson install -C build --destdir="../lite-xl"
- name: Package
run: |
Remove-Item -Recurse -Force -Path "lite-xl/lib","lite-xl/include"
Compress-Archive -Path lite-xl -DestinationPath "$env:INSTALL_NAME.zip"
- name: Upload Artifacts
uses: actions/upload-artifact@v2
with:
name: Windows Artifacts (MSVC)
path: ${{ env.INSTALL_NAME }}.zip

View File

@ -1,17 +1,21 @@
name: Release name: Release
on: on:
push:
tags:
- v[0-9]+.*
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version: version:
description: Release Version description: Release Version
default: v2.1.0 default: v2.1.1
required: true required: true
jobs: jobs:
release: release:
name: Create Release name: Create Release
runs-on: ubuntu-20.04 runs-on: ubuntu-18.04
outputs: outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
version: ${{ steps.tag.outputs.version }} version: ${{ steps.tag.outputs.version }}
@ -26,6 +30,12 @@ jobs:
else else
echo ::set-output name=version::${GITHUB_REF/refs\/tags\//} echo ::set-output name=version::${GITHUB_REF/refs\/tags\//}
fi fi
- name: Update Tag
uses: richardsimko/update-tag@v1
with:
tag_name: ${{ steps.tag.outputs.version }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release - name: Create Release
id: create_release id: create_release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
@ -33,14 +43,13 @@ jobs:
tag_name: ${{ steps.tag.outputs.version }} tag_name: ${{ steps.tag.outputs.version }}
name: Lite XL ${{ steps.tag.outputs.version }} name: Lite XL ${{ steps.tag.outputs.version }}
draft: true draft: true
prerelease: false
body_path: changelog.md body_path: changelog.md
generate_release_notes: true generate_release_notes: true
build_linux: build_linux:
name: Linux name: Linux
needs: release needs: release
runs-on: ubuntu-20.04 runs-on: ubuntu-18.04
env: env:
CC: gcc CC: gcc
CXX: g++ CXX: g++
@ -76,6 +85,7 @@ jobs:
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
tag_name: ${{ needs.release.outputs.version }} tag_name: ${{ needs.release.outputs.version }}
draft: true
files: | files: |
lite-xl-${{ env.INSTALL_REF }}-linux-x86_64-portable.tar.gz lite-xl-${{ env.INSTALL_REF }}-linux-x86_64-portable.tar.gz
lite-xl-${{ env.INSTALL_REF }}-addons-linux-x86_64-portable.tar.gz lite-xl-${{ env.INSTALL_REF }}-addons-linux-x86_64-portable.tar.gz
@ -86,6 +96,9 @@ jobs:
name: macOS (x86_64) name: macOS (x86_64)
needs: release needs: release
runs-on: macos-11 runs-on: macos-11
strategy:
matrix:
arch: [x86_64, arm64]
env: env:
CC: clang CC: clang
CXX: clang++ CXX: clang++
@ -100,8 +113,8 @@ jobs:
run: | run: |
echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV" echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
echo "INSTALL_NAME=lite-xl-${{ needs.release.outputs.version }}-macos-$(uname -m)" >> "$GITHUB_ENV" echo "INSTALL_NAME=lite-xl-${{ needs.release.outputs.version }}-macos-${{ matrix.arch }}" >> "$GITHUB_ENV"
echo "INSTALL_NAME_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-macos-$(uname -m)" >> "$GITHUB_ENV" echo "INSTALL_NAME_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-macos-${{ matrix.arch }}" >> "$GITHUB_ENV"
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Python Setup - name: Python Setup
uses: actions/setup-python@v2 uses: actions/setup-python@v2
@ -112,15 +125,16 @@ jobs:
- name: Build - name: Build
run: | run: |
bash --version bash --version
bash scripts/build.sh --bundle --debug --forcefallback --release CROSS_ARCH=${{ matrix.arch }} bash scripts/build.sh --bundle --debug --forcefallback --release
- name: Create DMG Image - name: Create DMG Image
run: | run: |
bash scripts/package.sh --version ${INSTALL_REF} --debug --dmg --release CROSS_ARCH=${{ matrix.arch }} bash scripts/package.sh --version ${INSTALL_REF} --debug --dmg --release
bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --dmg --release CROSS_ARCH=${{ matrix.arch }} bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --dmg --release
- name: Upload Files - name: Upload Files
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
tag_name: ${{ needs.release.outputs.version }} tag_name: ${{ needs.release.outputs.version }}
draft: true
files: | files: |
${{ env.INSTALL_NAME }}.dmg ${{ env.INSTALL_NAME }}.dmg
${{ env.INSTALL_NAME_ADDONS }}.dmg ${{ env.INSTALL_NAME_ADDONS }}.dmg
@ -176,6 +190,7 @@ jobs:
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
tag_name: ${{ needs.release.outputs.version }} tag_name: ${{ needs.release.outputs.version }}
draft: true
files: | files: |
${{ env.INSTALL_NAME }}.zip ${{ env.INSTALL_NAME }}.zip
${{ env.INSTALL_NAME_ADDONS }}.zip ${{ env.INSTALL_NAME_ADDONS }}.zip

View File

@ -1,6 +1,166 @@
# Changes Log # Changes Log
## [2.1.0] - 2022-09-25 ## [2.1.1] - 2022-12-29
### New Features
* Add config.keep_newline_whitespace option
([#1184](https://github.com/lite-xl/lite-xl/pull/1184))
* Add regex.find_offsets, regex.find, improve regex.match
([#1232](https://github.com/lite-xl/lite-xl/pull/1232))
* Added regex.gmatch ([#1233](https://github.com/lite-xl/lite-xl/pull/1233))
* add touch events ([#1245](https://github.com/lite-xl/lite-xl/pull/1245))
### Performance Improvements
* highlighter: autostop co-routine when not needed
([#881](https://github.com/lite-xl/lite-xl/pull/881))
* core: ported regex.gsub to faster native version
([#1233](https://github.com/lite-xl/lite-xl/pull/1233))
### Backward Incompatible Changes
* For correctness, the behaviour of `regex.match` was changed to more closely
behave like `string.match`.
* `regex.find_offsets` now provides the previous functionality of `regex.match`
with a more appropriate function name.
* `regex.gsub` doesn't provides the indexes of matches and replacements anymore,
now it behaves more similar to `string.gsub` (the only known affected plugin
was `regexreplacepreview` which has already been adapted)
### UI Enhancements
* statusview: respect right padding of item tooltip
([0373d29f](https://github.com/lite-xl/lite-xl/commit/0373d29f99f286b2fbdda5a6837ef3797c988b88))
* feat: encode home in statusview file path
([#1224](https://github.com/lite-xl/lite-xl/pull/1224))
* autocomplete: wrap the autocomplete results around
([#1223](https://github.com/lite-xl/lite-xl/pull/1223))
* feat: alert user via nagview if file cannot be saved
([#1230](https://github.com/lite-xl/lite-xl/pull/1230))
* contextmenu: make divider less aggressive
([#1228](https://github.com/lite-xl/lite-xl/pull/1228))
* Improve IME location updates
([#1170](https://github.com/lite-xl/lite-xl/pull/1170))
* fix: move tab scroll buttons to remove spacing before 1st tab
([#1231](https://github.com/lite-xl/lite-xl/pull/1231))
* Allow TreeView file operation commands when focused
([#1256](https://github.com/lite-xl/lite-xl/pull/1256))
* contextmenu: adjust y positioning if less than zero
([#1268](https://github.com/lite-xl/lite-xl/pull/1268))
### Fixes
* Don't sort in Doc:get_selection_idx with an invalid index
([b029f599](https://github.com/lite-xl/lite-xl/commit/b029f5993edb7dee5ccd2ba55faac1ec22e24609))
* tokenizer: remove the limit of 3 subsyntaxes depth
([#1186](https://github.com/lite-xl/lite-xl/pull/1186))
* dirmonitor: give kevent a timeout so it doesn't lock forever
([#1180](https://github.com/lite-xl/lite-xl/pull/1180))
* dirmonitor: fix win32 implementation name length to prevent ub
([5ab8dc0](https://github.com/lite-xl/lite-xl/commit/5ab8dc027502146dd947b3d2c7544ba096a3881b))
* Make linewrapping plugin recompute breaks before scrolling
([#1190](https://github.com/lite-xl/lite-xl/pull/1190))
* Add missing get_exe_filename() implementation for FreeBSD
([#1198](https://github.com/lite-xl/lite-xl/pull/1198))
* (Windows) Load fonts with UTF-8 filenames
([#1201](https://github.com/lite-xl/lite-xl/pull/1201))
* Use subsyntax info to toggle comments
([#1202](https://github.com/lite-xl/lite-xl/pull/1202))
* Pass the currently selected item to CommandView validation
([#1203](https://github.com/lite-xl/lite-xl/pull/1203))
* Windows font loading hotfix
([#1205](https://github.com/lite-xl/lite-xl/pull/1205))
* better error messages for checkcolor
([#1211](https://github.com/lite-xl/lite-xl/pull/1211))
* Fix native plugins not reloading upon core:restart
([#1219](https://github.com/lite-xl/lite-xl/pull/1219))
* Converted from bytes to characters, as this is what windows is expecting
([5ab8dc02](https://github.com/lite-xl/lite-xl/commit/5ab8dc027502146dd947b3d2c7544ba096a3881b))
* Fix some syntax errors ([#1243](https://github.com/lite-xl/lite-xl/pull/1243))
* toolbarview: Remove tooltip when hidden
([#1251](https://github.com/lite-xl/lite-xl/pull/1251))
* detectindent: Limit subsyntax depth
([#1253](https://github.com/lite-xl/lite-xl/pull/1253))
* Use Lua string length instead of relying on strlen (#1262)
([#1262](https://github.com/lite-xl/lite-xl/pull/1262))
* dirmonitor: fix high cpu usage
([#1271](https://github.com/lite-xl/lite-xl/pull/1271)),
([#1274](https://github.com/lite-xl/lite-xl/pull/1274))
* Fix popping subsyntaxes that end consecutively
([#1246](https://github.com/lite-xl/lite-xl/pull/1246))
* Fix userdata APIs for Lua 5.4 in native plugin interface
([#1188](https://github.com/lite-xl/lite-xl/pull/1188))
* Fix horizontal scroll with touchpad on MacOS
([74349f8e](https://github.com/lite-xl/lite-xl/commit/74349f8e566ec31acd9a831a060b677d706ae4e8))
### Other Changes
* (Windows) MSVC Support ([#1199](https://github.com/lite-xl/lite-xl/pull/1199))
* meson: updated all subproject wraps
([#1214](https://github.com/lite-xl/lite-xl/pull/1214))
* set arch tuple in meson ([#1254](https://github.com/lite-xl/lite-xl/pull/1254))
* update documentation for system
([#1210](https://github.com/lite-xl/lite-xl/pull/1210))
* docs api: added dirmonitor
([7bb86e16](https://github.com/lite-xl/lite-xl/commit/7bb86e16f291256a99d2e87beb77de890cfaf0fe))
* trimwhitespace: expose functionality and extra features
([#1238](https://github.com/lite-xl/lite-xl/pull/1238))
* plugins projectsearch: expose its functionality
([#1235](https://github.com/lite-xl/lite-xl/pull/1235))
* Simplify SDL message boxes
([#1249](https://github.com/lite-xl/lite-xl/pull/1249))
* Add example settings to _overwrite_ an existing key binding
([#1270](https://github.com/lite-xl/lite-xl/pull/1270))
* Fix two typos in data/init.lua
([#1272](https://github.com/lite-xl/lite-xl/pull/1272))
* Updated meson wraps to latest (SDL v2.26, PCRE2 v10.42)
## [2.1.0] - 2022-11-01
### New Features ### New Features
* Make distinction between * Make distinction between
@ -124,6 +284,12 @@
* Added in ability to have init.so as a require for cpath. * Added in ability to have init.so as a require for cpath.
([#1126](https://github.com/lite-xl/lite-xl/pull/1126)) ([#1126](https://github.com/lite-xl/lite-xl/pull/1126))
* Added system.raise_window() ([#1131](https://github.com/lite-xl/lite-xl/pull/1131))
* Initial horizontal scrollbar support ([#1124](https://github.com/lite-xl/lite-xl/pull/1124))
* IME support ([#991](https://github.com/lite-xl/lite-xl/pull/991))
### Performance Improvements ### Performance Improvements
* [Load space metrics only when creating font](https://github.com/lite-xl/lite-xl/pull/1032) * [Load space metrics only when creating font](https://github.com/lite-xl/lite-xl/pull/1032)
@ -327,11 +493,19 @@
* [Fix memory leak](https://github.com/lite-xl/lite-xl/pull/1039) and wrong * [Fix memory leak](https://github.com/lite-xl/lite-xl/pull/1039) and wrong
check in font_retrieve check in font_retrieve
* Many, many, many more changes that are too numerous to list.
* CommandView: do not change caret size with config.line_height * CommandView: do not change caret size with config.line_height
([#1080](https://github.com/lite-xl/lite-xl/pull/1080)) ([#1080](https://github.com/lite-xl/lite-xl/pull/1080))
* Fixed process layer argument quoting; allows for strings with spaces
([#1132](https://github.com/lite-xl/lite-xl/pull/1132))
* Draw lite-xl icon in TitleView ([#1143](https://github.com/lite-xl/lite-xl/pull/1143))
* Add parameter validation to checkcolor and f_font_group
([#1145](https://github.com/lite-xl/lite-xl/pull/1145))
* Many, many, many more changes that are too numerous to list.
## [2.0.5] - 2022-01-29 ## [2.0.5] - 2022-01-29
Revamp the project's user module so that modifications are immediately applied. Revamp the project's user module so that modifications are immediately applied.
@ -830,6 +1004,7 @@ A new global variable `USERDIR` is exposed to point to the user's directory.
- subpixel font rendering with gamma correction - subpixel font rendering with gamma correction
[2.1.1]: https://github.com/lite-xl/lite-xl/releases/tag/v2.1.1
[2.1.0]: https://github.com/lite-xl/lite-xl/releases/tag/v2.1.0 [2.1.0]: https://github.com/lite-xl/lite-xl/releases/tag/v2.1.0
[2.0.5]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.5 [2.0.5]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.5
[2.0.4]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.4 [2.0.4]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.4

View File

@ -3,12 +3,9 @@ local command = require "core.command"
local common = require "core.common" local common = require "core.common"
local config = require "core.config" local config = require "core.config"
local translate = require "core.doc.translate" local translate = require "core.doc.translate"
local style = require "core.style"
local DocView = require "core.docview" local DocView = require "core.docview"
local tokenizer = require "core.tokenizer"
local function dv()
return core.active_view
end
local function doc() local function doc()
@ -40,9 +37,24 @@ local function save(filename)
filename = core.normalize_to_project_dir(filename) filename = core.normalize_to_project_dir(filename)
abs_filename = core.project_absolute_path(filename) abs_filename = core.project_absolute_path(filename)
end end
doc():save(filename, abs_filename) local ok, err = pcall(doc().save, doc(), filename, abs_filename)
local saved_filename = doc().filename if ok then
core.log("Saved \"%s\"", saved_filename) local saved_filename = doc().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), {
{ font = style.font, text = "No", default_no = true },
{ font = style.font, text = "Yes" , default_yes = true }
}, function(item)
if item.text == "Yes" then
core.add_thread(function()
-- we need to run this in a thread because of the odd way the nagview is.
command.perform("doc:save-as")
end)
end
end)
end
end end
local function cut_or_copy(delete) local function cut_or_copy(delete)
@ -59,8 +71,9 @@ local function cut_or_copy(delete)
doc():delete_to_cursor(idx, 0) doc():delete_to_cursor(idx, 0)
end end
else -- Cut/copy whole line else -- Cut/copy whole line
text = doc().lines[line1] -- Remove newline from the text. It will be added as needed on paste.
full_text = full_text == "" and text or (full_text .. text) text = string.sub(doc().lines[line1], 1, -2)
full_text = full_text == "" and text or (full_text .. text .. "\n")
core.cursor_clipboard_whole_line[idx] = true core.cursor_clipboard_whole_line[idx] = true
if delete then if delete then
if line1 < #doc().lines then if line1 < #doc().lines then
@ -85,7 +98,15 @@ local function split_cursor(direction)
table.insert(new_cursors, { line1 + direction, col1 }) table.insert(new_cursors, { line1 + direction, col1 })
end end
end end
for i,v in ipairs(new_cursors) do doc():add_selection(v[1], v[2]) end -- add selections in the order that will leave the "last" added one as doc.last_selection
local start, stop = 1, #new_cursors
if direction < 0 then
start, stop = #new_cursors, 1
end
for i = start, stop, direction do
local v = new_cursors[i]
doc():add_selection(v[1], v[2])
end
core.blink_reset() core.blink_reset()
end end
@ -177,10 +198,30 @@ local function block_comment(comment, line1, col1, line2, col2)
end end
end end
local function insert_paste(doc, value, whole_line, idx)
if whole_line then
local line1, col1 = doc:get_selection_idx(idx)
doc:insert(line1, 1, value:gsub("\r", "").."\n")
-- Because we're inserting at the start of the line,
-- if the cursor is in the middle of the line
-- it gets carried to the next line along with the old text.
-- If it's at the start of the line it doesn't get carried,
-- so we move it of as many characters as we're adding.
if col1 == 1 then
doc:move_to_cursor(idx, #value+1)
end
else
doc:text_input(value:gsub("\r", ""), idx)
end
end
local commands = { local commands = {
["doc:select-none"] = function(dv) ["doc:select-none"] = function(dv)
local line, col = dv.doc:get_selection() local l1, c1 = dv.doc:get_selection_idx(dv.doc.last_selection)
dv.doc:set_selection(line, col) if not l1 then
l1, c1 = dv.doc:get_selection_idx(1)
end
dv.doc:set_selection(l1, c1)
end, end,
["doc:cut"] = function() ["doc:cut"] = function()
@ -202,27 +243,51 @@ local commands = {
["doc:paste"] = function(dv) ["doc:paste"] = function(dv)
local clipboard = system.get_clipboard() local clipboard = system.get_clipboard()
-- If the clipboard has changed since our last look, use that instead -- If the clipboard has changed since our last look, use that instead
local external_paste = core.cursor_clipboard["full"] ~= clipboard if core.cursor_clipboard["full"] ~= clipboard then
if external_paste then
core.cursor_clipboard = {} core.cursor_clipboard = {}
core.cursor_clipboard_whole_line = {} core.cursor_clipboard_whole_line = {}
end for idx in dv.doc:get_selections() do
local value, whole_line insert_paste(dv.doc, clipboard, false, idx)
for idx, line1, col1, line2, col2 in dv.doc:get_selections() do
if #core.cursor_clipboard_whole_line == (#dv.doc.selections/4) then
value = core.cursor_clipboard[idx]
whole_line = core.cursor_clipboard_whole_line[idx] == true
else
value = clipboard
whole_line = not external_paste and clipboard:find("\n") ~= nil
end end
if whole_line then return
dv.doc:insert(line1, 1, value:gsub("\r", "")) end
if col1 == 1 then -- Use internal clipboard(s)
dv.doc:move_to_cursor(idx, #value) -- If there are mixed whole lines and normal lines, consider them all as normal
local only_whole_lines = true
for _,whole_line in pairs(core.cursor_clipboard_whole_line) do
if not whole_line then
only_whole_lines = false
break
end
end
if #core.cursor_clipboard_whole_line == (#dv.doc.selections/4) then
-- If we have the same number of clipboards and selections,
-- paste each clipboard into its corresponding selection
for idx in dv.doc:get_selections() do
insert_paste(dv.doc, core.cursor_clipboard[idx], only_whole_lines, idx)
end
else
-- Paste every clipboard and add a selection at the end of each one
local new_selections = {}
for idx in dv.doc:get_selections() do
for cb_idx in ipairs(core.cursor_clipboard_whole_line) do
insert_paste(dv.doc, core.cursor_clipboard[cb_idx], only_whole_lines, idx)
if not only_whole_lines then
table.insert(new_selections, {dv.doc:get_selection_idx(idx)})
end
end
if only_whole_lines then
table.insert(new_selections, {dv.doc:get_selection_idx(idx)})
end
end
local first = true
for _,selection in pairs(new_selections) do
if first then
dv.doc:set_selection(table.unpack(selection))
first = false
else
dv.doc:add_selection(table.unpack(selection))
end end
else
dv.doc:text_input(value:gsub("\r", ""), idx)
end end
end end
end, end,
@ -234,7 +299,7 @@ local commands = {
indent = indent:sub(#indent + 2 - col) indent = indent:sub(#indent + 2 - col)
end end
-- Remove current line if it contains only whitespace -- Remove current line if it contains only whitespace
if dv.doc.lines[line]:match("^%s+$") then if not config.keep_newline_whitespace and dv.doc.lines[line]:match("^%s+$") then
dv.doc:remove(line, 1, line, math.huge) dv.doc:remove(line, 1, line, math.huge)
end end
dv.doc:text_input("\n" .. indent, idx) dv.doc:text_input("\n" .. indent, idx)
@ -380,15 +445,28 @@ local commands = {
end, end,
["doc:toggle-block-comments"] = function(dv) ["doc:toggle-block-comments"] = function(dv)
local comment = dv.doc.syntax.block_comment
if not comment then
if dv.doc.syntax.comment then
command.perform "doc:toggle-line-comments"
end
return
end
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
local current_syntax = dv.doc.syntax
if line1 > 1 then
-- Use the previous line state, as it will be the state
-- of the beginning of the current line
local state = dv.doc.highlighter:get_line(line1 - 1).state
local syntaxes = tokenizer.extract_subsyntaxes(dv.doc.syntax, state)
-- Go through all the syntaxes until the first with `block_comment` defined
for _, s in pairs(syntaxes) do
if s.block_comment then
current_syntax = s
break
end
end
end
local comment = current_syntax.block_comment
if not comment then
if dv.doc.syntax.comment then
command.perform "doc:toggle-line-comments"
end
return
end
-- if nothing is selected, toggle the whole line -- if nothing is selected, toggle the whole line
if line1 == line2 and col1 == col2 then if line1 == line2 and col1 == col2 then
col1 = 1 col1 = 1
@ -399,9 +477,23 @@ local commands = {
end, end,
["doc:toggle-line-comments"] = function(dv) ["doc:toggle-line-comments"] = function(dv)
local comment = dv.doc.syntax.comment or dv.doc.syntax.block_comment for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
if comment then local current_syntax = dv.doc.syntax
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do if line1 > 1 then
-- Use the previous line state, as it will be the state
-- of the beginning of the current line
local state = dv.doc.highlighter:get_line(line1 - 1).state
local syntaxes = tokenizer.extract_subsyntaxes(dv.doc.syntax, state)
-- Go through all the syntaxes until the first with comments defined
for _, s in pairs(syntaxes) do
if s.comment or s.block_comment then
current_syntax = s
break
end
end
end
local comment = current_syntax.comment or current_syntax.block_comment
if comment then
dv.doc:set_selections(idx, line_comment(comment, line1, col1, line2, col2)) dv.doc:set_selections(idx, line_comment(comment, line1, col1, line2, col2))
end end
end end
@ -538,7 +630,7 @@ local commands = {
} }
command.add(function(x, y) command.add(function(x, y)
if x == nil or y == nil or not core.active_view:is(DocView) then return false end if x == nil or y == nil or not core.active_view:extends(DocView) then return false end
local dv = core.active_view local dv = core.active_view
local x1,y1,x2,y2 = dv.position.x, dv.position.y, dv.position.x + dv.size.x, dv.position.y + dv.size.y local x1,y1,x2,y2 = dv.position.x, dv.position.y, dv.position.x + dv.size.x, dv.position.y + dv.size.y
return x >= x1 + dv:get_gutter_width() and x < x2 and y >= y1 and y < y2, dv, x, y return x >= x1 + dv:get_gutter_width() and x < x2 and y >= y1 and y < y2, dv, x, y

View File

@ -216,7 +216,7 @@ command.add("core.docview!", {
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil) return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
end end
local result, matches = regex.gsub(regex.compile(old, "m"), text, new) local result, matches = regex.gsub(regex.compile(old, "m"), text, new)
return result, #matches return result, matches
end) end)
end, end,

View File

@ -123,5 +123,13 @@ command.add(nil, {
return true return true
end end
return false return false
end,
["root:horizontal-scroll"] = function(delta)
local view = (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) or core.active_view
if view and view.scrollable then
view.scroll.to.x = view.scroll.to.x + delta * -config.mouse_wheel_scroll
return true
end
return false
end end
}) })

View File

@ -88,6 +88,10 @@ function CommandView:get_scrollable_size()
return 0 return 0
end end
function CommandView:get_h_scrollable_size()
return 0
end
function CommandView:scroll_to_make_visible() function CommandView:scroll_to_make_visible()
-- no-op function to disable this functionality -- no-op function to disable this functionality
@ -155,7 +159,7 @@ end
function CommandView:submit() function CommandView:submit()
local suggestion = self.suggestions[self.suggestion_idx] local suggestion = self.suggestions[self.suggestion_idx]
local text = self:get_text() local text = self:get_text()
if self.state.validate(text) then if self.state.validate(text, suggestion) then
local submit = self.state.submit local submit = self.state.submit
self:exit(true) self:exit(true)
submit(text, suggestion) submit(text, suggestion)

View File

@ -6,8 +6,19 @@ config.message_timeout = 5
config.mouse_wheel_scroll = 50 * SCALE config.mouse_wheel_scroll = 50 * SCALE
config.animate_drag_scroll = false config.animate_drag_scroll = false
config.scroll_past_end = true config.scroll_past_end = true
---@type "expanded" | "contracted" | false @Force the scrollbar status of the DocView
config.force_scrollbar_status = false
config.file_size_limit = 10 config.file_size_limit = 10
config.ignore_files = { "^%." } config.ignore_files = {
-- folders
"^%.svn/", "^%.git/", "^%.hg/", "^CVS/", "^%.Trash/", "^%.Trash%-.*/",
"^node_modules/", "^%.cache/", "^__pycache__/",
-- files
"%.pyc$", "%.pyo$", "%.exe$", "%.dll$", "%.obj$", "%.o$",
"%.a$", "%.lib$", "%.so$", "%.dylib$", "%.ncb$", "%.sdf$",
"%.suo$", "%.pdb$", "%.idb$", "%.class$", "%.psd$", "%.db$",
"^desktop%.ini$", "^%.DS_Store$", "^%.directory$",
}
config.symbol_pattern = "[%a_][%w_]*" config.symbol_pattern = "[%a_][%w_]*"
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-" config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
config.undo_merge_timeout = 0.3 config.undo_merge_timeout = 0.3
@ -19,6 +30,7 @@ config.highlight_current_line = true
config.line_height = 1.2 config.line_height = 1.2
config.indent_size = 2 config.indent_size = 2
config.tab_type = "soft" config.tab_type = "soft"
config.keep_newline_whitespace = false
config.line_limit = 80 config.line_limit = 80
config.max_project_files = 2000 config.max_project_files = 2000
config.transitions = true config.transitions = true
@ -47,8 +59,9 @@ config.plugins = {}
-- Allow you to set plugin configs even if we haven't seen the plugin before. -- Allow you to set plugin configs even if we haven't seen the plugin before.
setmetatable(config.plugins, { setmetatable(config.plugins, {
__index = function(t, k) __index = function(t, k)
if rawget(t, k) == nil then rawset(t, k, {}) end local v = rawget(t, k)
return rawget(t, k) if v == true or v == nil then v = {} rawset(t, k, v) end
return v
end end
}) })

View File

@ -9,6 +9,7 @@ local View = require "core.view"
local border_width = 1 local border_width = 1
local divider_width = 1 local divider_width = 1
local divider_padding = 5
local DIVIDER = {} local DIVIDER = {}
---@class core.contextmenu : core.object ---@class core.contextmenu : core.object
@ -29,7 +30,7 @@ local function get_item_size(item)
local lw, lh local lw, lh
if item == DIVIDER then if item == DIVIDER then
lw = 0 lw = 0
lh = divider_width lh = divider_width + divider_padding * SCALE * 2
else else
lw = style.font:get_width(item.text) lw = style.font:get_width(item.text)
if item.info then if item.info then
@ -82,12 +83,8 @@ function ContextMenu:show(x, y)
local w, h = self.items.width, self.items.height local w, h = self.items.width, self.items.height
-- by default the box is opened on the right and below -- by default the box is opened on the right and below
if x + w >= core.root_view.size.x then x = common.clamp(x, 0, core.root_view.size.x - w - style.padding.x)
x = x - w y = common.clamp(y, 0, core.root_view.size.y - h)
end
if y + h >= core.root_view.size.y then
y = y - h
end
self.position.x, self.position.y = x, y self.position.x, self.position.y = x, y
self.show_context_menu = true self.show_context_menu = true
@ -224,7 +221,7 @@ function ContextMenu:draw_context_menu()
for i, item, x, y, w, h in self:each_item() do for i, item, x, y, w, h in self:each_item() do
if item == DIVIDER then if item == DIVIDER then
renderer.draw_rect(x, y, w, h, style.caret) renderer.draw_rect(x, y + divider_padding * SCALE, w, divider_width, style.divider)
else else
if i == self.selected then if i == self.selected then
renderer.draw_rect(x, y, w, h, style.selection) renderer.draw_rect(x, y, w, h, style.selection)

View File

@ -2,7 +2,7 @@ local common = require "core.common"
local config = require "core.config" local config = require "core.config"
local dirwatch = {} local dirwatch = {}
function dirwatch:__index(idx) function dirwatch:__index(idx)
local value = rawget(self, idx) local value = rawget(self, idx)
if value ~= nil then return value end if value ~= nil then return value end
return dirwatch[idx] return dirwatch[idx]
@ -14,8 +14,8 @@ function dirwatch.new()
watched = {}, watched = {},
reverse_watched = {}, reverse_watched = {},
monitor = dirmonitor.new(), monitor = dirmonitor.new(),
windows_watch_top = nil, single_watch_top = nil,
windows_watch_count = 0 single_watch_count = 0
} }
setmetatable(t, dirwatch) setmetatable(t, dirwatch)
return t return t
@ -38,23 +38,23 @@ function dirwatch:watch(directory, bool)
local info = system.get_file_info(directory) local info = system.get_file_info(directory)
if not info then return end if not info then return end
if not self.watched[directory] and not self.scanned[directory] then if not self.watched[directory] and not self.scanned[directory] then
if PLATFORM == "Windows" then if self.monitor:mode() == "single" then
if info.type ~= "dir" then return self:scan(directory) end if info.type ~= "dir" then return self:scan(directory) end
if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then if not self.single_watch_top or directory:find(self.single_watch_top, 1, true) ~= 1 then
-- Get the highest level of directory that is common to this directory, and the original. -- Get the highest level of directory that is common to this directory, and the original.
local target = directory local target = directory
while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do while self.single_watch_top and self.single_watch_top:find(target, 1, true) ~= 1 do
target = common.dirname(target) target = common.dirname(target)
end end
if target ~= self.windows_watch_top then if target ~= self.single_watch_top then
local value = self.monitor:watch(target) local value = self.monitor:watch(target)
if value and value < 0 then if value and value < 0 then
return self:scan(directory) return self:scan(directory)
end end
self.windows_watch_top = target self.single_watch_top = target
end end
end end
self.windows_watch_count = self.windows_watch_count + 1 self.single_watch_count = self.single_watch_count + 1
self.watched[directory] = true self.watched[directory] = true
else else
local value = self.monitor:watch(directory) local value = self.monitor:watch(directory)
@ -72,13 +72,13 @@ end
-- this should be an absolute path -- this should be an absolute path
function dirwatch:unwatch(directory) function dirwatch:unwatch(directory)
if self.watched[directory] then if self.watched[directory] then
if PLATFORM ~= "Windows" then if self.monitor:mode() == "multiple" then
self.monitor:unwatch(self.watched[directory]) self.monitor:unwatch(self.watched[directory])
self.reverse_watched[directory] = nil self.reverse_watched[directory] = nil
else else
self.windows_watch_count = self.windows_watch_count - 1 self.single_watch_count = self.single_watch_count - 1
if self.windows_watch_count == 0 then if self.single_watch_count == 0 then
self.windows_watch_top = nil self.single_watch_top = nil
self.monitor:unwatch(directory) self.monitor:unwatch(directory)
end end
end end
@ -93,8 +93,12 @@ function dirwatch:check(change_callback, scan_time, wait_time)
local had_change = false local had_change = false
self.monitor:check(function(id) self.monitor:check(function(id)
had_change = true had_change = true
if PLATFORM == "Windows" then if self.monitor:mode() == "single" then
change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id)) local path = common.dirname(id)
if not string.match(id, "^/") and not string.match(id, "^%a:[/\\]") then
path = common.dirname(self.single_watch_top .. PATHSEP .. id)
end
change_callback(path)
elseif self.reverse_watched[id] then elseif self.reverse_watched[id] then
change_callback(self.reverse_watched[id]) change_callback(self.reverse_watched[id])
end end
@ -162,7 +166,7 @@ end
local function compare_file(a, b) local function compare_file(a, b)
return a.filename < b.filename return system.path_compare(a.filename, a.type, b.filename, b.type)
end end

View File

@ -10,42 +10,49 @@ local Highlighter = Object:extend()
function Highlighter:new(doc) function Highlighter:new(doc)
self.doc = doc self.doc = doc
self.running = false
self:reset() self:reset()
end
-- init incremental syntax highlighting -- init incremental syntax highlighting
function Highlighter:start()
if self.running then return end
self.running = true
core.add_thread(function() core.add_thread(function()
while true do while self.first_invalid_line < self.max_wanted_line do
if self.first_invalid_line > self.max_wanted_line then local max = math.min(self.first_invalid_line + 40, self.max_wanted_line)
self.max_wanted_line = 0 local retokenized_from
coroutine.yield(1 / config.fps) for i = self.first_invalid_line, max do
local state = (i > 1) and self.lines[i - 1].state
else local line = self.lines[i]
local max = math.min(self.first_invalid_line + 40, self.max_wanted_line) if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
retokenized_from = retokenized_from or i
local retokenized_from self.lines[i] = self:tokenize_line(i, state)
for i = self.first_invalid_line, max do elseif retokenized_from then
local state = (i > 1) and self.lines[i - 1].state self:update_notify(retokenized_from, i - retokenized_from - 1)
local line = self.lines[i] retokenized_from = nil
if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
retokenized_from = retokenized_from or i
self.lines[i] = self:tokenize_line(i, state)
elseif retokenized_from then
self:update_notify(retokenized_from, i - retokenized_from - 1)
retokenized_from = nil
end
end end
if retokenized_from then
self:update_notify(retokenized_from, max - retokenized_from)
end
self.first_invalid_line = max + 1
core.redraw = true
coroutine.yield()
end end
if retokenized_from then
self:update_notify(retokenized_from, max - retokenized_from)
end
self.first_invalid_line = max + 1
core.redraw = true
coroutine.yield()
end end
self.max_wanted_line = 0
self.running = false
end, self) end, self)
end end
local function set_max_wanted_lines(self, amount)
self.max_wanted_line = amount
if self.first_invalid_line < self.max_wanted_line then
self:start()
end
end
function Highlighter:reset() function Highlighter:reset()
self.lines = {} self.lines = {}
@ -62,7 +69,7 @@ end
function Highlighter:invalidate(idx) function Highlighter:invalidate(idx)
self.first_invalid_line = math.min(self.first_invalid_line, idx) self.first_invalid_line = math.min(self.first_invalid_line, idx)
self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines) set_max_wanted_lines(self, math.min(self.max_wanted_line, #self.doc.lines))
end end
function Highlighter:insert_notify(line, n) function Highlighter:insert_notify(line, n)
@ -101,7 +108,7 @@ function Highlighter:get_line(idx)
self.lines[idx] = line self.lines[idx] = line
self:update_notify(idx, 0) self:update_notify(idx, 0)
end end
self.max_wanted_line = math.max(self.max_wanted_line, idx) set_max_wanted_lines(self, math.max(self.max_wanted_line, idx))
return line return line
end end

View File

@ -33,6 +33,7 @@ end
function Doc:reset() function Doc:reset()
self.lines = { "\n" } self.lines = { "\n" }
self.selections = { 1, 1, 1, 1 } self.selections = { 1, 1, 1, 1 }
self.last_selection = 1
self.undo_stack = { idx = 1 } self.undo_stack = { idx = 1 }
self.redo_stack = { idx = 1 } self.redo_stack = { idx = 1 }
self.clean_change_id = 1 self.clean_change_id = 1
@ -141,15 +142,39 @@ function Doc:get_change_id()
return self.undo_stack.idx return self.undo_stack.idx
end end
local function sort_positions(line1, col1, line2, col2)
if line1 > line2 or line1 == line2 and col1 > col2 then
return line2, col2, line1, col1, true
end
return line1, col1, line2, col2, false
end
-- Cursor section. Cursor indices are *only* valid during a get_selections() call. -- Cursor section. Cursor indices are *only* valid during a get_selections() call.
-- Cursors will always be iterated in order from top to bottom. Through normal operation -- Cursors will always be iterated in order from top to bottom. Through normal operation
-- curors can never swap positions; only merge or split, or change their position in cursor -- curors can never swap positions; only merge or split, or change their position in cursor
-- order. -- order.
function Doc:get_selection(sort) function Doc:get_selection(sort)
local idx, line1, col1, line2, col2, swap = self:get_selections(sort)({ self.selections, sort }, 0) local line1, col1, line2, col2, swap = self:get_selection_idx(self.last_selection, sort)
if not line1 then
line1, col1, line2, col2, swap = self:get_selection_idx(1, sort)
end
return line1, col1, line2, col2, swap return line1, col1, line2, col2, swap
end 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]
if line1 and sort then
return sort_positions(line1, col1, line2, col2)
else
return line1, col1, line2, col2
end
end
function Doc:get_selection_text(limit) function Doc:get_selection_text(limit)
limit = limit or math.huge limit = limit or math.huge
local result = {} local result = {}
@ -181,13 +206,6 @@ function Doc:sanitize_selection()
end end
end end
local function sort_positions(line1, col1, line2, col2)
if line1 > line2 or line1 == line2 and col1 > col2 then
return line2, col2, line1, col1, true
end
return line1, col1, line2, col2, false
end
function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm) function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm)
assert(not line2 == not col2, "expected 3 or 5 arguments") assert(not line2 == not col2, "expected 3 or 5 arguments")
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
@ -206,10 +224,14 @@ function Doc:add_selection(line1, col1, line2, col2, swap)
end end
end end
self:set_selections(target, line1, col1, line2, col2, swap, 0) self:set_selections(target, line1, col1, line2, col2, swap, 0)
self.last_selection = target
end end
function Doc:remove_selection(idx) function Doc:remove_selection(idx)
if self.last_selection >= idx then
self.last_selection = self.last_selection - 1
end
common.splice(self.selections, (idx - 1) * 4 + 1, 4) common.splice(self.selections, (idx - 1) * 4 + 1, 4)
end end
@ -217,6 +239,7 @@ end
function Doc:set_selection(line1, col1, line2, col2, swap) function Doc:set_selection(line1, col1, line2, col2, swap)
self.selections = {} self.selections = {}
self:set_selections(1, line1, col1, line2, col2, swap) self:set_selections(1, line1, col1, line2, col2, swap)
self.last_selection = 1
end end
function Doc:merge_cursors(idx) function Doc:merge_cursors(idx)
@ -225,6 +248,9 @@ function Doc:merge_cursors(idx)
if self.selections[i] == self.selections[j] and if self.selections[i] == self.selections[j] and
self.selections[i+1] == self.selections[j+1] then self.selections[i+1] == self.selections[j+1] then
common.splice(self.selections, i, 4) common.splice(self.selections, i, 4)
if self.last_selection >= (i+3)/4 then
self.last_selection = self.last_selection - 1
end
break break
end end
end end
@ -456,6 +482,18 @@ function Doc:text_input(text, idx)
end end
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
self:delete_to_cursor(sidx)
end
self:insert(line1, col1, text)
self:set_selections(sidx, line1, col1 + #text, line1, col1)
end
end
function Doc:replace_cursor(idx, line1, col1, line2, col2, fn) function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
local old_text = self:get_text(line1, col1, line2, col2) local old_text = self:get_text(line1, col1, line2, col2)
local new_text, res = fn(old_text) local new_text, res = fn(old_text)

View File

@ -4,6 +4,7 @@ local config = require "core.config"
local style = require "core.style" local style = require "core.style"
local keymap = require "core.keymap" local keymap = require "core.keymap"
local translate = require "core.doc.translate" local translate = require "core.doc.translate"
local ime = require "core.ime"
local View = require "core.view" local View = require "core.view"
---@class core.docview : core.view ---@class core.docview : core.view
@ -60,6 +61,11 @@ function DocView:new(doc)
self.doc = assert(doc) self.doc = assert(doc)
self.font = "code_font" self.font = "code_font"
self.last_x_offset = {} self.last_x_offset = {}
self.ime_selection = { from = 0, size = 0 }
self.ime_status = false
self.hovering_gutter = false
self.v_scrollbar:set_forced_status(config.force_scrollbar_status)
self.h_scrollbar:set_forced_status(config.force_scrollbar_status)
end end
@ -111,6 +117,10 @@ function DocView:get_scrollable_size()
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
end end
function DocView:get_h_scrollable_size()
return math.huge
end
function DocView:get_font() function DocView:get_font()
return style[self.font] return style[self.font]
@ -239,8 +249,14 @@ end
function DocView:on_mouse_moved(x, y, ...) function DocView:on_mouse_moved(x, y, ...)
DocView.super.on_mouse_moved(self, x, y, ...) DocView.super.on_mouse_moved(self, x, y, ...)
if self.hovered_scrollbar_track or self.dragging_scrollbar then self.hovering_gutter = false
local gw = self:get_gutter_width()
if self:scrollbar_hovering() or self:scrollbar_dragging() then
self.cursor = "arrow" self.cursor = "arrow"
elseif gw > 0 and x >= self.position.x and x <= (self.position.x + gw) then
self.cursor = "arrow"
self.hovering_gutter = true
else else
self.cursor = "ibeam" self.cursor = "ibeam"
end end
@ -282,6 +298,29 @@ function DocView:mouse_selection(doc, snap_type, line1, col1, line2, col2)
end end
function DocView:on_mouse_pressed(button, x, y, clicks)
if button ~= "left" or not self.hovering_gutter then
return DocView.super.on_mouse_pressed(self, button, x, y, clicks)
end
local line = self:resolve_screen_position(x, y)
if keymap.modkeys["shift"] then
local sline, scol, sline2, scol2 = self.doc:get_selection(true)
if line > sline then
self.doc:set_selection(sline, 1, line, #self.doc.lines[line])
else
self.doc:set_selection(line, 1, sline2, #self.doc.lines[sline2])
end
else
if clicks == 1 then
self.doc:set_selection(line, 1, line, 1)
elseif clicks == 2 then
self.doc:set_selection(line, 1, line, #self.doc.lines[line])
end
end
return true
end
function DocView:on_mouse_released(...) function DocView:on_mouse_released(...)
DocView.super.on_mouse_released(self, ...) DocView.super.on_mouse_released(self, ...)
self.mouse_selecting = nil self.mouse_selecting = nil
@ -292,13 +331,53 @@ function DocView:on_text_input(text)
self.doc:text_input(text) self.doc:text_input(text)
end end
function DocView:on_ime_text_editing(text, start, length)
self.doc:ime_text_editing(text, start, length)
self.ime_status = #text > 0
self.ime_selection.from = start
self.ime_selection.size = length
-- Set the composition bounding box that the system IME
-- will consider when drawing its interface
local line1, col1, line2, col2 = self.doc:get_selection(true)
local col = math.min(col1, col2)
self:update_ime_location()
self:scroll_to_make_visible(line1, col + start)
end
---Update the composition bounding box that the system IME
---will consider when drawing its interface
function DocView:update_ime_location()
if not self.ime_status then return end
local line1, col1, line2, col2 = self.doc:get_selection(true)
local x, y = self:get_line_screen_position(line1)
local h = self:get_line_height()
local col = math.min(col1, col2)
local x1, x2 = 0, 0
if self.ime_selection.size > 0 then
-- focus on a part of the text
local from = col + self.ime_selection.from
local to = from + self.ime_selection.size
x1 = self:get_col_x_offset(line1, from)
x2 = self:get_col_x_offset(line1, to)
else
-- focus the whole text
x1 = self:get_col_x_offset(line1, col1)
x2 = self:get_col_x_offset(line2, col2)
end
ime.set_location(x + x1, y, x2 - x1, h)
end
function DocView:update() function DocView:update()
-- scroll to make caret visible and reset blink timer if it moved -- scroll to make caret visible and reset blink timer if it moved
local line1, col1, line2, col2 = self.doc:get_selection() local line1, col1, line2, col2 = self.doc:get_selection()
if (line1 ~= self.last_line1 or col1 ~= self.last_col1 or if (line1 ~= self.last_line1 or col1 ~= self.last_col1 or
line2 ~= self.last_line2 or col2 ~= self.last_col2) and self.size.x > 0 then line2 ~= self.last_line2 or col2 ~= self.last_col2) and self.size.x > 0 then
if core.active_view == self then if core.active_view == self and not ime.editing then
self:scroll_to_make_visible(line1, col1) self:scroll_to_make_visible(line1, col1)
end end
core.blink_reset() core.blink_reset()
@ -316,6 +395,8 @@ function DocView:update()
core.blink_timer = tb core.blink_timer = tb
end end
self:update_ime_location()
DocView.super.update(self) DocView.super.update(self)
end end
@ -329,9 +410,17 @@ end
function DocView:draw_line_text(line, x, y) function DocView:draw_line_text(line, x, y)
local default_font = self:get_font() local default_font = self:get_font()
local tx, ty = x, y + self:get_line_text_y_offset() local tx, ty = x, y + self:get_line_text_y_offset()
for _, type, text in self.doc.highlighter:each_token(line) do local last_token = nil
local tokens = self.doc.highlighter:get_line(line).tokens
local tokens_count = #tokens
if string.sub(tokens[tokens_count], -1) == "\n" then
last_token = tokens_count - 1
end
for tidx, type, text in self.doc.highlighter:each_token(line) do
local color = style.syntax[type] local color = style.syntax[type]
local font = style.syntax_fonts[type] or default_font local font = style.syntax_fonts[type] or default_font
-- do not render newline, fixes issue #1164
if tidx == last_token then text = text:sub(1, -2) end
tx = renderer.draw_text(font, text, tx, ty, color) tx = renderer.draw_text(font, text, tx, ty, color)
end end
return self:get_line_height() return self:get_line_height()
@ -399,17 +488,45 @@ function DocView:draw_line_gutter(line, x, y, width)
end end
function DocView:draw_ime_decoration(line1, col1, line2, col2)
local x, y = self:get_line_screen_position(line1)
local line_size = math.max(1, SCALE)
local lh = self:get_line_height()
-- Draw IME underline
local x1 = self:get_col_x_offset(line1, col1)
local x2 = self:get_col_x_offset(line2, col2)
renderer.draw_rect(x + math.min(x1, x2), y + lh - line_size, math.abs(x1 - x2), line_size, style.text)
-- Draw IME selection
local col = math.min(col1, col2)
local from = col + self.ime_selection.from
local to = from + self.ime_selection.size
x1 = self:get_col_x_offset(line1, from)
if from ~= to then
x2 = self:get_col_x_offset(line1, to)
line_size = style.caret_width
renderer.draw_rect(x + math.min(x1, x2), y + lh - line_size, math.abs(x1 - x2), line_size, style.caret)
end
self:draw_caret(x + x1, y)
end
function DocView:draw_overlay() function DocView:draw_overlay()
if core.active_view == self then if core.active_view == self then
local minline, maxline = self:get_visible_line_range() local minline, maxline = self:get_visible_line_range()
-- draw caret if it overlaps this line -- draw caret if it overlaps this line
local T = config.blink_period local T = config.blink_period
for _, line, col in self.doc:get_selections() do for _, line1, col1, line2, col2 in self.doc:get_selections() do
if line >= minline and line <= maxline if line1 >= minline and line1 <= maxline
and system.window_has_focus() then and system.window_has_focus() then
if config.disable_blink if ime.editing then
or (core.blink_timer - core.blink_start) % T < T / 2 then self:draw_ime_decoration(line1, col1, line2, col2)
self:draw_caret(self:get_line_screen_position(line, col)) 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))
end
end end
end end
end end

View File

@ -7,9 +7,15 @@ local View = require "core.view"
local EmptyView = View:extend() local EmptyView = View:extend()
local function draw_text(x, y, color) local function draw_text(x, y, color)
local lines = {
{ fmt = "%s to run a command", cmd = "core:find-command" },
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
}
local th = style.big_font:get_height() local th = style.big_font:get_height()
local dh = 2 * th + style.padding.y * 2 local dh = 2 * th + style.padding.y * 2
local x1, y1 = x, y + (dh - th) / 2 local x1, y1 = x, y + ((dh - th) / #lines)
local xv = x1 local xv = x1
local title = "Lite XL" local title = "Lite XL"
local version = "version " .. VERSION local version = "version " .. VERSION
@ -24,12 +30,6 @@ local function draw_text(x, y, color)
renderer.draw_text(style.font, version, xv, y1 + th, color) renderer.draw_text(style.font, version, xv, y1 + th, color)
x = x + style.padding.x x = x + style.padding.x
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color) renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
local lines = {
{ fmt = "%s to run a command", cmd = "core:find-command" },
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
}
th = style.font:get_height() th = style.font:get_height()
y = y + (dh - (th + style.padding.y) * #lines) / 2 y = y + (dh - (th + style.padding.y) * #lines) / 2
local w = 0 local w = 0

92
data/core/ime.lua Normal file
View File

@ -0,0 +1,92 @@
local core = require "core"
local ime = { }
function ime.reset()
ime.editing = false
ime.last_location = { x = 0, y = 0, w = 0, h = 0 }
end
---Convert from utf-8 offset and length (from SDL) to byte offsets
---@param text string @Textediting string
---@param start integer @0-based utf-8 offset of the starting position of the selection
---@param length integer @Size of the utf-8 length of the selection
function ime.ingest(text, start, length)
if #text == 0 then
-- finished textediting
ime.reset()
return "", 0, 0
end
ime.editing = true
if start < 0 then
-- we assume no selection and caret at the end
return text, #text, 0
end
-- start is 0-based, so we use start + 1
local start_byte = utf8.offset(text, start + 1)
if not start_byte then
-- bad start offset
-- we assume it meant the last byte of the text
start_byte = #text
else
start_byte = math.min(start_byte - 1, #text)
end
if length < 0 then
-- caret only
return text, start_byte, 0
end
local end_byte = utf8.offset(text, start + length + 1)
if not end_byte or end_byte - 1 < start_byte then
-- bad length, assume caret only
return text, start_byte, 0
end
end_byte = math.min(end_byte - 1, #text)
return text, start_byte, end_byte - start_byte
end
---Forward the given textediting SDL event data to Views.
---@param text string @Textediting string
---@param start integer @0-based utf-8 offset of the starting position of the selection
---@param length integer @Size of the utf-8 length of the selection
function ime.on_text_editing(text, start, length, ...)
if ime.editing or #text > 0 then
core.root_view:on_ime_text_editing(ime.ingest(text, start, length, ...))
end
end
---Stop IME composition.
---Might not completely work on every platform.
function ime.stop()
if ime.editing then
-- SDL_ClearComposition for now doesn't work everywhere
system.clear_ime()
ime.on_text_editing("", 0, 0)
end
end
---Set the bounding box of the text pertaining the IME.
---The IME will draw its interface based on this info.
---@param x number
---@param y number
---@param w number
---@param h number
function ime.set_location(x, y, w, h)
if not ime.last_location or
ime.last_location.x ~= x or
ime.last_location.y ~= y or
ime.last_location.w ~= w or
ime.last_location.h ~= h
then
ime.last_location.x, ime.last_location.y, ime.last_location.w, ime.last_location.h = x, y, w, h
system.set_text_input_rect(x, y, w, h)
end
end
ime.reset()
return ime

View File

@ -6,6 +6,7 @@ local style = require "colors.default"
local command local command
local keymap local keymap
local dirwatch local dirwatch
local ime
local RootView local RootView
local StatusView local StatusView
local TitleView local TitleView
@ -295,7 +296,19 @@ function core.add_project_directory(path)
end end
return refresh_directory(topdir, dirpath) return refresh_directory(topdir, dirpath)
end, 0.01, 0.01) end, 0.01, 0.01)
coroutine.yield(changed and 0.05 or 0) -- properly exit coroutine if project not open anymore to clear dir watch
local project_dir_open = false
for _, prj in ipairs(core.project_directories) do
if topdir == prj then
project_dir_open = true
break
end
end
if project_dir_open then
coroutine.yield(changed and 0.05 or 0)
else
return
end
end end
end) end)
@ -361,6 +374,11 @@ end
function core.update_project_subdir(dir, filename, expanded) function core.update_project_subdir(dir, filename, expanded)
assert(dir.files_limit, "function should be called only when directory is in files limit mode") assert(dir.files_limit, "function should be called only when directory is in files limit mode")
dir.shown_subdir[filename] = expanded dir.shown_subdir[filename] = expanded
if expanded then
dir.watch:watch(dir.name .. PATHSEP .. filename)
else
dir.watch:unwatch(dir.name .. PATHSEP .. filename)
end
return refresh_directory(dir, filename) return refresh_directory(dir, filename)
end end
@ -477,6 +495,9 @@ local style = require "core.style"
-- key binding: -- key binding:
-- keymap.add { ["ctrl+escape"] = "core:quit" } -- keymap.add { ["ctrl+escape"] = "core:quit" }
-- pass 'true' for second parameter to overwrite an existing binding
-- keymap.add({ ["ctrl+pageup"] = "root:switch-to-previous-tab" }, true)
-- keymap.add({ ["ctrl+pagedown"] = "root:switch-to-next-tab" }, true)
------------------------------- Fonts ---------------------------------------- ------------------------------- Fonts ----------------------------------------
@ -512,11 +533,26 @@ local style = require "core.style"
-- enable or disable plugin loading setting config entries: -- enable or disable plugin loading setting config entries:
-- enable plugins.trimwhitespace, otherwise it is disable by default: -- enable plugins.trimwhitespace, otherwise it is disabled by default:
-- config.plugins.trimwhitespace = true -- config.plugins.trimwhitespace = true
-- --
-- disable detectindent, otherwise it is enabled by default -- disable detectindent, otherwise it is enabled by default
-- config.plugins.detectindent = false -- config.plugins.detectindent = false
---------------------------- Miscellaneous -------------------------------------
-- modify list of files to ignore when indexing the project:
-- config.ignore_files = {
-- -- folders
-- "^%.svn/", "^%.git/", "^%.hg/", "^CVS/", "^%.Trash/", "^%.Trash%-.*/",
-- "^node_modules/", "^%.cache/", "^__pycache__/",
-- -- files
-- "%.pyc$", "%.pyo$", "%.exe$", "%.dll$", "%.obj$", "%.o$",
-- "%.a$", "%.lib$", "%.so$", "%.dylib$", "%.ncb$", "%.sdf$",
-- "%.suo$", "%.pdb$", "%.idb$", "%.class$", "%.psd$", "%.db$",
-- "^desktop%.ini$", "^%.DS_Store$", "^%.directory$",
-- }
]]) ]])
init_file:close() init_file:close()
end end
@ -558,7 +594,7 @@ local config = require "core.config"
-- "^/build.*/" match any top level directory whose name begins with "build" -- "^/build.*/" match any top level directory whose name begins with "build"
-- "^/subprojects/.+/" match any directory inside a top-level folder named "subprojects". -- "^/subprojects/.+/" match any directory inside a top-level folder named "subprojects".
-- You may activate some plugins on a pre-project base to override the user's settings. -- You may activate some plugins on a per-project basis to override the user's settings.
-- config.plugins.trimwitespace = true -- config.plugins.trimwitespace = true
]]) ]])
init_file:close() init_file:close()
@ -640,6 +676,7 @@ function core.init()
command = require "core.command" command = require "core.command"
keymap = require "core.keymap" keymap = require "core.keymap"
dirwatch = require "core.dirwatch" dirwatch = require "core.dirwatch"
ime = require "core.ime"
RootView = require "core.rootview" RootView = require "core.rootview"
StatusView = require "core.statusview" StatusView = require "core.statusview"
TitleView = require "core.titleview" TitleView = require "core.titleview"
@ -1034,6 +1071,8 @@ end
function core.set_active_view(view) function core.set_active_view(view)
assert(view, "Tried to set active view to nil") assert(view, "Tried to set active view to nil")
-- Reset the IME even if the focus didn't change
ime.stop()
if view ~= core.active_view then if view ~= core.active_view then
if core.active_view and core.active_view.force_focus then if core.active_view and core.active_view.force_focus then
core.next_active_view = view core.next_active_view = view
@ -1136,7 +1175,7 @@ function core.custom_log(level, show, backtrace, fmt, ...)
text = text, text = text,
time = os.time(), time = os.time(),
at = at, at = at,
info = backtrace and debug.traceback(nil, 2):gsub("\t", "") info = backtrace and debug.traceback("", 2):gsub("\t", "")
} }
table.insert(core.log_items, item) table.insert(core.log_items, item)
if #core.log_items > config.max_log_items then if #core.log_items > config.max_log_items then
@ -1185,7 +1224,7 @@ function core.try(fn, ...)
local err local err
local ok, res = xpcall(fn, function(msg) local ok, res = xpcall(fn, function(msg)
local item = core.error("%s", msg) local item = core.error("%s", msg)
item.info = debug.traceback(nil, 2):gsub("\t", "") item.info = debug.traceback("", 2):gsub("\t", "")
err = msg err = msg
end, ...) end, ...)
if ok then if ok then
@ -1198,6 +1237,8 @@ function core.on_event(type, ...)
local did_keymap = false local did_keymap = false
if type == "textinput" then if type == "textinput" then
core.root_view:on_text_input(...) core.root_view:on_text_input(...)
elseif type == "textediting" then
ime.on_text_editing(...)
elseif type == "keypressed" then elseif type == "keypressed" then
did_keymap = keymap.on_key_pressed(...) did_keymap = keymap.on_key_pressed(...)
elseif type == "keyreleased" then elseif type == "keyreleased" then
@ -1384,7 +1425,7 @@ function core.on_error(err)
-- write error to file -- write error to file
local fp = io.open(USERDIR .. "/error.txt", "wb") local fp = io.open(USERDIR .. "/error.txt", "wb")
fp:write("Error: " .. tostring(err) .. "\n") fp:write("Error: " .. tostring(err) .. "\n")
fp:write(debug.traceback(nil, 4) .. "\n") fp:write(debug.traceback("", 4) .. "\n")
fp:close() fp:close()
-- save copy of all unsaved documents -- save copy of all unsaved documents
for _, doc in ipairs(core.docs) do for _, doc in ipairs(core.docs) do

View File

@ -6,7 +6,7 @@ local function keymap_macos(keymap)
["cmd+n"] = "core:new-doc", ["cmd+n"] = "core:new-doc",
["cmd+shift+c"] = "core:change-project-folder", ["cmd+shift+c"] = "core:change-project-folder",
["cmd+shift+o"] = "core:open-project-folder", ["cmd+shift+o"] = "core:open-project-folder",
["cmd+shift+r"] = "core:restart", ["cmd+option+r"] = "core:restart",
["cmd+ctrl+return"] = "core:toggle-fullscreen", ["cmd+ctrl+return"] = "core:toggle-fullscreen",
["cmd+ctrl+shift+j"] = "root:split-left", ["cmd+ctrl+shift+j"] = "root:split-left",
@ -34,7 +34,9 @@ local function keymap_macos(keymap)
["cmd+8"] = "root:switch-to-tab-8", ["cmd+8"] = "root:switch-to-tab-8",
["cmd+9"] = "root:switch-to-tab-9", ["cmd+9"] = "root:switch-to-tab-9",
["wheel"] = "root:scroll", ["wheel"] = "root:scroll",
["hwheel"] = "root:horizontal-scroll",
["shift+hwheel"] = "root:horizontal-scroll",
["cmd+f"] = "find-replace:find", ["cmd+f"] = "find-replace:find",
["cmd+r"] = "find-replace:replace", ["cmd+r"] = "find-replace:replace",
["f3"] = "find-replace:repeat-find", ["f3"] = "find-replace:repeat-find",

View File

@ -1,6 +1,7 @@
local core = require "core" local core = require "core"
local command = require "core.command" local command = require "core.command"
local config = require "core.config" local config = require "core.config"
local ime = require "core.ime"
local keymap = {} local keymap = {}
---@alias keymap.shortcut string ---@alias keymap.shortcut string
@ -179,6 +180,10 @@ end
-- Events listening -- Events listening
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function keymap.on_key_pressed(k, ...) 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] local mk = modkey_map[k]
if mk then if mk then
keymap.modkeys[mk] = true keymap.modkeys[mk] = true
@ -209,9 +214,32 @@ function keymap.on_key_pressed(k, ...)
return false return false
end end
function keymap.on_mouse_wheel(delta, ...) function keymap.on_mouse_wheel(delta_y, delta_x, ...)
return not (keymap.on_key_pressed("wheel" .. (delta > 0 and "up" or "down"), delta, ...) local y_direction = delta_y > 0 and "up" or "down"
or keymap.on_key_pressed("wheel", delta, ...)) local x_direction = delta_x > 0 and "left" or "right"
-- Try sending a "cumulative" event for both scroll directions
if delta_y ~= 0 and delta_x ~= 0 then
local result = keymap.on_key_pressed("wheel" .. y_direction .. x_direction, delta_y, delta_x, ...)
if not result then
result = keymap.on_key_pressed("wheelyx", delta_y, delta_x, ...)
end
if result then return true end
end
-- Otherwise send each direction as its own separate event
local y_result, x_result
if delta_y ~= 0 then
y_result = keymap.on_key_pressed("wheel" .. y_direction, delta_y, ...)
if not y_result then
y_result = keymap.on_key_pressed("wheel", delta_y, ...)
end
end
if delta_x ~= 0 then
x_result = keymap.on_key_pressed("wheel" .. x_direction, delta_x, ...)
if not x_result then
x_result = keymap.on_key_pressed("hwheel", delta_x, ...)
end
end
return y_result or x_result
end end
function keymap.on_mouse_pressed(button, x, y, clicks) function keymap.on_mouse_pressed(button, x, y, clicks)
@ -274,6 +302,8 @@ keymap.add_direct {
["alt+8"] = "root:switch-to-tab-8", ["alt+8"] = "root:switch-to-tab-8",
["alt+9"] = "root:switch-to-tab-9", ["alt+9"] = "root:switch-to-tab-9",
["wheel"] = "root:scroll", ["wheel"] = "root:scroll",
["hwheel"] = "root:horizontal-scroll",
["shift+wheel"] = "root:horizontal-scroll",
["ctrl+f"] = "find-replace:find", ["ctrl+f"] = "find-replace:find",
["ctrl+r"] = "find-replace:replace", ["ctrl+r"] = "find-replace:replace",

View File

@ -323,15 +323,14 @@ end
function Node:get_scroll_button_rect(index) function Node:get_scroll_button_rect(index)
local w, pad = get_scroll_button_width() local w, pad = get_scroll_button_width()
local h = style.font:get_height() + style.padding.y * 2 local h = style.font:get_height() + style.padding.y * 2
local x = self.position.x + (index == 1 and 0 or self.size.x - w) local x = self.position.x + (index == 1 and self.size.x - w * 2 or self.size.x - w)
return x, self.position.y, w, h, pad return x, self.position.y, w, h, pad
end end
function Node:get_tab_rect(idx) function Node:get_tab_rect(idx)
local sbw = get_scroll_button_width() local maxw = self.size.x
local maxw = self.size.x - 2 * sbw local x0 = self.position.x
local x0 = self.position.x + sbw
local x1 = x0 + common.clamp(self.tab_width * (idx - 1) - self.tab_shift, 0, maxw) local x1 = x0 + common.clamp(self.tab_width * (idx - 1) - self.tab_shift, 0, maxw)
local x2 = x0 + common.clamp(self.tab_width * idx - self.tab_shift, 0, maxw) local x2 = x0 + common.clamp(self.tab_width * idx - self.tab_shift, 0, maxw)
local h = style.font:get_height() + style.padding.y * 2 local h = style.font:get_height() + style.padding.y * 2
@ -469,7 +468,10 @@ end
function Node:target_tab_width() function Node:target_tab_width()
local n = self:get_visible_tabs_number() local n = self:get_visible_tabs_number()
local w = self.size.x - get_scroll_button_width() * 2 local w = self.size.x
if #self.views > n then
w = self.size.x - get_scroll_button_width() * 2
end
return common.clamp(style.tab_width, w / config.max_tabs, w / n) return common.clamp(style.tab_width, w / config.max_tabs, w / n)
end end
@ -547,24 +549,14 @@ function Node:draw_tab(view, is_active, is_hovered, is_close_hovered, x, y, w, h
end end
function Node:draw_tabs() function Node:draw_tabs()
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1) local _, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
local x = self.position.x
local ds = style.divider_size local ds = style.divider_size
local dots_width = style.font:get_width("") local dots_width = style.font:get_width("")
core.push_clip_rect(x, y, self.size.x, h) core.push_clip_rect(x, y, self.size.x, h)
renderer.draw_rect(x, y, self.size.x, h, style.background2) renderer.draw_rect(x, y, self.size.x, h, style.background2)
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider) renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
if self.tab_offset > 1 then
local button_style = self.hovered_scroll_button == 1 and style.text or style.dim
common.draw_text(style.icon_font, button_style, "<", nil, x + scroll_padding, y, 0, h)
end
local tabs_number = self:get_visible_tabs_number() local tabs_number = self:get_visible_tabs_number()
if #self.views > self.tab_offset + tabs_number - 1 then
local xrb, yrb, wrb = self:get_scroll_button_rect(2)
local button_style = self.hovered_scroll_button == 2 and style.text or style.dim
common.draw_text(style.icon_font, button_style, ">", nil, xrb + scroll_padding, yrb, 0, h)
end
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
local view = self.views[i] local view = self.views[i]
@ -574,6 +566,18 @@ function Node:draw_tabs()
x, y, w, h) x, y, w, h)
end end
if #self.views > tabs_number then
local _, pad = get_scroll_button_width()
local xrb, yrb, wrb, hrb = self:get_scroll_button_rect(1)
renderer.draw_rect(xrb + pad, yrb, wrb * 2, hrb, style.background2)
local left_button_style = (self.hovered_scroll_button == 1 and self.tab_offset > 1) and style.text or style.dim
common.draw_text(style.icon_font, left_button_style, "<", nil, xrb + scroll_padding, yrb, 0, h)
xrb, yrb, wrb = self:get_scroll_button_rect(2)
local right_button_style = (self.hovered_scroll_button == 2 and #self.views > self.tab_offset + tabs_number - 1) and style.text or style.dim
common.draw_text(style.icon_font, right_button_style, ">", nil, xrb + scroll_padding, yrb, 0, h)
end
core.pop_clip_rect() core.pop_clip_rect()
end end

View File

@ -2,70 +2,81 @@
-- pattern:gsub(string). -- pattern:gsub(string).
regex.__index = function(table, key) return regex[key]; end regex.__index = function(table, key) return regex[key]; end
regex.match = function(pattern_string, string, offset, options) ---Looks for the first match of `pattern` in the string `str`.
local pattern = type(pattern_string) == "table" and ---If it finds a match, it returns the indices of `str` where this occurrence
pattern_string or regex.compile(pattern_string) ---starts and ends; otherwise, it returns `nil`.
local res = { regex.cmatch(pattern, string, offset or 1, options or 0) } ---If the pattern has captures, the captured start and end indexes are returned,
res[2] = res[2] and res[2] - 1 ---after the two initial ones.
---
---@param pattern string|table The regex pattern to use, either as a simple string or precompiled.
---@param str string The string to search for valid matches.
---@param offset? integer The position on the subject to start searching.
---@param options? integer A bit field of matching options, eg: regex.NOTBOL | regex.NOTEMPTY
---
---@return integer? start Offset where the first match was found; `nil` if no match.
---@return integer? end Offset where the first match ends; `nil` if no match.
---@return integer? ... #Captured matches offsets.
regex.find_offsets = function(pattern, str, offset, options)
if type(pattern) ~= "table" then
pattern = regex.compile(pattern)
end
local res = { regex.cmatch(pattern, str, offset or 1, options or 0) }
-- Reduce every end delimiter by 1
for i = 2,#res,2 do
res[i] = res[i] - 1
end
return table.unpack(res) return table.unpack(res)
end end
-- Will iterate back through any UTF-8 bytes so that we don't replace bits ---Behaves like `string.match`.
-- mid character. ---Looks for the first match of `pattern` in the string `str`.
local function previous_character(str, index) ---If it finds a match, it returns the matched string; otherwise, it returns `nil`.
local byte ---If the pattern has captures, only the captured strings are returned.
repeat ---If a capture is empty, its offset is returned instead.
index = index - 1 ---
byte = string.byte(str, index) ---@param pattern string|table The regex pattern to use, either as a simple string or precompiled.
until byte < 128 or byte >= 192 ---@param str string The string to search for valid matches.
return index ---@param offset? integer The position on the subject to start searching.
---@param options? integer A bit field of matching options, eg: regex.NOTBOL | regex.NOTEMPTY
---
---@return (string|integer)? ... #List of captured matches; the entire match if no matches were specified; if the match is empty, its offset is returned instead.
regex.match = function(pattern, str, offset, options)
local res = { regex.find(pattern, str, offset, options) }
if #res == 0 then return end
-- If available, only return captures
if #res > 2 then return table.unpack(res, 3) end
return string.sub(str, res[1], res[2])
end end
-- Moves to the end of the identified character. ---Behaves like `string.find`.
local function end_character(str, index) ---Looks for the first match of `pattern` in the string `str`.
local byte = string.byte(str, index + 1) ---If it finds a match, it returns the indices of `str` where this occurrence
while byte and byte >= 128 and byte < 192 do ---starts and ends; otherwise, it returns `nil`.
index = index + 1 ---If the pattern has captures, the captured strings are returned,
byte = string.byte(str, index + 1) ---after the two indexes ones.
end ---If a capture is empty, its offset is returned instead.
return index ---
end ---@param pattern string|table The regex pattern to use, either as a simple string or precompiled.
---@param str string The string to search for valid matches.
-- Build off matching. For now, only support basic replacements, but capture ---@param offset? integer The position on the subject to start searching.
-- groupings should be doable. We can even have custom group replacements and ---@param options? integer A bit field of matching options, eg: regex.NOTBOL | regex.NOTEMPTY
-- transformations and stuff in lua. Currently, this takes group replacements ---
-- as \1 - \9. ---@return integer? start Offset where the first match was found; `nil` if no match.
-- Should work on UTF-8 text. ---@return integer? end Offset where the first match ends; `nil` if no match.
regex.gsub = function(pattern_string, str, replacement) ---@return (string|integer)? ... #List of captured matches; if the match is empty, its offset is returned instead.
local pattern = type(pattern_string) == "table" and regex.find = function(pattern, str, offset, options)
pattern_string or regex.compile(pattern_string) local res = { regex.find_offsets(pattern, str, offset, options) }
local result, indices = "" local out = { }
local matches, replacements = {}, {} if #res == 0 then return end
repeat out[1] = res[1]
indices = { regex.cmatch(pattern, str) } out[2] = res[2]
if #indices > 0 then for i = 3,#res,2 do
table.insert(matches, indices) if res[i] > res[i+1] then
local currentReplacement = replacement -- Like in string.find, if the group has size 0, return the index
if #indices > 2 then table.insert(out, res[i])
for i = 1, (#indices/2 - 1) do else
currentReplacement = string.gsub( table.insert(out, string.sub(str, res[i], res[i+1]))
currentReplacement,
"\\" .. i,
str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))
)
end
end
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
table.insert(replacements, { indices[1], #currentReplacement+indices[1] })
if indices[1] > 1 then
result = result ..
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
else
result = result .. currentReplacement
end
str = str:sub(indices[2])
end end
until #indices == 0 or indices[1] == indices[2] end
return result .. str, matches, replacements return table.unpack(out)
end end

View File

@ -334,6 +334,9 @@ function RootView:on_text_input(...)
core.active_view:on_text_input(...) core.active_view:on_text_input(...)
end end
function RootView:on_ime_text_editing(...)
core.active_view:on_ime_text_editing(...)
end
function RootView:on_focus_lost(...) function RootView:on_focus_lost(...)
-- We force a redraw so documents can redraw without the cursor. -- We force a redraw so documents can redraw without the cursor.

342
data/core/scrollbar.lua Normal file
View File

@ -0,0 +1,342 @@
local core = require "core"
local common = require "core.common"
local config = require "core.config"
local style = require "core.style"
local Object = require "core.object"
---Scrollbar
---Use Scrollbar:set_size to set the bounding box of the view the scrollbar belongs to.
---Use Scrollbar:update to update the scrollbar animations.
---Use Scrollbar:draw to draw the scrollbar.
---Use Scrollbar:on_mouse_pressed, Scrollbar:on_mouse_released,
---Scrollbar:on_mouse_moved and Scrollbar:on_mouse_left to react to mouse movements;
---the scrollbar won't update automatically.
---Use Scrollbar:set_percent to set the scrollbar location externally.
---
---To manage all the orientations, the scrollbar changes the coordinates system
---accordingly. The "normal" coordinate system adapts the scrollbar coordinates
---as if it's always a vertical scrollbar, positioned at the end of the bounding box.
---@class core.scrollbar : core.object
local Scrollbar = Object:extend()
---@class ScrollbarOptions
---@field direction "v" | "h" @Vertical or Horizontal
---@field alignment "s" | "e" @Start or End (left to right, top to bottom)
---@field force_status "expanded" | "contracted" | false @Force the scrollbar status
---@field expanded_size number? @Override the default value specified by `style.expanded_scrollbar_size`
---@field contracted_size number? @Override the default value specified by `style.scrollbar_size`
---@param options ScrollbarOptions
function Scrollbar:new(options)
---Position information of the owner
self.rect = {
x = 0, y = 0, w = 0, h = 0,
---Scrollable size
scrollable = 0
}
self.normal_rect = {
across = 0,
along = 0,
across_size = 0,
along_size = 0,
scrollable = 0
}
---@type integer @Position in percent [0-1]
self.percent = 0
---@type boolean @Scrollbar dragging status
self.dragging = false
---@type integer @Private. Used to offset the start of the drag from the top of the thumb
self.drag_start_offset = 0
---What is currently being hovered. `thumb` implies` track`
self.hovering = { track = false, thumb = false }
---@type "v" | "h"@Vertical or Horizontal
self.direction = options.direction or "v"
---@type "s" | "e" @Start or End (left to right, top to bottom)
self.alignment = options.alignment or "e"
---@type number @Private. Used to keep track of animations
self.expand_percent = 0
---@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.expanded_size = options.expanded_size
end
---Set the status the scrollbar is forced to keep
---@param status "expanded" | "contracted" | false @The status to force
function Scrollbar:set_forced_status(status)
self.force_status = status
if self.force_status == "expanded" then
self.expand_percent = 1
end
end
function Scrollbar:real_to_normal(x, y, w, h)
x, y, w, h = x or 0, y or 0, w or 0, h or 0
if self.direction == "v" then
if self.alignment == "s" then
x = (self.rect.x + self.rect.w) - x - w
end
return x, y, w, h
else
if self.alignment == "s" then
y = (self.rect.y + self.rect.h) - y - h
end
return y, x, h, w
end
end
function Scrollbar:normal_to_real(x, y, w, h)
x, y, w, h = x or 0, y or 0, w or 0, h or 0
if self.direction == "v" then
if self.alignment == "s" then
x = (self.rect.x + self.rect.w) - x - w
end
return x, y, w, h
else
if self.alignment == "s" then
x = (self.rect.y + self.rect.h) - x - w
end
return y, x, h, w
end
end
function Scrollbar:_get_thumb_rect_normal()
local nr = self.normal_rect
local sz = nr.scrollable
if sz == math.huge or sz <= nr.along_size
then
return 0, 0, 0, 0
end
local scrollbar_size = self.contracted_size or style.scrollbar_size
local expanded_scrollbar_size = self.expanded_size or style.expanded_scrollbar_size
local along_size = math.max(20, nr.along_size * nr.along_size / sz)
local across_size = scrollbar_size
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),
across_size,
along_size
end
---Get the thumb rect (the part of the scrollbar that can be dragged)
---@return integer,integer,integer,integer @x, y, w, h
function Scrollbar:get_thumb_rect()
return self:normal_to_real(self:_get_thumb_rect_normal())
end
function Scrollbar:_get_track_rect_normal()
local nr = self.normal_rect
local sz = nr.scrollable
if sz <= nr.along_size or sz == math.huge then
return 0, 0, 0, 0
end
local scrollbar_size = self.contracted_size or style.scrollbar_size
local expanded_scrollbar_size = self.expanded_size or style.expanded_scrollbar_size
local across_size = scrollbar_size
across_size = across_size + (expanded_scrollbar_size - scrollbar_size) * self.expand_percent
return
nr.across + nr.across_size - across_size,
nr.along,
across_size,
nr.along_size
end
---Get the track rect (the "background" of the scrollbar)
---@return number,number,number,number @x, y, w, h
function Scrollbar:get_track_rect()
return self:normal_to_real(self:_get_track_rect_normal())
end
function Scrollbar:_overlaps_normal(x, y)
local sx, sy, sw, sh = self:_get_thumb_rect_normal()
local scrollbar_size = self.contracted_size or style.scrollbar_size
local result
if x >= sx - scrollbar_size * 3 and x <= sx + sw and y >= sy and y <= sy + sh then
result = "thumb"
else
sx, sy, sw, sh = self:_get_track_rect_normal()
if x >= sx - scrollbar_size * 3 and x <= sx + sw and y >= sy and y <= sy + sh then
result = "track"
end
end
return result
end
---Get what part of the scrollbar the coordinates overlap
---@return "thumb"|"track"|nil
function Scrollbar:overlaps(x, y)
x, y = self:real_to_normal(x, y)
return self:_overlaps_normal(x, y)
end
function Scrollbar:_on_mouse_pressed_normal(button, x, y, clicks)
local overlaps = self:_overlaps_normal(x, y)
if overlaps then
local _, along, _, along_size = self:_get_thumb_rect_normal()
self.dragging = true
if overlaps == "thumb" then
self.drag_start_offset = along - y
return true
elseif overlaps == "track" then
self.drag_start_offset = - along_size / 2
return (y - self.normal_rect.along - along_size / 2) / self.normal_rect.along_size
end
end
end
---Updates the scrollbar with mouse pressed info.
---Won't update the scrollbar position automatically.
---Use Scrollbar:set_percent to update it.
---
---This sets the dragging status if needed.
---
---Returns a falsy value if the event happened outside the scrollbar.
---Returns `true` if the thumb was pressed.
---If the track was pressed this returns a value between 0 and 1
---representing the percent of the position.
---@return boolean|number
function Scrollbar:on_mouse_pressed(button, x, y, clicks)
if button ~= "left" then return end
x, y = self:real_to_normal(x, y)
return self:_on_mouse_pressed_normal(button, x, y, clicks)
end
---Updates the scrollbar hover status.
---This gets called by other functions and shouldn't be called manually
function Scrollbar:_update_hover_status_normal(x, y)
local overlaps = self:_overlaps_normal(x, y)
self.hovering.thumb = overlaps == "thumb"
self.hovering.track = self.hovering.thumb or overlaps == "track"
return self.hovering.track or self.hovering.thumb
end
function Scrollbar:_on_mouse_released_normal(button, x, y)
self.dragging = false
return self:_update_hover_status_normal(x, y)
end
---Updates the scrollbar dragging status
function Scrollbar:on_mouse_released(button, x, y)
if button ~= "left" then return end
x, y = self:real_to_normal(x, y)
return self:_on_mouse_released_normal(button, x, y)
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)
end
return self:_update_hover_status_normal(x, y)
end
---Updates the scrollbar with mouse moved info.
---Won't update the scrollbar position automatically.
---Use Scrollbar:set_percent to update it.
---
---This updates the hovering status.
---
---Returns a falsy value if the event happened outside the scrollbar.
---Returns `true` if the scrollbar is hovered.
---If the scrollbar was being dragged, this returns a value between 0 and 1
---representing the percent of the position.
---@return boolean|number
function Scrollbar:on_mouse_moved(x, y, dx, dy)
x, y = self:real_to_normal(x, y)
dx, dy = self:real_to_normal(dx, dy) -- TODO: do we need this? (is this even correct?)
return self:_on_mouse_moved_normal(x, y, dx, dy)
end
---Updates the scrollbar hovering status
function Scrollbar:on_mouse_left()
self.hovering.track, self.hovering.thumb = false, false
end
---Updates the bounding box of the view the scrollbar belongs to.
---@param x number
---@param y number
---@param w number
---@param h number
---@param scrollable number @size of the scrollable area
function Scrollbar:set_size(x, y, w, h, scrollable)
self.rect.x, self.rect.y, self.rect.w, self.rect.h = x, y, w, h
self.rect.scrollable = scrollable
local nr = self.normal_rect
nr.across, nr.along, nr.across_size, nr.along_size = self:real_to_normal(x, y, w, h)
nr.scrollable = scrollable
end
---Updates the scrollbar location
---@param percent number @number between 0 and 1 representing the position of the middle part of the thumb
function Scrollbar:set_percent(percent)
self.percent = percent
end
---Updates the scrollbar animations
function Scrollbar:update()
-- TODO: move the animation code to its own class
if not self.force_status then
local dest = (self.hovering.track or self.dragging) and 1 or 0
local diff = math.abs(self.expand_percent - dest)
if not config.transitions or diff < 0.05 or config.disabled_transitions["scroll"] then
self.expand_percent = dest
else
local rate = 0.3
if config.fps ~= 60 or config.animation_rate ~= 1 then
local dt = 60 / config.fps
rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt)
end
self.expand_percent = common.lerp(self.expand_percent, dest, rate)
end
if diff > 1e-8 then
core.redraw = true
end
elseif self.force_status == "expanded" then
self.expand_percent = 1
elseif self.force_status == "contracted" then
self.expand_percent = 0
end
end
---Draw the scrollbar track
function Scrollbar:draw_track()
if not (self.hovering.track or self.dragging)
and self.expand_percent == 0 then
return
end
local color = { table.unpack(style.scrollbar_track) }
color[4] = color[4] * self.expand_percent
local x, y, w, h = self:get_track_rect()
renderer.draw_rect(x, y, w, h, color)
end
---Draw the scrollbar thumb
function Scrollbar:draw_thumb()
local highlight = self.hovering.thumb or self.dragging
local color = highlight and style.scrollbar2 or style.scrollbar
local x, y, w, h = self:get_thumb_rect()
renderer.draw_rect(x, y, w, h, color)
end
---Draw both the scrollbar track and thumb
function Scrollbar:draw()
self:draw_track()
self:draw_thumb()
end
return Scrollbar

View File

@ -10,11 +10,12 @@ if MACOS_RESOURCES then
DATADIR = MACOS_RESOURCES DATADIR = MACOS_RESOURCES
else else
local prefix = EXEDIR:match("^(.+)[/\\]bin$") local prefix = EXEDIR:match("^(.+)[/\\]bin$")
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data') DATADIR = prefix and (prefix .. PATHSEP .. 'share' .. PATHSEP .. 'lite-xl') or (EXEDIR .. PATHSEP .. 'data')
end end
USERDIR = (system.get_file_info(EXEDIR .. '/user') and (EXEDIR .. '/user')) USERDIR = (system.get_file_info(EXEDIR .. PATHSEP .. 'user') and (EXEDIR .. PATHSEP .. 'user'))
or ((os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl")) or os.getenv("LITE_USERDIR")
or (HOME and (HOME .. '/.config/lite-xl')) or ((os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. PATHSEP .. "lite-xl"))
or (HOME and (HOME .. PATHSEP .. '.config' .. PATHSEP .. 'lite-xl'))
package.path = DATADIR .. '/?.lua;' package.path = DATADIR .. '/?.lua;'
package.path = DATADIR .. '/?/init.lua;' .. package.path package.path = DATADIR .. '/?/init.lua;' .. package.path

View File

@ -11,26 +11,27 @@ local Object = require "core.object"
---@alias core.statusview.styledtext table<integer, renderer.font|renderer.color|string> ---@alias core.statusview.styledtext table<integer, renderer.font|renderer.color|string>
---@alias core.statusview.position '"left"' | '"right"'
---A status bar implementation for lite, check core.status_view. ---A status bar implementation for lite, check core.status_view.
---@class core.statusview : core.view ---@class core.statusview : core.view
---@field public super core.view ---@field super core.view
---@field private items core.statusview.item[] ---@field items core.statusview.item[]
---@field private active_items core.statusview.item[] ---@field active_items core.statusview.item[]
---@field private hovered_item core.statusview.item ---@field hovered_item core.statusview.item
---@field private message_timeout number ---@field message_timeout number
---@field private message core.statusview.styledtext ---@field message core.statusview.styledtext
---@field private tooltip_mode boolean ---@field tooltip_mode boolean
---@field private tooltip core.statusview.styledtext ---@field tooltip core.statusview.styledtext
---@field private left_width number ---@field left_width number
---@field private right_width number ---@field right_width number
---@field private r_left_width number ---@field r_left_width number
---@field private r_right_width number ---@field r_right_width number
---@field private left_xoffset number ---@field left_xoffset number
---@field private right_xoffset number ---@field right_xoffset number
---@field private dragged_panel '"left"' | '"right"' ---@field dragged_panel '""' | core.statusview.position
---@field private hovered_panel '"left"' | '"right"' ---@field hovered_panel '""' | core.statusview.position
---@field private hide_messages boolean ---@field hide_messages boolean
local StatusView = View:extend() local StatusView = View:extend()
---Space separator ---Space separator
@ -42,81 +43,73 @@ StatusView.separator = " "
StatusView.separator2 = " | " StatusView.separator2 = " | "
---@alias core.statusview.item.separator ---@alias core.statusview.item.separator
---|>'core.statusview.separator' # Space separator ---|>`StatusView.separator`
---| 'core.statusview.separator2' # Pipe separator ---| `StatusView.separator2`
---@alias core.statusview.item.predicate fun():boolean ---@alias core.statusview.item.predicate fun():boolean
---@alias core.statusview.item.onclick fun(button: string, x: number, y: number) ---@alias core.statusview.item.onclick fun(button: string, x: number, y: number)
---@alias core.statusview.item.get_item fun():core.statusview.styledtext,core.statusview.styledtext ---@alias core.statusview.item.get_item fun(self: core.statusview.item):core.statusview.styledtext?,core.statusview.styledtext?
---@alias core.statusview.item.ondraw fun(x, y, h, hovered: boolean, calc_only?: boolean):number ---@alias core.statusview.item.ondraw fun(x, y, h, hovered: boolean, calc_only?: boolean):number
---@class core.statusview.item : core.object ---@class core.statusview.item : core.object
---@field name string ---@field name string
---@field predicate core.statusview.item.predicate ---@field predicate core.statusview.item.predicate
---@field alignment core.statusview.item.alignment ---@field alignment core.statusview.item.alignment
---@field tooltip string | nil ---@field tooltip string
---@field command string | nil @Command to perform when the item is clicked. ---@field command string | nil @Command to perform when the item is clicked.
---@field on_click core.statusview.item.onclick | nil @Function called when item is clicked and no command is set. ---Function called when item is clicked and no command is set.
---@field on_draw core.statusview.item.ondraw | nil @Custom drawing that when passed calc true should return the needed width for drawing and when false should draw. ---@field on_click core.statusview.item.onclick | nil
---Custom drawing that when passed calc true should return the needed width for
---drawing and when false should draw.
---@field on_draw core.statusview.item.ondraw | nil
---@field background_color renderer.color | nil ---@field background_color renderer.color | nil
---@field background_color_hover renderer.color | nil ---@field background_color_hover renderer.color | nil
---@field visible boolean ---@field visible boolean
---@field separator core.statusview.item.separator ---@field separator core.statusview.item.separator
---@field private active boolean ---@field active boolean
---@field private x number ---@field x number
---@field private w number ---@field w number
---@field private cached_item core.statusview.styledtext ---@field cached_item core.statusview.styledtext
local StatusViewItem = Object:extend() local StatusViewItem = Object:extend()
---Available StatusViewItem options. ---Available StatusViewItem options.
---@class core.statusview.item.options : table ---@class core.statusview.item.options : table
---A condition to evaluate if the item should be displayed. If a string
---is given it is treated as a require import that should return a valid object
---which is checked against the current active view, the sames applies if a
---table is given. A function that returns a boolean can be used instead to
---perform a custom evaluation, setting to nil means always evaluates to true.
---@field predicate string | table | core.statusview.item.predicate ---@field predicate string | table | core.statusview.item.predicate
---@field name string ---A unique name to identify the item on the status bar.
---@field name string @A unique name to identify the item on the status bar.
---@field alignment core.statusview.item.alignment ---@field alignment core.statusview.item.alignment
---A function that should return a core.statusview.styledtext element,
---returning an empty table is allowed.
---@field get_item core.statusview.item.get_item ---@field get_item core.statusview.item.get_item
---@field command? string | core.statusview.item.onclick ---The name of a valid registered command or a callback function to execute
---when the item is clicked.
---@field command string | core.statusview.item.onclick | nil
---The position in which to insert the given item on the internal table,
---a value of -1 inserts the item at the end which is the default. A value
---of 1 will insert the item at the beggining.
---@field position? integer ---@field position? integer
---@field tooltip? string ---@field tooltip? string @Text displayed when mouse hovers the item.
---@field visible boolean @Flag to show or hide the item
---The type of separator rendered to the right of the item if another item
---follows it.
---@field separator? core.statusview.item.separator ---@field separator? core.statusview.item.separator
local StatusViewItemOptions = {
---A condition to evaluate if the item should be displayed. If a string
---is given it is treated as a require import that should return a valid object
---which is checked against the current active view, the sames applies if a
---table is given. A function that returns a boolean can be used instead to
---perform a custom evaluation, setting to nil means always evaluates to true.
predicate = nil,
---A unique name to identify the item on the status bar.
name = nil,
alignment = nil,
---A function that should return a core.statusview.styledtext element,
---returning empty table is allowed.
get_item = nil,
---The name of a valid registered command or a callback function to execute
---when the item is clicked.
command = nil,
---The position in which to insert the given item on the internal table,
---a value of -1 inserts the item at the end which is the default. A value
---of 1 will insert the item at the beggining.
position = nil,
---Displayed when mouse hovers the item
tooltip = nil,
separator = nil,
}
StatusViewItem.options = StatusViewItemOptions
---Flag to tell the item should me aligned on left side of status bar. ---Flag to tell the item should me aligned on left side of status bar.
---@type number ---@type integer
StatusViewItem.LEFT = 1 StatusViewItem.LEFT = 1
---Flag to tell the item should me aligned on right side of status bar. ---Flag to tell the item should me aligned on right side of status bar.
---@type number ---@type integer
StatusViewItem.RIGHT = 2 StatusViewItem.RIGHT = 2
---@alias core.statusview.item.alignment ---@alias core.statusview.item.alignment
---|>'core.statusview.item.LEFT' ---|>`StatusView.Item.LEFT`
---| 'core.statusview.item.RIGHT' ---| `StatusView.Item.RIGHT`
---Constructor ---Constructor
---@param options core.statusview.item.options ---@param options core.statusview.item.options
@ -210,7 +203,7 @@ function StatusView:register_docview_items()
return { return {
dv.doc:is_dirty() and style.accent or style.text, style.icon_font, "f", dv.doc:is_dirty() and style.accent or style.text, style.icon_font, "f",
style.dim, style.font, self.separator2, style.text, style.dim, style.font, self.separator2, style.text,
dv.doc.filename and style.text or style.dim, dv.doc:get_name() dv.doc.filename and style.text or style.dim, common.home_encode(dv.doc:get_name())
} }
end end
}) })
@ -470,7 +463,7 @@ end
---Hides the given items from the status view or all if no names given. ---Hides the given items from the status view or all if no names given.
---@param names table<integer, string> | string | nil ---@param names? table<integer, string> | string
function StatusView:hide_items(names) function StatusView:hide_items(names)
if type(names) == "string" then if type(names) == "string" then
names = {names} names = {names}
@ -489,7 +482,7 @@ end
---Shows the given items from the status view or all if no names given. ---Shows the given items from the status view or all if no names given.
---@param names table<integer, string> | string | nil ---@param names? table<integer, string> | string
function StatusView:show_items(names) function StatusView:show_items(names)
if type(names) == "string" then if type(names) == "string" then
names = {names} names = {names}
@ -607,8 +600,8 @@ function StatusView:draw_item_tooltip(item)
local x = self.pointer.x - (w / 2) - (style.padding.x * 2) local x = self.pointer.x - (w / 2) - (style.padding.x * 2)
if x < 0 then x = 0 end if x < 0 then x = 0 end
if x + w + (style.padding.x * 2) > self.size.x then if (x + w + (style.padding.x * 3)) > self.size.x then
x = self.size.x - w - (style.padding.x * 2) x = self.size.x - w - (style.padding.x * 3)
end end
renderer.draw_rect( renderer.draw_rect(
@ -783,7 +776,7 @@ function StatusView:update_active_items()
item.cached_item = {} item.cached_item = {}
if item.visible and item:predicate() then if item.visible and item:predicate() then
local styled_text = type(item.get_item) == "function" local styled_text = type(item.get_item) == "function"
and item.get_item(self) or item.get_item and item.get_item(item) or item.get_item
if #styled_text > 0 then if #styled_text > 0 then
remove_spacing(self, styled_text) remove_spacing(self, styled_text)
@ -881,7 +874,7 @@ end
---Drag the given panel if possible. ---Drag the given panel if possible.
---@param panel '"left"' | '"right"' ---@param panel core.statusview.position
---@param dx number ---@param dx number
function StatusView:drag_panel(panel, dx) function StatusView:drag_panel(panel, dx)
if panel == "left" and self.r_left_width > self.left_width then if panel == "left" and self.r_left_width > self.left_width then
@ -915,10 +908,8 @@ end
function StatusView:get_hovered_panel(x, y) function StatusView:get_hovered_panel(x, y)
if y >= self.position.y and x <= self.left_width + style.padding.x then if y >= self.position.y and x <= self.left_width + style.padding.x then
return "left" return "left"
else
return "right"
end end
return "" return "right"
end end
@ -1053,9 +1044,13 @@ function StatusView:on_mouse_released(button, x, y)
end end
function StatusView:on_mouse_wheel(y) function StatusView:on_mouse_wheel(y, x)
if not self.visible then return end if not self.visible or self.hovered_panel == "" then return end
self:drag_panel(self.hovered_panel, y * self.left_width / 10) if x ~= 0 then
self:drag_panel(self.hovered_panel, x * self.left_width / 10)
else
self:drag_panel(self.hovered_panel, y * self.left_width / 10)
end
end end

View File

@ -30,16 +30,21 @@ end
local function find(string, field) local function find(string, field)
local best_match = 0
local best_syntax
for i = #syntax.items, 1, -1 do for i = #syntax.items, 1, -1 do
local t = syntax.items[i] local t = syntax.items[i]
if common.match_pattern(string, t[field] or {}) then local s, e = common.match_pattern(string, t[field] or {})
return t if s and e - s > best_match then
best_match = e - s
best_syntax = t
end end
end end
return best_syntax
end end
function syntax.get(filename, header) function syntax.get(filename, header)
return find(filename, "files") return find(common.basename(filename), "files")
or (header and find(header, "headers")) or (header and find(header, "headers"))
or plain_text_syntax or plain_text_syntax
end end

View File

@ -3,6 +3,14 @@ local common = require "core.common"
local style = require "core.style" local style = require "core.style"
local View = require "core.view" local View = require "core.view"
local icon_colors = {
bg = { common.color "#2e2e32ff" },
color6 = { common.color "#e1e1e6ff" },
color7 = { common.color "#ffa94dff" },
color8 = { common.color "#93ddfaff" },
color9 = { common.color "#f7c95cff" }
};
local restore_command = { local restore_command = {
symbol = "w", action = function() system.set_window_mode("normal") end symbol = "w", action = function() system.set_window_mode("normal") end
} }
@ -43,6 +51,10 @@ function TitleView:configure_hit_test(borderless)
end end
end end
function TitleView:on_scale_change()
self:configure_hit_test(self.visible)
end
function TitleView:update() function TitleView:update()
self.size.y = self.visible and title_view_height() or 0 self.size.y = self.visible and title_view_height() or 0
title_commands[2] = core.window_mode == "maximized" and restore_command or maximize_command title_commands[2] = core.window_mode == "maximized" and restore_command or maximize_command
@ -55,7 +67,11 @@ function TitleView:draw_window_title()
local ox, oy = self:get_content_offset() local ox, oy = self:get_content_offset()
local color = style.text local color = style.text
local x, y = ox + style.padding.x, oy + style.padding.y local x, y = ox + style.padding.x, oy + style.padding.y
x = common.draw_text(style.icon_font, color, "M ", nil, x, y, 0, h) common.draw_text(style.icon_font, icon_colors.bg, "5", nil, x, y, 0, h)
common.draw_text(style.icon_font, icon_colors.color6, "6", nil, x, y, 0, h)
common.draw_text(style.icon_font, icon_colors.color7, "7", nil, x, y, 0, h)
common.draw_text(style.icon_font, icon_colors.color8, "8", nil, x, y, 0, h)
x = common.draw_text(style.icon_font, icon_colors.color9, "9 ", nil, x, y, 0, h)
local title = core.compose_window_title(core.window_title) local title = core.compose_window_title(core.window_title)
common.draw_text(style.font, color, title, nil, x, y, 0, h) common.draw_text(style.font, color, title, nil, x, y, 0, h)
end end

View File

@ -1,6 +1,5 @@
local core = require "core" local core = require "core"
local syntax = require "core.syntax" local syntax = require "core.syntax"
local common = require "core.common"
local tokenizer = {} local tokenizer = {}
local bad_patterns = {} local bad_patterns = {}
@ -51,31 +50,37 @@ local function push_tokens(t, syn, pattern, full_text, find_results)
end end
end end
-- State is a string of bytes, where the count of bytes represents the depth
-- of the subsyntax we are currently in. Each individual byte represents the
-- index of the pattern for the current subsyntax in relation to its parent
-- syntax. Using a string of bytes allows us to have as many subsyntaxes as
-- bytes can be stored on a string while keeping some level of performance in
-- comparison to a Lua table. The only limitation is that a syntax would not
-- be able to contain more than 255 patterns.
--
-- Lets say a state contains 2 bytes byte #1 with value `3` and byte #2 with
-- a value of `5`. This would mean that on the parent syntax at index `3` a
-- pattern subsyntax that matched current text was found, then inside that
-- subsyntax another subsyntax pattern at index `5` that matched current text
-- was also found.
-- State is a 32-bit number that is four separate bytes, illustrating how many -- Calling `push_subsyntax` appends the current subsyntax pattern index to the
-- differnet delimiters we have open, and which subsyntaxes we have active. -- state and increases the stack depth. Calling `pop_subsyntax` clears the
-- At most, there are 3 subsyntaxes active at the same time. Beyond that, -- last appended subsyntax and decreases the stack.
-- does not support further highlighting.
-- You can think of it as a maximum 4 integer (0-255) stack. It always has
-- 1 integer in it. Calling `push_subsyntax` increases the stack depth. Calling
-- `pop_subsyntax` decreases it. The integers represent the index of a pattern
-- that we're following in the syntax. The top of the stack can be any valid
-- pattern index, any integer lower in the stack must represent a pattern that
-- specifies a subsyntax.
-- If you do not have subsyntaxes in your syntax, the three most
-- singificant numbers will always be 0, the stack will only ever be length 1
-- and the state variable will only ever range from 0-255.
local function retrieve_syntax_state(incoming_syntax, state) local function retrieve_syntax_state(incoming_syntax, state)
local current_syntax, subsyntax_info, current_pattern_idx, current_level = local current_syntax, subsyntax_info, current_pattern_idx, current_level =
incoming_syntax, nil, state, 0 incoming_syntax, nil, state:byte(1) or 0, 1
if state > 0 and (state > 255 or current_syntax.patterns[state].syntax) then if
-- If we have higher bits, then decode them one at a time, and find which current_pattern_idx > 0
and
current_syntax.patterns[current_pattern_idx]
then
-- If the state is not empty we iterate over each byte, and find which
-- syntax we're using. Rather than walking the bytes, and calling into -- syntax we're using. Rather than walking the bytes, and calling into
-- `syntax` each time, we could probably cache this in a single table. -- `syntax` each time, we could probably cache this in a single table.
for i = 0, 2 do for i = 1, #state do
local target = bit32.extract(state, i*8, 8) local target = state:byte(i)
if target ~= 0 then if target ~= 0 then
if current_syntax.patterns[target].syntax then if current_syntax.patterns[target].syntax then
subsyntax_info = current_syntax.patterns[target] subsyntax_info = current_syntax.patterns[target]
@ -95,6 +100,21 @@ local function retrieve_syntax_state(incoming_syntax, state)
return current_syntax, subsyntax_info, current_pattern_idx, current_level return current_syntax, subsyntax_info, current_pattern_idx, current_level
end end
---Return the list of syntaxes used in the specified state.
---@param base_syntax table @The initial base syntax (the syntax of the file)
---@param state string @The state of the tokenizer to extract from
---@return table @Array of syntaxes starting from the innermost one
function tokenizer.extract_subsyntaxes(base_syntax, state)
local current_syntax
local t = {}
repeat
current_syntax = retrieve_syntax_state(base_syntax, state)
table.insert(t, current_syntax)
state = string.sub(state, 2)
until #state == 0
return t
end
local function report_bad_pattern(log_fn, syntax, pattern_idx, msg, ...) local function report_bad_pattern(log_fn, syntax, pattern_idx, msg, ...)
if not bad_patterns[syntax] then if not bad_patterns[syntax] then
bad_patterns[syntax] = { } bad_patterns[syntax] = { }
@ -107,7 +127,7 @@ end
---@param incoming_syntax table ---@param incoming_syntax table
---@param text string ---@param text string
---@param state integer ---@param state string
function tokenizer.tokenize(incoming_syntax, text, state) function tokenizer.tokenize(incoming_syntax, text, state)
local res = {} local res = {}
local i = 1 local i = 1
@ -116,9 +136,9 @@ function tokenizer.tokenize(incoming_syntax, text, state)
return { "normal", text } return { "normal", text }
end end
state = state or 0 state = state or string.char(0)
-- incoming_syntax : the parent syntax of the file. -- incoming_syntax : the parent syntax of the file.
-- state : a 32-bit number representing syntax state (see above) -- state : a string of bytes representing syntax state (see above)
-- current_syntax : the syntax we're currently in. -- current_syntax : the syntax we're currently in.
-- subsyntax_info : info about the delimiters of this subsyntax. -- subsyntax_info : info about the delimiters of this subsyntax.
@ -130,7 +150,18 @@ function tokenizer.tokenize(incoming_syntax, text, state)
-- Should be used to set the state variable. Don't modify it directly. -- Should be used to set the state variable. Don't modify it directly.
local function set_subsyntax_pattern_idx(pattern_idx) local function set_subsyntax_pattern_idx(pattern_idx)
current_pattern_idx = pattern_idx current_pattern_idx = pattern_idx
state = bit32.replace(state, pattern_idx, current_level*8, 8) local state_len = #state
if current_level > state_len then
state = state .. string.char(pattern_idx)
elseif state_len == 1 then
state = string.char(pattern_idx)
else
state = ("%s%s%s"):format(
state:sub(1,current_level-1),
string.char(pattern_idx),
state:sub(current_level+1)
)
end
end end
@ -144,8 +175,8 @@ function tokenizer.tokenize(incoming_syntax, text, state)
end end
local function pop_subsyntax() local function pop_subsyntax()
set_subsyntax_pattern_idx(0)
current_level = current_level - 1 current_level = current_level - 1
state = string.sub(state, 1, current_level)
set_subsyntax_pattern_idx(0) set_subsyntax_pattern_idx(0)
current_syntax, subsyntax_info, current_pattern_idx, current_level = current_syntax, subsyntax_info, current_pattern_idx, current_level =
retrieve_syntax_state(incoming_syntax, state) retrieve_syntax_state(incoming_syntax, state)
@ -183,23 +214,12 @@ function tokenizer.tokenize(incoming_syntax, text, state)
return return
end end
res = p.pattern and { text:ufind((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) } res = p.pattern and { text:ufind((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) }
or { regex.match(code, text, text:ucharpos(next), (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) } or { regex.find(code, text, text:ucharpos(next), (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) }
if p.regex and #res > 0 then -- set correct utf8 len for regex result if p.regex and #res > 0 then -- set correct utf8 len for regex result
local char_pos_1 = string.ulen(text:sub(1, res[1])) local char_pos_1 = res[1] > next and string.ulen(text:sub(1, res[1])) or next
local char_pos_2 = char_pos_1 + string.ulen(text:sub(res[1], res[2])) - 1 local char_pos_2 = string.ulen(text:sub(1, res[2]))
-- `regex.match` returns group results as a series of `begin, end` for i=3,#res do
-- we only want `begin`s res[i] = string.ulen(text:sub(1, res[i] - 1)) + 1
if #res >= 3 then
res[3] = char_pos_1 + string.ulen(text:sub(res[1], res[3])) - 1
end
for i=1,(#res-3) do
local curr = i + 3
local from = i * 2 + 3
if from < #res then
res[curr] = char_pos_1 + string.ulen(text:sub(res[1], res[from])) - 1
else
res[curr] = nil
end
end end
res[1] = char_pos_1 res[1] = char_pos_1
res[2] = char_pos_2 res[2] = char_pos_2
@ -317,7 +337,7 @@ function tokenizer.tokenize(incoming_syntax, text, state)
end end
end end
end end
return res, state return res, state
end end

View File

@ -1,8 +1,8 @@
local core = require "core" local core = require "core"
local config = require "core.config" local config = require "core.config"
local style = require "core.style"
local common = require "core.common" local common = require "core.common"
local Object = require "core.object" local Object = require "core.object"
local Scrollbar = require "core.scrollbar"
---@class core.view.position ---@class core.view.position
---@field x number ---@field x number
@ -28,10 +28,6 @@ local Object = require "core.object"
---@field w core.view.thumbtrackwidth ---@field w core.view.thumbtrackwidth
---@field h core.view.thumbtrack ---@field h core.view.thumbtrack
---@class core.view.increment
---@field value number
---@field to number
---@alias core.view.cursor "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'" ---@alias core.view.cursor "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'"
---@alias core.view.mousebutton "'left'" | "'right'" ---@alias core.view.mousebutton "'left'" | "'right'"
@ -47,8 +43,9 @@ local Object = require "core.object"
---@field scroll core.view.scroll ---@field scroll core.view.scroll
---@field cursor core.view.cursor ---@field cursor core.view.cursor
---@field scrollable boolean ---@field scrollable boolean
---@field scrollbar core.view.scrollbar ---@field v_scrollbar core.scrollbar
---@field scrollbar_alpha core.view.increment ---@field h_scrollbar core.scrollbar
---@field current_scale number
local View = Object:extend() local View = Object:extend()
-- context can be "application" or "session". The instance of objects -- context can be "application" or "session". The instance of objects
@ -62,13 +59,9 @@ function View:new()
self.scroll = { x = 0, y = 0, to = { x = 0, y = 0 } } self.scroll = { x = 0, y = 0, to = { x = 0, y = 0 } }
self.cursor = "arrow" self.cursor = "arrow"
self.scrollable = false self.scrollable = false
self.scrollbar = { self.v_scrollbar = Scrollbar({direction = "v", alignment = "e"})
x = { thumb = 0, track = 0 }, self.h_scrollbar = Scrollbar({direction = "h", alignment = "e"})
y = { thumb = 0, track = 0 }, self.current_scale = SCALE
w = { thumb = 0, track = 0, to = { thumb = 0, track = 0 } },
h = { thumb = 0, track = 0 },
}
self.scrollbar_alpha = { value = 0, to = 0 }
end end
function View:move_towards(t, k, dest, rate, name) function View:move_towards(t, k, dest, rate, name)
@ -109,47 +102,9 @@ function View:get_scrollable_size()
return math.huge return math.huge
end end
---@return number
---@return number x function View:get_h_scrollable_size()
---@return number y return 0
---@return number width
---@return number height
function View:get_scrollbar_track_rect()
local sz = self:get_scrollable_size()
if sz <= self.size.y or sz == math.huge then
return 0, 0, 0, 0
end
local width = style.scrollbar_size
if self.hovered_scrollbar_track or self.dragging_scrollbar then
width = style.expanded_scrollbar_size
end
return
self.position.x + self.size.x - width,
self.position.y,
width,
self.size.y
end
---@return number x
---@return number y
---@return number width
---@return number height
function View:get_scrollbar_rect()
local sz = self:get_scrollable_size()
if sz <= self.size.y or sz == math.huge then
return 0, 0, 0, 0
end
local h = math.max(20, self.size.y * self.size.y / sz)
local width = style.scrollbar_size
if self.hovered_scrollbar_track or self.dragging_scrollbar then
width = style.expanded_scrollbar_size
end
return
self.position.x + self.size.x - width,
self.position.y + self.scroll.y * (self.size.y - h) / (sz - self.size.y),
width,
h
end end
@ -157,16 +112,19 @@ end
---@param y number ---@param y number
---@return boolean ---@return boolean
function View:scrollbar_overlaps_point(x, y) function View:scrollbar_overlaps_point(x, y)
local sx, sy, sw, sh = self:get_scrollbar_rect() return not (not (self.v_scrollbar:overlaps(x, y) or self.h_scrollbar:overlaps(x, y)))
return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh
end end
---@param x number
---@param y number
---@return boolean ---@return boolean
function View:scrollbar_track_overlaps_point(x, y) function View:scrollbar_dragging()
local sx, sy, sw, sh = self:get_scrollbar_track_rect() return self.v_scrollbar.dragging or self.h_scrollbar.dragging
return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh end
---@return boolean
function View:scrollbar_hovering()
return self.v_scrollbar.hovering.track or self.h_scrollbar.hovering.track
end end
@ -176,14 +134,18 @@ end
---@param clicks integer ---@param clicks integer
---return boolean ---return boolean
function View:on_mouse_pressed(button, x, y, clicks) function View:on_mouse_pressed(button, x, y, clicks)
if self:scrollbar_track_overlaps_point(x, y) then if not self.scrollable then return end
if self:scrollbar_overlaps_point(x, y) then local result = self.v_scrollbar:on_mouse_pressed(button, x, y, clicks)
self.dragging_scrollbar = true if result then
else if result ~= true then
local _, _, _, sh = self:get_scrollbar_rect() self.scroll.to.y = result * self:get_scrollable_size()
local ly = (y - self.position.y) - sh / 2 end
local pct = common.clamp(ly / self.size.y, 0, 100) return true
self.scroll.to.y = self:get_scrollable_size() * pct 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()
end end
return true return true
end end
@ -194,7 +156,9 @@ end
---@param x number ---@param x number
---@param y number ---@param y number
function View:on_mouse_released(button, x, y) function View:on_mouse_released(button, x, y)
self.dragging_scrollbar = false if not self.scrollable then return end
self.v_scrollbar:on_mouse_released(button, x, y)
self.h_scrollbar:on_mouse_released(button, x, y)
end end
@ -203,22 +167,41 @@ end
---@param dx number ---@param dx number
---@param dy number ---@param dy number
function View:on_mouse_moved(x, y, dx, dy) function View:on_mouse_moved(x, y, dx, dy)
if self.dragging_scrollbar then if not self.scrollable then return end
local delta = self:get_scrollable_size() / self.size.y * dy local result
self.scroll.to.y = self.scroll.to.y + delta if self.h_scrollbar.dragging then goto skip_v_scrollbar end
if not config.animate_drag_scroll then result = self.v_scrollbar:on_mouse_moved(x, y, dx, dy)
self:clamp_scroll_position() if result then
self.scroll.y = self.scroll.to.y if result ~= true then
self.scroll.to.y = result * self:get_scrollable_size()
if not config.animate_drag_scroll then
self:clamp_scroll_position()
self.scroll.y = self.scroll.to.y
end
end end
-- hide horizontal scrollbar
self.h_scrollbar:on_mouse_left()
return true
end
::skip_v_scrollbar::
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()
if not config.animate_drag_scroll then
self:clamp_scroll_position()
self.scroll.x = self.scroll.to.x
end
end
return true
end end
self.hovered_scrollbar = self:scrollbar_overlaps_point(x, y)
self.hovered_scrollbar_track = self.hovered_scrollbar or self:scrollbar_track_overlaps_point(x, y)
end end
function View:on_mouse_left() function View:on_mouse_left()
self.hovered_scrollbar = false if not self.scrollable then return end
self.hovered_scrollbar_track = false self.v_scrollbar:on_mouse_left()
self.h_scrollbar:on_mouse_left()
end end
@ -236,12 +219,25 @@ function View:on_text_input(text)
-- no-op -- no-op
end end
---@param y number
---@return boolean
function View:on_mouse_wheel(y)
function View:on_ime_text_editing(text, start, length)
-- no-op
end end
---@param y number @Vertical scroll delta; positive is "up"
---@param x number @Horizontal scroll delta; positive is "left"
---@return boolean @Capture event
function View:on_mouse_wheel(y, x)
-- no-op
end
---Can be overriden to listen for scale change events to apply
---any neccesary changes in sizes, padding, etc...
---@param new_scale number
---@param prev_scale number
function View:on_scale_change(new_scale, prev_scale) end
function View:get_content_bounds() function View:get_content_bounds()
local x = self.scroll.x local x = self.scroll.x
local y = self.scroll.y local y = self.scroll.y
@ -261,35 +257,35 @@ end
function View:clamp_scroll_position() function View:clamp_scroll_position()
local max = self:get_scrollable_size() - self.size.y local max = self:get_scrollable_size() - self.size.y
self.scroll.to.y = common.clamp(self.scroll.to.y, 0, max) self.scroll.to.y = common.clamp(self.scroll.to.y, 0, max)
max = self:get_h_scrollable_size() - self.size.x
self.scroll.to.x = common.clamp(self.scroll.to.x, 0, max)
end end
function View:update_scrollbar() function View:update_scrollbar()
local x, y, w, h = self:get_scrollbar_rect() local v_scrollable = self:get_scrollable_size()
self.scrollbar.w.to.thumb = w self.v_scrollbar:set_size(self.position.x, self.position.y, self.size.x, self.size.y, v_scrollable)
self:move_towards(self.scrollbar.w, "thumb", self.scrollbar.w.to.thumb, 0.3, "scroll") self.v_scrollbar:set_percent(self.scroll.y/v_scrollable)
self.scrollbar.x.thumb = x + w - self.scrollbar.w.thumb self.v_scrollbar:update()
self.scrollbar.y.thumb = y
self.scrollbar.h.thumb = h
local x, y, w, h = self:get_scrollbar_track_rect() local h_scrollable = self:get_h_scrollable_size()
self.scrollbar.w.to.track = w self.h_scrollbar:set_size(self.position.x, self.position.y, self.size.x, self.size.y, h_scrollable)
self:move_towards(self.scrollbar.w, "track", self.scrollbar.w.to.track, 0.3, "scroll") self.h_scrollbar:set_percent(self.scroll.x/h_scrollable)
self.scrollbar.x.track = x + w - self.scrollbar.w.track self.h_scrollbar:update()
self.scrollbar.y.track = y
self.scrollbar.h.track = h
-- we use 100 for a smoother transition
self.scrollbar_alpha.to = (self.hovered_scrollbar_track or self.dragging_scrollbar) and 100 or 0
self:move_towards(self.scrollbar_alpha, "value", self.scrollbar_alpha.to, 0.3, "scroll")
end end
function View:update() function View:update()
if self.current_scale ~= SCALE then
self:on_scale_change(SCALE, self.current_scale)
self.current_scale = SCALE
end
self:clamp_scroll_position() self:clamp_scroll_position()
self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3, "scroll") self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3, "scroll")
self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3, "scroll") self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3, "scroll")
if not self.scrollable then return end
self:update_scrollbar() self:update_scrollbar()
end end
@ -302,29 +298,9 @@ function View:draw_background(color)
end end
function View:draw_scrollbar_track()
if not (self.hovered_scrollbar_track or self.dragging_scrollbar)
and self.scrollbar_alpha.value == 0 then
return
end
local color = { table.unpack(style.scrollbar_track) }
color[4] = color[4] * self.scrollbar_alpha.value / 100
renderer.draw_rect(self.scrollbar.x.track, self.scrollbar.y.track,
self.scrollbar.w.track, self.scrollbar.h.track, color)
end
function View:draw_scrollbar_thumb()
local highlight = self.hovered_scrollbar or self.dragging_scrollbar
local color = highlight and style.scrollbar2 or style.scrollbar
renderer.draw_rect(self.scrollbar.x.thumb, self.scrollbar.y.thumb,
self.scrollbar.w.thumb, self.scrollbar.h.thumb, color)
end
function View:draw_scrollbar() function View:draw_scrollbar()
self:draw_scrollbar_track() self.v_scrollbar:draw()
self:draw_scrollbar_thumb() self.h_scrollbar:draw()
end end

Binary file not shown.

View File

@ -588,8 +588,11 @@ function autocomplete.open(on_close)
end end
local av = get_active_view() local av = get_active_view()
last_line, last_col = av.doc:get_selection() if av then
update_suggestions() partial = get_partial_symbol()
last_line, last_col = av.doc:get_selection()
update_suggestions()
end
end end
function autocomplete.close() function autocomplete.close()
@ -645,11 +648,11 @@ command.add(predicate, {
end, end,
["autocomplete:previous"] = function() ["autocomplete:previous"] = function()
suggestions_idx = math.max(suggestions_idx - 1, 1) suggestions_idx = (suggestions_idx - 2) % #suggestions + 1
end, end,
["autocomplete:next"] = function() ["autocomplete:next"] = function()
suggestions_idx = math.min(suggestions_idx + 1, #suggestions) suggestions_idx = (suggestions_idx % #suggestions) + 1
end, end,
["autocomplete:cycle"] = function() ["autocomplete:cycle"] = function()

View File

@ -75,7 +75,9 @@ local function escape_comment_tokens(token)
end end
local function get_comment_patterns(syntax) local function get_comment_patterns(syntax, _loop)
_loop = _loop or 1
if _loop > 5 then return end
if comments_cache[syntax] then if comments_cache[syntax] then
if #comments_cache[syntax] > 0 then if #comments_cache[syntax] > 0 then
return comments_cache[syntax] return comments_cache[syntax]
@ -125,7 +127,7 @@ local function get_comment_patterns(syntax)
elseif pattern.syntax then elseif pattern.syntax then
local subsyntax = type(pattern.syntax) == 'table' and pattern.syntax local subsyntax = type(pattern.syntax) == 'table' and pattern.syntax
or core_syntax.get("file"..pattern.syntax, "") or core_syntax.get("file"..pattern.syntax, "")
local sub_comments = get_comment_patterns(subsyntax) local sub_comments = get_comment_patterns(subsyntax, _loop + 1)
if sub_comments then if sub_comments then
for s=1, #sub_comments do for s=1, #sub_comments do
table.insert(comments, sub_comments[s]) table.insert(comments, sub_comments[s])
@ -190,11 +192,11 @@ local function get_non_empty_lines(syntax, lines)
end end
else else
if comment[3] then if comment[3] then
local start, ending = regex.match( local start, ending = regex.find_offsets(
comment[2], line, 1, regex.ANCHORED comment[2], line, 1, regex.ANCHORED
) )
if start then if start then
if not regex.match( if not regex.find_offsets(
comment[3], line, ending+1, regex.ANCHORED comment[3], line, ending+1, regex.ANCHORED
) )
then then
@ -204,7 +206,7 @@ local function get_non_empty_lines(syntax, lines)
end end
break break
end end
elseif regex.match(comment[2], line, 1, regex.ANCHORED) then elseif regex.find_offsets(comment[2], line, 1, regex.ANCHORED) then
is_comment = true is_comment = true
break break
end end
@ -214,7 +216,7 @@ local function get_non_empty_lines(syntax, lines)
is_comment = true is_comment = true
inside_comment = false inside_comment = false
end_pattern = nil end_pattern = nil
elseif end_regex and regex.match(end_regex, line) then elseif end_regex and regex.find_offsets(end_regex, line) then
is_comment = true is_comment = true
inside_comment = false inside_comment = false
end_regex = nil end_regex = nil

View File

@ -244,7 +244,7 @@ function DocView:draw_line_text(idx, x, y)
local color = base_color local color = base_color
local draw = false local draw = false
if e == #text - 1 then if e >= #text - 1 then
draw = show_trailing draw = show_trailing
color = trailing_color color = trailing_color
elseif s == 1 then elseif s == 1 then
@ -290,14 +290,15 @@ function DocView:draw_line_text(idx, x, y)
local ty = y + self:get_line_text_y_offset() local ty = y + self:get_line_text_y_offset()
local cache = ws_cache[self.doc.highlighter][idx] local cache = ws_cache[self.doc.highlighter][idx]
for i=1,#cache,4 do for i=1,#cache,4 do
local sub = cache[i]
local tx = cache[i + 1] + x local tx = cache[i + 1] + x
local tw = cache[i + 2] local tw = cache[i + 2]
local color = cache[i + 3] if tx <= x2 then
if tx + tw >= x1 then local sub = cache[i]
tx = renderer.draw_text(font, sub, tx, ty, color) local color = cache[i + 3]
if tx + tw >= x1 then
tx = renderer.draw_text(font, sub, tx, ty, color)
end
end end
if tx > x2 then break end
end end
return draw_line_text(self, idx, x, y) return draw_line_text(self, idx, x, y)

View File

@ -42,9 +42,9 @@ syntax.add {
-- blockquote -- blockquote
{ pattern = "^%s*>+%s", type = "string" }, { pattern = "^%s*>+%s", type = "string" },
-- alternative bold italic formats -- alternative bold italic formats
{ pattern = { "%s___", "___%f[%s]" }, type = "markdown_bold_italic" }, { pattern = { "%s___", "___" }, type = "markdown_bold_italic" },
{ pattern = { "%s__", "__%f[%s]" }, type = "markdown_bold" }, { pattern = { "%s__", "__" }, type = "markdown_bold" },
{ pattern = { "%s_[%S]", "_%f[%s]" }, type = "markdown_italic" }, { pattern = { "%s_[%S]", "_" }, type = "markdown_italic" },
-- reference links -- reference links
{ {
pattern = "^%s*%[%^()["..in_squares_match.."]+()%]: ", pattern = "^%s*%[%^()["..in_squares_match.."]+()%]: ",
@ -112,6 +112,7 @@ syntax.add {
{ pattern = { "%$%$", "%$%$", "\\" }, type = "string", syntax = ".tex"}, { pattern = { "%$%$", "%$%$", "\\" }, type = "string", syntax = ".tex"},
{ regex = { "\\$", [[\$|(?=\\*\n)]], "\\" }, type = "string", syntax = ".tex"}, { regex = { "\\$", [[\$|(?=\\*\n)]], "\\" }, type = "string", syntax = ".tex"},
-- code blocks -- code blocks
{ pattern = { "```caddyfile", "```" }, type = "string", syntax = "Caddyfile" },
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" }, { pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
{ pattern = { "```cpp", "```" }, type = "string", syntax = ".cpp" }, { pattern = { "```cpp", "```" }, type = "string", syntax = ".cpp" },
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" }, { pattern = { "```python", "```" }, type = "string", syntax = ".py" },
@ -149,14 +150,15 @@ syntax.add {
{ pattern = { "```", "```" }, type = "string" }, { pattern = { "```", "```" }, type = "string" },
{ pattern = { "``", "``" }, type = "string" }, { pattern = { "``", "``" }, type = "string" },
{ pattern = { "%f[\\`]%`[%S]", "`" }, type = "string" }, { pattern = { "%f[\\`]%`[%S]", "`" }, type = "string" },
-- lines
{ pattern = "^%-%-%-+\n" , type = "comment" },
{ pattern = "^%*%*%*+\n", type = "comment" },
{ pattern = "^___+\n", type = "comment" },
{ pattern = "^===+\n", type = "comment" },
-- strike -- strike
{ pattern = { "~~", "~~" }, type = "keyword2" }, { pattern = { "~~", "~~" }, type = "keyword2" },
-- highlight -- highlight
{ pattern = { "==", "==" }, type = "literal" }, { pattern = { "==", "==" }, type = "literal" },
-- lines
{ pattern = "^%-%-%-+$" , type = "comment" },
{ pattern = "^%*%*%*+$", type = "comment" },
{ pattern = "^___+$", type = "comment" },
-- bold and italic -- bold and italic
{ pattern = { "%*%*%*%S", "%*%*%*" }, type = "markdown_bold_italic" }, { pattern = { "%*%*%*%S", "%*%*%*" }, type = "markdown_bold_italic" },
{ pattern = { "%*%*%S", "%*%*" }, type = "markdown_bold" }, { pattern = { "%*%*%S", "%*%*" }, type = "markdown_bold" },
@ -166,16 +168,16 @@ syntax.add {
type = "markdown_italic" type = "markdown_italic"
}, },
-- alternative bold italic formats -- alternative bold italic formats
{ pattern = "^___[%s%p%w]+___%s" , type = "markdown_bold_italic" }, { pattern = "^___[%s%p%w]+___" , type = "markdown_bold_italic" },
{ pattern = "^__[%s%p%w]+__%s" , type = "markdown_bold" }, { pattern = "^__[%s%p%w]+__" , type = "markdown_bold" },
{ pattern = "^_[%s%p%w]+_%s" , type = "markdown_italic" }, { pattern = "^_[%s%p%w]+_" , type = "markdown_italic" },
-- heading with custom id -- heading with custom id
{ {
pattern = "^#+%s[%w%s%p]+(){()#[%w%-]+()}", pattern = "^#+%s[%w%s%p]+(){()#[%w%-]+()}",
type = { "keyword", "function", "string", "function" } type = { "keyword", "function", "string", "function" }
}, },
-- headings -- headings
{ pattern = "^#+%s.+$", type = "keyword" }, { pattern = "^#+%s.+\n", type = "keyword" },
-- superscript and subscript -- superscript and subscript
{ {
pattern = "%^()%d+()%^", pattern = "%^()%d+()%^",

View File

@ -86,7 +86,7 @@ function DocView:draw_overlay(...)
and and
config.plugins.lineguide.enabled config.plugins.lineguide.enabled
and and
not self:is(CommandView) self:is(DocView)
then then
local line_x = self:get_line_screen_position(1) local line_x = self:get_line_screen_position(1)
local character_width = self:get_font():get_width("n") local character_width = self:get_font():get_width("n")

View File

@ -219,7 +219,7 @@ function LineWrapping.draw_guide(docview)
end end
function LineWrapping.update_docview_breaks(docview) function LineWrapping.update_docview_breaks(docview)
local x,y,w,h = docview:get_scrollbar_rect() local x,y,w,h = docview.v_scrollbar:get_thumb_rect()
local width = (type(config.plugins.linewrapping.width_override) == "function" and config.plugins.linewrapping.width_override(docview)) 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) 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 if (not docview.wrapped_settings or docview.wrapped_settings.width == nil or width ~= docview.wrapped_settings.width) then
@ -355,18 +355,34 @@ function DocView:get_scrollable_size()
return self:get_line_height() * (get_total_wrapped_lines(self) - 1) + self.size.y return self:get_line_height() * (get_total_wrapped_lines(self) - 1) + self.size.y
end end
local old_get_h_scrollable_size = DocView.get_h_scrollable_size
function DocView:get_h_scrollable_size(...)
if self.wrapping_enabled then return 0 end
return old_get_h_scrollable_size(self, ...)
end
local old_new = DocView.new local old_new = DocView.new
function DocView:new(doc) function DocView:new(doc)
old_new(self, doc) old_new(self, doc)
if not open_files[doc] then open_files[doc] = {} end if not open_files[doc] then open_files[doc] = {} end
table.insert(open_files[doc], self) table.insert(open_files[doc], self)
if config.plugins.linewrapping.enable_by_default then if config.plugins.linewrapping.enable_by_default then
self.wrapping_enabled = true
LineWrapping.update_docview_breaks(self) LineWrapping.update_docview_breaks(self)
else
self.wrapping_enabled = false
end end
end end
local old_scroll_to_line = DocView.scroll_to_line
function DocView:scroll_to_line(...)
if self.wrapping_enabled then LineWrapping.update_docview_breaks(self) end
old_scroll_to_line(self, ...)
end
local old_scroll_to_make_visible = DocView.scroll_to_make_visible local old_scroll_to_make_visible = DocView.scroll_to_make_visible
function DocView:scroll_to_make_visible(line, col) function DocView:scroll_to_make_visible(line, col)
if self.wrapping_enabled then LineWrapping.update_docview_breaks(self) end
old_scroll_to_make_visible(self, line, col) old_scroll_to_make_visible(self, line, col)
if self.wrapped_settings then self.scroll.to.x = 0 end if self.wrapped_settings then self.scroll.to.x = 0 end
end end
@ -557,11 +573,13 @@ end
command.add(nil, { command.add(nil, {
["line-wrapping:enable"] = function() ["line-wrapping:enable"] = function()
if core.active_view and core.active_view.doc then if core.active_view and core.active_view.doc then
core.active_view.wrapping_enabled = true
LineWrapping.update_docview_breaks(core.active_view) LineWrapping.update_docview_breaks(core.active_view)
end end
end, end,
["line-wrapping:disable"] = function() ["line-wrapping:disable"] = function()
if core.active_view and core.active_view.doc then if core.active_view and core.active_view.doc then
core.active_view.wrapping_enabled = false
LineWrapping.reconstruct_breaks(core.active_view, core.active_view:get_font(), math.huge) LineWrapping.reconstruct_breaks(core.active_view, core.active_view:get_font(), math.huge)
end end
end, end,

View File

@ -6,7 +6,7 @@ local command = require "core.command"
local style = require "core.style" local style = require "core.style"
local View = require "core.view" local View = require "core.view"
---@class plugins.projectsearch.resultsview : core.view
local ResultsView = View:extend() local ResultsView = View:extend()
ResultsView.context = "session" ResultsView.context = "session"
@ -219,6 +219,10 @@ function ResultsView:draw()
end end
---@param path string
---@param text string
---@param fn fun(line_text:string):...
---@return plugins.projectsearch.resultsview?
local function begin_search(path, text, fn) local function begin_search(path, text, fn)
if text == "" then if text == "" then
core.error("Expected non-empty string") core.error("Expected non-empty string")
@ -226,6 +230,7 @@ local function begin_search(path, text, fn)
end end
local rv = ResultsView(path, text, fn) local rv = ResultsView(path, text, fn)
core.root_view:get_active_node_default():add_view(rv) core.root_view:get_active_node_default():add_view(rv)
return rv
end end
@ -249,6 +254,59 @@ local function normalize_path(path)
return path return path
end end
---@class plugins.projectsearch
local projectsearch = {}
---@type plugins.projectsearch.resultsview
projectsearch.ResultsView = ResultsView
---@param text string
---@param path string
---@param insensitive? boolean
---@return plugins.projectsearch.resultsview?
function projectsearch.search_plain(text, path, insensitive)
if insensitive then text = text:lower() end
return begin_search(path, text, function(line_text)
if insensitive then
return line_text:lower():find(text, nil, true)
else
return line_text:find(text, nil, true)
end
end)
end
---@param text string
---@param path string
---@param insensitive? boolean
---@return plugins.projectsearch.resultsview?
function projectsearch.search_regex(text, path, insensitive)
local re, errmsg
if insensitive then
re, errmsg = regex.compile(text, "i")
else
re, errmsg = regex.compile(text)
end
if not re then core.log("%s", errmsg) return end
return begin_search(path, text, function(line_text)
return regex.cmatch(re, line_text)
end)
end
---@param text string
---@param path string
---@param insensitive? boolean
---@return plugins.projectsearch.resultsview?
function projectsearch.search_fuzzy(text, path, insensitive)
if insensitive then text = text:lower() end
return begin_search(path, text, function(line_text)
if insensitive then
return common.fuzzy_match(line_text:lower(), text) and 1
else
return common.fuzzy_match(line_text, text) and 1
end
end)
end
command.add(nil, { command.add(nil, {
["project-search:find"] = function(path) ["project-search:find"] = function(path)
@ -256,10 +314,7 @@ command.add(nil, {
text = get_selected_text(), text = get_selected_text(),
select_text = true, select_text = true,
submit = function(text) submit = function(text)
text = text:lower() projectsearch.search_plain(text, path, true)
begin_search(path, text, function(line_text)
return line_text:lower():find(text, nil, true)
end)
end end
}) })
end, end,
@ -267,10 +322,7 @@ command.add(nil, {
["project-search:find-regex"] = function(path) ["project-search:find-regex"] = function(path)
core.command_view:enter("Find Regex In " .. (normalize_path(path) or "Project"), { core.command_view:enter("Find Regex In " .. (normalize_path(path) or "Project"), {
submit = function(text) submit = function(text)
local re = regex.compile(text, "i") projectsearch.search_regex(text, path, true)
begin_search(path, text, function(line_text)
return regex.cmatch(re, line_text)
end)
end end
}) })
end, end,
@ -280,9 +332,7 @@ command.add(nil, {
text = get_selected_text(), text = get_selected_text(),
select_text = true, select_text = true,
submit = function(text) submit = function(text)
begin_search(path, text, function(line_text) projectsearch.search_fuzzy(text, path, true)
return common.fuzzy_match(line_text, text) and 1
end)
end end
}) })
end, end,
@ -344,3 +394,6 @@ keymap.add {
["home"] = "project-search:move-to-start-of-doc", ["home"] = "project-search:move-to-start-of-doc",
["end"] = "project-search:move-to-end-of-doc" ["end"] = "project-search:move-to-end-of-doc"
} }
return projectsearch

View File

@ -39,6 +39,11 @@ end
function ToolbarView:toggle_visible() function ToolbarView:toggle_visible()
self.visible = not self.visible self.visible = not self.visible
if self.tooltip then
core.status_view:remove_tooltip()
self.tooltip = false
end
self.hovered_item = nil
end end
function ToolbarView:get_icon_width() function ToolbarView:get_icon_width()
@ -73,6 +78,7 @@ end
function ToolbarView:draw() function ToolbarView:draw()
if not self.visible then return end
self:draw_background(style.background2) self:draw_background(style.background2)
for item, x, y, w, h in self:each_item() do for item, x, y, w, h in self:each_item() do
@ -83,6 +89,7 @@ end
function ToolbarView:on_mouse_pressed(button, x, y, clicks) function ToolbarView:on_mouse_pressed(button, x, y, clicks)
if not self.visible then return end
local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks) local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks)
if caught then return caught end if caught then return caught end
core.set_active_view(core.last_active_view) core.set_active_view(core.last_active_view)
@ -94,6 +101,7 @@ end
function ToolbarView:on_mouse_moved(px, py, ...) function ToolbarView:on_mouse_moved(px, py, ...)
if not self.visible then return end
ToolbarView.super.on_mouse_moved(self, px, py, ...) ToolbarView.super.on_mouse_moved(self, px, py, ...)
self.hovered_item = nil self.hovered_item = nil
local x_min, x_max, y_min, y_max = self.size.x, 0, self.size.y, 0 local x_min, x_max, y_min, y_max = self.size.x, 0, self.size.y, 0

View File

@ -50,20 +50,6 @@ function TreeView:new()
self.item_icon_width = 0 self.item_icon_width = 0
self.item_text_spacing = 0 self.item_text_spacing = 0
self:add_core_hooks()
end
function TreeView:add_core_hooks()
-- When a file or directory is deleted we delete the corresponding cache entry
-- because if the entry is recreated we may use wrong information from cache.
local on_delete = core.on_dirmonitor_delete
core.on_dirmonitor_delete = function(dir, filepath)
local cache = self.cache[dir.name]
if cache then cache[filepath] = nil end
on_delete(dir, filepath)
end
end end
@ -86,7 +72,7 @@ function TreeView:get_cached(dir, item, dirname)
-- used only to identify the entry into the cache. -- used only to identify the entry into the cache.
local cache_name = item.filename .. (item.topdir and ":" or "") local cache_name = item.filename .. (item.topdir and ":" or "")
local t = dir_cache[cache_name] local t = dir_cache[cache_name]
if not t then if not t or t.type ~= item.type then
t = {} t = {}
local basename = common.basename(item.filename) local basename = common.basename(item.filename)
if item.topdir then if item.topdir then
@ -209,10 +195,10 @@ end
function TreeView:on_mouse_moved(px, py, ...) function TreeView:on_mouse_moved(px, py, ...)
if not self.visible then return end if not self.visible then return end
TreeView.super.on_mouse_moved(self, px, py, ...)
self.cursor_pos.x = px self.cursor_pos.x = px
self.cursor_pos.y = py self.cursor_pos.y = py
if self.dragging_scrollbar then if TreeView.super.on_mouse_moved(self, px, py, ...) then
-- mouse movement handled by the View (scrollbar)
self.hovered_item = nil self.hovered_item = nil
return return
end end
@ -728,16 +714,8 @@ command.add(
end end
end end
) )
end end,
})
command.add(function()
if not (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) then return end
if core.root_view.overlapping_node.active_view ~= view then return end
local item = treeitem()
return item ~= nil, item
end, {
["treeview:rename"] = function(item) ["treeview:rename"] = function(item)
local old_filename = item.filename local old_filename = item.filename
local old_abs_filename = item.abs_filename local old_abs_filename = item.abs_filename

View File

@ -1,10 +1,53 @@
-- mod-version:3 -- mod-version:3
local core = require "core" local common = require "core.common"
local config = require "core.config"
local command = require "core.command" local command = require "core.command"
local Doc = require "core.doc" local Doc = require "core.doc"
---@class config.plugins.trimwhitespace
---@field enabled boolean
---@field trim_empty_end_lines boolean
config.plugins.trimwhitespace = common.merge({
enabled = true,
trim_empty_end_lines = false,
config_spec = {
name = "Trim Whitespace",
{
label = "Enabled",
description = "Disable or enable the trimming of white spaces by default.",
path = "enabled",
type = "toggle",
default = true
},
{
label = "Trim Empty End Lines",
description = "Remove any empty new lines at the end of documents.",
path = "trim_empty_end_lines",
type = "toggle",
default = false
}
}
}, config.plugins.trimwhitespace)
local function trim_trailing_whitespace(doc) ---@class plugins.trimwhitespace
local trimwhitespace = {}
---Disable whitespace trimming for a specific document.
---@param doc core.doc
function trimwhitespace.disable(doc)
doc.disable_trim_whitespace = true
end
---Re-enable whitespace trimming if previously disabled.
---@param doc core.doc
function trimwhitespace.enable(doc)
doc.disable_trim_whitespace = nil
end
---Perform whitespace trimming in all lines of a document except the
---line where the caret is currently positioned.
---@param doc core.doc
function trimwhitespace.trim(doc)
local cline, ccol = doc:get_selection() local cline, ccol = doc:get_selection()
for i = 1, #doc.lines do for i = 1, #doc.lines do
local old_text = doc:get_text(i, 1, i, math.huge) local old_text = doc:get_text(i, 1, i, math.huge)
@ -22,16 +65,54 @@ local function trim_trailing_whitespace(doc)
end end
end end
---Removes all empty new lines at the end of the document.
---@param doc core.doc
---@param raw_remove? boolean Perform the removal not registering to undo stack
function trimwhitespace.trim_empty_end_lines(doc, raw_remove)
for _=#doc.lines, 1, -1 do
local l = #doc.lines
if l > 1 and doc.lines[l] == "\n" then
local current_line = doc:get_selection()
if current_line == l then
doc:set_selection(l-1, math.huge, l-1, math.huge)
end
if not raw_remove then
doc:remove(l-1, math.huge, l, math.huge)
else
table.remove(doc.lines, l)
end
else
break
end
end
end
command.add("core.docview", { command.add("core.docview", {
["trim-whitespace:trim-trailing-whitespace"] = function(dv) ["trim-whitespace:trim-trailing-whitespace"] = function(dv)
trim_trailing_whitespace(dv.doc) trimwhitespace.trim(dv.doc)
end,
["trim-whitespace:trim-empty-end-lines"] = function(dv)
trimwhitespace.trim_empty_end_lines(dv.doc)
end, end,
}) })
local save = Doc.save local doc_save = Doc.save
Doc.save = function(self, ...) Doc.save = function(self, ...)
trim_trailing_whitespace(self) if
save(self, ...) config.plugins.trimwhitespace.enabled
and
not self.disable_trim_whitespace
then
trimwhitespace.trim(self)
if config.plugins.trimwhitespace.trim_empty_end_lines then
trimwhitespace.trim_empty_end_lines(self)
end
end
doc_save(self, ...)
end end
return trimwhitespace

View File

@ -92,7 +92,8 @@ local function save_view(view)
return { return {
type = "view", type = "view",
active = (core.active_view == view), active = (core.active_view == view),
module = name module = name,
scroll = { x = view.scroll.to.x, y = view.scroll.to.y, to = { x = view.scroll.to.x, y = view.scroll.to.y } },
} }
end end
end end
@ -162,6 +163,9 @@ local function load_node(node, t)
if t.active_view == i then if t.active_view == i then
active_view = view active_view = view
end end
if not view:is(DocView) then
view.scroll = v.scroll
end
end end
end end
if active_view then if active_view then

66
docs/api/dirmonitor.lua Normal file
View File

@ -0,0 +1,66 @@
---@meta
---
---Functionality that allows to monitor a directory or file for changes
---using the native facilities provided by the current operating system
---for better efficiency and performance.
---@class dirmonitor
dirmonitor = {}
---@alias dirmonitor.callback fun(fd_or_path:integer|string)
---
---Creates a new dirmonitor object.
---
---@return dirmonitor
function dirmonitor.new() end
---
---Monitors a directory or file for changes.
---
---In "multiple" mode you will need to call this method more than once to
---recursively monitor directories and files.
---
---In "single" mode you will only need to call this method for the parent
---directory and every sub directory and files will get automatically monitored.
---
---@param path string
---
---@return integer fd The file descriptor id assigned to the monitored path when
---the mode is "multiple", in "single" mode: 1 for success or -1 on failure.
function dirmonitor:watch(path) end
---
---Stops monitoring a file descriptor in "multiple" mode
---or in "single" mode a directory path.
---
---@param fd_or_path integer | string A file descriptor or path.
function dirmonitor:unwatch(fd_or_path) end
---
---Verify if the resources registered for monitoring have changed, should
---be called periodically to check for changes.
---
---The callback will be called for each file or directory that was:
---edited, removed or added. A file descriptor will be passed to the
---callback in "multiple" mode or a path in "single" mode.
---
---@param callback dirmonitor.callback
---
---@return boolean? changes True when changes were detected.
function dirmonitor:check(callback) end
---
---Get the working mode for the current file system monitoring backend.
---
---"multiple": various file descriptors are needed to recursively monitor a
---directory contents, backends: inotify and kqueue.
---
---"single": a single process takes care of monitoring a path recursively
---so no individual file descriptors are used, backends: win32 and fsevents.
---
---@return "single" | "multiple"
function dirmonitor:mode() end
return dirmonitor

View File

@ -10,7 +10,7 @@ ARGS = {}
ARCH = "Architecture-OperatingSystem" ARCH = "Architecture-OperatingSystem"
---The current operating system. ---The current operating system.
---@type string | "'Windows'" | "'Mac OS X'" | "'Linux'" | "'iOS'" | "'Android'" ---@type string | "Windows" | "Mac OS X" | "Linux" | "iOS" | "Android"
PLATFORM = "Operating System" PLATFORM = "Operating System"
---The current text or ui scale. ---The current text or ui scale.

View File

@ -81,27 +81,27 @@ process.REDIRECT_DISCARD = 3
process.REDIRECT_STDOUT = 4 process.REDIRECT_STDOUT = 4
---@alias process.errortype ---@alias process.errortype
---|>'process.ERROR_PIPE' ---| `process.ERROR_PIPE`
---| 'process.ERROR_WOULDBLOCK' ---| `process.ERROR_WOULDBLOCK`
---| 'process.ERROR_TIMEDOUT' ---| `process.ERROR_TIMEDOUT`
---| 'process.ERROR_INVAL' ---| `process.ERROR_INVAL`
---| 'process.ERROR_NOMEM' ---| `process.ERROR_NOMEM`
---@alias process.streamtype ---@alias process.streamtype
---|>'process.STREAM_STDIN' ---| `process.STREAM_STDIN`
---| 'process.STREAM_STDOUT' ---| `process.STREAM_STDOUT`
---| 'process.STREAM_STDERR' ---| `process.STREAM_STDERR`
---@alias process.waittype ---@alias process.waittype
---|>'process.WAIT_INFINITE' ---| `process.WAIT_INFINITE`
---| 'process.WAIT_DEADLINE' ---| `process.WAIT_DEADLINE`
---@alias process.redirecttype ---@alias process.redirecttype
---|>'process.REDIRECT_DEFAULT' ---| `process.REDIRECT_DEFAULT`
---| 'process.REDIRECT_PIPE' ---| `process.REDIRECT_PIPE`
---| 'process.REDIRECT_PARENT' ---| `process.REDIRECT_PARENT`
---| 'process.REDIRECT_DISCARD' ---| `process.REDIRECT_DISCARD`
---| 'process.REDIRECT_STDOUT' ---| `process.REDIRECT_STDOUT`
--- ---
--- Options that can be passed to process.start() --- Options that can be passed to process.start()
@ -112,7 +112,6 @@ process.REDIRECT_STDOUT = 4
---@field public stdout process.redirecttype ---@field public stdout process.redirecttype
---@field public stderr process.redirecttype ---@field public stderr process.redirecttype
---@field public env table<string, string> ---@field public env table<string, string>
process.options = {}
--- ---
---Create and start a new process ---Create and start a new process
@ -233,3 +232,6 @@ function process:returncode() end
--- ---
---@return boolean ---@return boolean
function process:running() end function process:running() end
return process

View File

@ -31,9 +31,9 @@ regex.NOTEMPTY = 0x00000004
regex.NOTEMPTY_ATSTART = 0x00000008 regex.NOTEMPTY_ATSTART = 0x00000008
---@alias regex.modifiers ---@alias regex.modifiers
---|>'"i"' # Case insesitive matching ---| "i" # Case insesitive matching
---| '"m"' # Multiline matching ---| "m" # Multiline matching
---| '"s"' # Match all characters with dot (.) metacharacter even new lines ---| "s" # Match all characters with dot (.) metacharacter even new lines
--- ---
---Compiles a regular expression pattern that can be used to search in strings. ---Compiles a regular expression pattern that can be used to search in strings.
@ -41,8 +41,8 @@ regex.NOTEMPTY_ATSTART = 0x00000008
---@param pattern string ---@param pattern string
---@param options? regex.modifiers A string of one or more pattern modifiers. ---@param options? regex.modifiers A string of one or more pattern modifiers.
--- ---
---@return regex|string regex Ready to use regular expression object or error ---@return regex? regex Ready to use regular expression object or nil on error.
---message if compiling the pattern failed. ---@return string? error The error message if compiling the pattern failed.
function regex.compile(pattern, options) end function regex.compile(pattern, options) end
--- ---
@ -53,5 +53,42 @@ function regex.compile(pattern, options) end
---@param options? integer A bit field of matching options, eg: ---@param options? integer A bit field of matching options, eg:
---regex.NOTBOL | regex.NOTEMPTY ---regex.NOTBOL | regex.NOTEMPTY
--- ---
---@return table<integer, integer> list List of offsets where a match was found. ---@return integer? ... List of offsets where a match was found.
function regex:cmatch(subject, offset, options) end function regex:cmatch(subject, offset, options) end
---
---Returns an iterator function that, each time it is called, returns the
---next captures from `pattern` over the string subject.
---
---Example:
---```lua
--- s = "hello world hello world"
--- for hello, world in regex.gmatch("(hello)\\s+(world)", s) do
--- print(hello .. " " .. world)
--- end
---```
---
---@param pattern string
---@param subject string
---@param offset? integer
---
---@return fun():string, ...
function regex.gmatch(pattern, subject, offset) end
---
---Replaces the matched pattern globally on the subject with the given
---replacement, supports named captures ((?'name'<pattern>), ${name}) and
---$[1-9][0-9]* substitutions. Raises an error when failing to compile the
---pattern or by a substitution mistake.
---
---@param pattern regex|string
---@param subject string
---@param replacement string
---@param limit? integer Limits the number of substitutions that will be done.
---
---@return string? replaced_subject
---@return integer? total_replacements
function regex.gsub(pattern, subject, replacement, limit) end
return regex

View File

@ -14,19 +14,17 @@ renderer = {}
---@field public g number Green ---@field public g number Green
---@field public b number Blue ---@field public b number Blue
---@field public a number Alpha ---@field public a number Alpha
renderer.color = {}
--- ---
---Represent options that affect a font's rendering. ---Represent options that affect a font's rendering.
---@class renderer.fontoptions ---@class renderer.fontoptions
---@field public antialiasing "'none'" | "'grayscale'" | "'subpixel'" ---@field public antialiasing "none" | "grayscale" | "subpixel"
---@field public hinting "'slight'" | "'none'" | '"full"' ---@field public hinting "slight" | "none" | "full"
-- @field public bold boolean ---@field public bold boolean
-- @field public italic boolean ---@field public italic boolean
-- @field public underline boolean ---@field public underline boolean
-- @field public smoothing boolean ---@field public smoothing boolean
-- @field public strikethrough boolean ---@field public strikethrough boolean
renderer.fontoptions = {}
--- ---
---@class renderer.font ---@class renderer.font
@ -154,3 +152,6 @@ function renderer.draw_rect(x, y, width, height, color) end
--- ---
---@return number x ---@return number x
function renderer.draw_text(font, text, x, y, color) end function renderer.draw_text(font, text, x, y, color) end
return renderer

View File

@ -101,14 +101,14 @@ function string.unext(s, charpos, index) end
---@param s string ---@param s string
---@param idx? integer ---@param idx? integer
---@param substring string ---@param substring string
---return string new_string ---@return string new_string
function string.uinsert(s, idx, substring) end function string.uinsert(s, idx, substring) end
---Equivalent to utf8.remove() ---Equivalent to utf8.remove()
---@param s string ---@param s string
---@param start? integer ---@param start? integer
---@param stop? integer ---@param stop? integer
---return string new_string ---@return string new_string
function string.uremove(s, start, stop) end function string.uremove(s, start, stop) end
---Equivalent to utf8.width() ---Equivalent to utf8.width()
@ -130,12 +130,12 @@ function string.uwidthindex(s, location, ambi_is_double, default_width) end
---Equivalent to utf8.title() ---Equivalent to utf8.title()
---@param s string ---@param s string
---return string new_string ---@return string new_string
function string.utitle(s) end function string.utitle(s) end
---Equivalent to utf8.fold() ---Equivalent to utf8.fold()
---@param s string ---@param s string
---return string new_string ---@return string new_string
function string.ufold(s) end function string.ufold(s) end
---Equivalent to utf8.ncasecmp() ---Equivalent to utf8.ncasecmp()

View File

@ -6,15 +6,15 @@
system = {} system = {}
---@alias system.fileinfotype ---@alias system.fileinfotype
---|>'"file"' # It is a file. ---| "file" # It is a file.
---| '"dir"' # It is a directory. ---| "dir" # It is a directory.
--- ---
---@class system.fileinfo ---@class system.fileinfo
---@field public modified number A timestamp in seconds. ---@field public modified number A timestamp in seconds.
---@field public size number Size in bytes. ---@field public size number Size in bytes.
---@field public type system.fileinfotype Type of file ---@field public type system.fileinfotype Type of file
system.fileinfo = {} ---@field public symlink boolean The directory is a symlink. This field is only set on Linux and on directories.
--- ---
---Core function used to retrieve the current event been triggered by SDL. ---Core function used to retrieve the current event been triggered by SDL.
@ -24,7 +24,7 @@ system.fileinfo = {}
--- ---
---Window events: ---Window events:
--- * "quit" --- * "quit"
--- * "resized" -> width, height --- * "resized" -> width, height (in points)
--- * "exposed" --- * "exposed"
--- * "minimized" --- * "minimized"
--- * "maximized" --- * "maximized"
@ -38,12 +38,18 @@ system.fileinfo = {}
--- * "keypressed" -> key_name --- * "keypressed" -> key_name
--- * "keyreleased" -> key_name --- * "keyreleased" -> key_name
--- * "textinput" -> text --- * "textinput" -> text
--- * "textediting" -> text, start, length
--- ---
---Mouse events: ---Mouse events:
--- * "mousepressed" -> button_name, x, y, amount_of_clicks --- * "mousepressed" -> button_name, x, y, amount_of_clicks
--- * "mousereleased" -> button_name, x, y --- * "mousereleased" -> button_name, x, y
--- * "mousemoved" -> x, y, relative_x, relative_y --- * "mousemoved" -> x, y, relative_x, relative_y
--- * "mousewheel" -> y --- * "mousewheel" -> y, x
---
---Touch events:
--- * "touchpressed" -> x, y, finger_id
--- * "touchreleased" -> x, y, finger_id
--- * "touchmoved" -> x, y, distance_x, distance_y, finger_id
--- ---
---@return string type ---@return string type
---@return any? arg1 ---@return any? arg1
@ -64,7 +70,7 @@ function system.wait_event(timeout) end
--- ---
---Change the cursor type displayed on screen. ---Change the cursor type displayed on screen.
--- ---
---@param type string | "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'" ---@param type string | "arrow" | "ibeam" | "sizeh" | "sizev" | "hand"
function system.set_cursor(type) end function system.set_cursor(type) end
--- ---
@ -74,10 +80,10 @@ function system.set_cursor(type) end
function system.set_window_title(title) end function system.set_window_title(title) end
---@alias system.windowmode ---@alias system.windowmode
---|>'"normal"' ---| "normal"
---| '"minimized"' ---| "minimized"
---| '"maximized"' ---| "maximized"
---| '"fullscreen"' ---| "fullscreen"
--- ---
---Change the window mode. ---Change the window mode.
@ -103,7 +109,7 @@ function system.set_window_bordered(bordered) end
---for custom window management. ---for custom window management.
--- ---
---@param title_height number ---@param title_height number
---@param controls_width number This is for minimize, maximize, close, etc... ---@param controls_width number Width of window controls (maximize,minimize and close buttons, etc).
---@param resize_border number The amount of pixels reserved for resizing ---@param resize_border number The amount of pixels reserved for resizing
function system.set_window_hit_test(title_height, controls_width, resize_border) end function system.set_window_hit_test(title_height, controls_width, resize_border) end
@ -131,6 +137,30 @@ function system.set_window_size(width, height, x, y) end
---@return boolean ---@return boolean
function system.window_has_focus() end function system.window_has_focus() end
---
---Gets the mode of the window.
---
---@return system.windowmode
function system.get_window_mode() end
---
---Sets the position of the IME composition window.
---
---@param x number
---@param y number
---@param width number
---@param height number
function system.set_text_input_rect(x, y, width, height) end
---
---Clears any ongoing composition on the IME
function system.clear_ime() end
---
---Raise the main window and give it input focus.
---Note: may not always be obeyed by the users window manager.
function system.raise_window() end
--- ---
---Opens a message box to display an error message. ---Opens a message box to display an error message.
--- ---
@ -138,6 +168,14 @@ function system.window_has_focus() end
---@param message string ---@param message string
function system.show_fatal_error(title, message) end function system.show_fatal_error(title, message) end
---
---Deletes an empty directory.
---
---@param path string
---@return boolean success True if the operation suceeded, false otherwise
---@return string? message An error message if the operation failed
function system.rmdir(path) end
--- ---
---Change the current directory path which affects relative file operations. ---Change the current directory path which affects relative file operations.
---This function raises an error if the path doesn't exists. ---This function raises an error if the path doesn't exists.
@ -152,6 +190,7 @@ function system.chdir(path) end
---@param directory_path string ---@param directory_path string
--- ---
---@return boolean created True on success or false on failure. ---@return boolean created True on success or false on failure.
---@return string? message The error message if the operation failed.
function system.mkdir(directory_path) end function system.mkdir(directory_path) end
--- ---
@ -168,7 +207,7 @@ function system.list_dir(path) end
--- ---
---@param path string ---@param path string
--- ---
---@return string ---@return string? abspath
function system.absolute_path(path) end function system.absolute_path(path) end
--- ---
@ -180,6 +219,28 @@ function system.absolute_path(path) end
---@return string? message Error message in case of error. ---@return string? message Error message in case of error.
function system.get_file_info(path) end function system.get_file_info(path) end
---@alias system.fstype
---| "ext2/ext3"
---| "nfs"
---| "fuse"
---| "smb"
---| "smb2"
---| "reiserfs"
---| "tmpfs"
---| "ramfs"
---| "ntfs"
---
---Gets the filesystem type of a path.
---Note: This only works on Linux.
---
---@param path string Can be path to a directory or a file
---
---@return system.fstype
function system.get_fs_type(path) end
--- ---
---Retrieve the text currently stored on the clipboard. ---Retrieve the text currently stored on the clipboard.
--- ---
@ -193,7 +254,7 @@ function system.get_clipboard() end
function system.set_clipboard(text) end function system.set_clipboard(text) end
--- ---
---Get the process id of lite-xl it self. ---Get the process id of lite-xl itself.
--- ---
---@return integer ---@return integer
function system.get_process_id() end function system.get_process_id() end
@ -215,7 +276,9 @@ function system.sleep(seconds) end
---Similar to os.execute() but does not return the exit status of the ---Similar to os.execute() but does not return the exit status of the
---executed command and executes the process in a non blocking way by ---executed command and executes the process in a non blocking way by
---forking it to the background. ---forking it to the background.
---Note: Do not use this function, use the Process API instead.
--- ---
---@deprecated
---@param command string The command to execute. ---@param command string The command to execute.
function system.exec(command) end function system.exec(command) end
@ -237,4 +300,25 @@ function system.fuzzy_match(haystack, needle, file) end
--- ---
---@param opacity number A value from 0.0 to 1.0, the lower the value ---@param opacity number A value from 0.0 to 1.0, the lower the value
---the less visible the window will be. ---the less visible the window will be.
---@return boolean success True if the operation suceeded.
function system.set_window_opacity(opacity) end function system.set_window_opacity(opacity) end
---
---Loads a lua native module using the default Lua API or lite-xl native plugin API.
---Note: Never use this function directly.
---
---@param name string the name of the module
---@param path string the path to the shared library file
---@return number nargs the return value of the entrypoint
function system.load_native_plugin(name, path) end
---
---Compares two paths in the order used by TreeView.
---
---@param path1 string
---@param path2 string
---@return boolean compare_result True if path1 < path2
function system.path_compare(path1, path2) end
return system

View File

@ -131,7 +131,7 @@ function utf8extra.next(s, charpos, index) end
---@param s string ---@param s string
---@param idx? integer ---@param idx? integer
---@param substring string ---@param substring string
---return string new_string ---@return string new_string
function utf8extra.insert(s, idx, substring) end function utf8extra.insert(s, idx, substring) end
---Delete a substring in s. If neither start nor stop is given, delete the last ---Delete a substring in s. If neither start nor stop is given, delete the last
@ -141,7 +141,7 @@ function utf8extra.insert(s, idx, substring) end
---@param s string ---@param s string
---@param start? integer ---@param start? integer
---@param stop? integer ---@param stop? integer
---return string new_string ---@return string new_string
function utf8extra.remove(s, start, stop) end function utf8extra.remove(s, start, stop) end
---Calculate the width of UTF-8 string s. if ambi_is_double is given, the ---Calculate the width of UTF-8 string s. if ambi_is_double is given, the
@ -174,14 +174,14 @@ function utf8extra.widthindex(s, location, ambi_is_double, default_width) end
---is a number, it's treat as a code point and return a convert code point ---is a number, it's treat as a code point and return a convert code point
---(number). utf8.lower/utf8.pper has the same extension. ---(number). utf8.lower/utf8.pper has the same extension.
---@param s string ---@param s string
---return string new_string ---@return string new_string
function utf8extra.title(s) end function utf8extra.title(s) end
---Convert UTF-8 string s to folded case, used to compare by ignore case. if s ---Convert UTF-8 string s to folded case, used to compare by ignore case. if s
---is a number, it's treat as a code point and return a convert code point ---is a number, it's treat as a code point and return a convert code point
---(number). utf8.lower/utf8.pper has the same extension. ---(number). utf8.lower/utf8.pper has the same extension.
---@param s string ---@param s string
---return string new_string ---@return string new_string
function utf8extra.fold(s) end function utf8extra.fold(s) end
---Compare a and b without case, -1 means a < b, 0 means a == b and 1 means a > b. ---Compare a and b without case, -1 means a < b, 0 means a == b and 1 means a > b.
@ -189,3 +189,6 @@ function utf8extra.fold(s) end
---@param b string ---@param b string
---@return integer result ---@return integer result
function utf8extra.ncasecmp(a, b) end function utf8extra.ncasecmp(a, b) end
return utf8extra

View File

@ -1,8 +1,8 @@
project('lite-xl', project('lite-xl',
['c'], ['c'],
version : '2.1.0', version : '2.1.1',
license : 'MIT', license : 'MIT',
meson_version : '>= 0.47', meson_version : '>= 0.56',
default_options : [ default_options : [
'c_std=gnu11', 'c_std=gnu11',
'wrap_mode=nofallback' 'wrap_mode=nofallback'
@ -52,6 +52,13 @@ lite_cargs = ['-DSDL_MAIN_HANDLED', '-DPCRE2_STATIC']
if get_option('renderer') or host_machine.system() == 'darwin' if get_option('renderer') or host_machine.system() == 'darwin'
lite_cargs += '-DLITE_USE_SDL_RENDERER' lite_cargs += '-DLITE_USE_SDL_RENDERER'
endif endif
if get_option('arch_tuple') != ''
arch_tuple = get_option('arch_tuple')
else
arch_tuple = '@0@-@1@'.format(target_machine.cpu_family(), target_machine.system())
endif
lite_cargs += '-DLITE_ARCH_TUPLE="@0@"'.format(arch_tuple)
#=============================================================================== #===============================================================================
# Linker Settings # Linker Settings
#=============================================================================== #===============================================================================
@ -82,13 +89,20 @@ if not get_option('source-only')
foreach lua : lua_names foreach lua : lua_names
last_lua = (lua == lua_names[-1] or get_option('wrap_mode') == 'forcefallback') last_lua = (lua == lua_names[-1] or get_option('wrap_mode') == 'forcefallback')
lua_dep = dependency(lua, fallback: last_lua ? ['lua', 'lua_dep'] : [], required : last_lua, lua_dep = dependency(lua, fallback: last_lua ? ['lua', 'lua_dep'] : [], required : false,
version: '>= 5.4', version: '>= 5.4',
default_options: default_fallback_options + ['default_library=static', 'line_editing=false', 'interpreter=false'] default_options: default_fallback_options + ['default_library=static', 'line_editing=false', 'interpreter=false']
) )
if lua_dep.found() if lua_dep.found()
break break
endif endif
if last_lua
# If we could not find lua on the system and fallbacks are disabled
# try the compiler as a last ditch effort, since Lua has no official
# pkg-config support.
lua_dep = cc.find_library('lua', required : true)
endif
endforeach endforeach
pcre2_dep = dependency('libpcre2-8', fallback: ['pcre2', 'libpcre2_8'], pcre2_dep = dependency('libpcre2-8', fallback: ['pcre2', 'libpcre2_8'],

View File

@ -2,4 +2,5 @@ option('bundle', type : 'boolean', value : false, description: 'Build a macOS bu
option('source-only', type : 'boolean', value : false, description: 'Configure source files only, doesn\'t checks for dependencies') option('source-only', type : 'boolean', value : false, description: 'Configure source files only, doesn\'t checks for dependencies')
option('portable', type : 'boolean', value : false, description: 'Portable install') option('portable', type : 'boolean', value : false, description: 'Portable install')
option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer') option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer')
option('dirmonitor_backend', type : 'combo', value : '', choices : ['', 'inotify', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use') 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')

View File

@ -29,6 +29,6 @@
</provides> </provides>
<releases> <releases>
<release version="2.0.1" date="2021-08-28" /> <release version="2.1.1" date="2022-12-29" />
</releases> </releases>
</component> </component>

View File

@ -7,4 +7,4 @@ Icon=lite-xl
Terminal=false Terminal=false
StartupWMClass=lite-xl StartupWMClass=lite-xl
Categories=Development;IDE; Categories=Development;IDE;
MimeType=text/plain; MimeType=text/plain;inode/directory;

View File

@ -4,7 +4,7 @@
The lite_xl plugin API is quite simple. Any shared library can be a plugin file, so long The lite_xl plugin API is quite simple. Any shared library can be a plugin file, so long
as it has an entrypoint that looks like the following, where xxxxx is the plugin name: as it has an entrypoint that looks like the following, where xxxxx is the plugin name:
#include "lite_xl_plugin_api.h" #include "lite_xl_plugin_api.h"
int lua_open_lite_xl_xxxxx(lua_State* L, void* XL) { int luaopen_lite_xl_xxxxx(lua_State* L, void* XL) {
lite_xl_plugin_init(XL); lite_xl_plugin_init(XL);
... ...
return 1; return 1;
@ -12,6 +12,11 @@ int lua_open_lite_xl_xxxxx(lua_State* L, void* XL) {
In linux, to compile this file, you'd do: 'gcc -o xxxxx.so -shared xxxxx.c'. Simple! In linux, to compile this file, you'd do: 'gcc -o xxxxx.so -shared xxxxx.c'. Simple!
Due to the way the API is structured, you *should not* link or include lua libraries. Due to the way the API is structured, you *should not* link or include lua libraries.
This file was automatically generated. DO NOT MODIFY DIRECTLY. This file was automatically generated. DO NOT MODIFY DIRECTLY.
UNLESS you're us, and you had to modify this file manually to get it ready for 2.1.
Go figure.
**/ **/
@ -231,9 +236,6 @@ static int (*lua_absindex) (lua_State *L, int idx);
static int (*lua_gettop) (lua_State *L); static int (*lua_gettop) (lua_State *L);
static void (*lua_settop) (lua_State *L, int idx); static void (*lua_settop) (lua_State *L, int idx);
static void (*lua_pushvalue) (lua_State *L, int idx); static void (*lua_pushvalue) (lua_State *L, int idx);
static void (*lua_remove) (lua_State *L, int idx);
static void (*lua_insert) (lua_State *L, int idx);
static void (*lua_replace) (lua_State *L, int idx);
static void (*lua_copy) (lua_State *L, int fromidx, int toidx); static void (*lua_copy) (lua_State *L, int fromidx, int toidx);
static int (*lua_checkstack) (lua_State *L, int sz); static int (*lua_checkstack) (lua_State *L, int sz);
static void (*lua_xmove) (lua_State *from, lua_State *to, int n); static void (*lua_xmove) (lua_State *from, lua_State *to, int n);
@ -276,8 +278,10 @@ static void (*lua_rawgeti) (lua_State *L, int idx, int n);
static void (*lua_rawgetp) (lua_State *L, int idx, const void *p); static void (*lua_rawgetp) (lua_State *L, int idx, const void *p);
static void (*lua_createtable) (lua_State *L, int narr, int nrec); static void (*lua_createtable) (lua_State *L, int narr, int nrec);
static void *(*lua_newuserdata) (lua_State *L, size_t sz); static void *(*lua_newuserdata) (lua_State *L, size_t sz);
static void *(*lua_newuserdatauv) (lua_State *L, size_t sz, int nuvalue);
static int (*lua_getmetatable) (lua_State *L, int objindex); static int (*lua_getmetatable) (lua_State *L, int objindex);
static void (*lua_getuservalue) (lua_State *L, int idx); static void (*lua_getuservalue) (lua_State *L, int idx);
static void (*lua_getiuservalue) (lua_State *L, int idx, int n);
static void (*lua_setglobal) (lua_State *L, const char *var); static void (*lua_setglobal) (lua_State *L, const char *var);
static void (*lua_settable) (lua_State *L, int idx); static void (*lua_settable) (lua_State *L, int idx);
static void (*lua_setfield) (lua_State *L, int idx, const char *k); static void (*lua_setfield) (lua_State *L, int idx, const char *k);
@ -286,11 +290,12 @@ static void (*lua_rawseti) (lua_State *L, int idx, int n);
static void (*lua_rawsetp) (lua_State *L, int idx, const void *p); static void (*lua_rawsetp) (lua_State *L, int idx, const void *p);
static int (*lua_setmetatable) (lua_State *L, int objindex); static int (*lua_setmetatable) (lua_State *L, int objindex);
static void (*lua_setuservalue) (lua_State *L, int idx); static void (*lua_setuservalue) (lua_State *L, int idx);
static void (*lua_setiuservalue) (lua_State *L, int idx, int n);
static void (*lua_callk) (lua_State *L, int nargs, int nresults, int ctx, lua_CFunction k); static void (*lua_callk) (lua_State *L, int nargs, int nresults, int ctx, lua_CFunction k);
static int (*lua_getctx) (lua_State *L, int *ctx); static int (*lua_getctx) (lua_State *L, int *ctx);
static int (*lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k); static int (*lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k);
static int (*lua_load) (lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode); static int (*lua_load) (lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode);
static int (*lua_dump) (lua_State *L, lua_Writer writer, void *data); static int (*lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip);
static int (*lua_yieldk) (lua_State *L, int nresults, int ctx, lua_CFunction k); static int (*lua_yieldk) (lua_State *L, int nresults, int ctx, lua_CFunction k);
static int (*lua_resume) (lua_State *L, lua_State *from, int narg); static int (*lua_resume) (lua_State *L, lua_State *from, int narg);
static int (*lua_status) (lua_State *L); static int (*lua_status) (lua_State *L);
@ -391,6 +396,9 @@ static int (*lua_gethookcount) (lua_State *L);
#define lua_pushliteral(L, s) lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1) #define lua_pushliteral(L, s) lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1)
#define lua_pushglobaltable(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) #define lua_pushglobaltable(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)
#define lua_tostring(L,i) lua_tolstring(L, (i), NULL) #define lua_tostring(L,i) lua_tolstring(L, (i), NULL)
#define lua_insert(L,idx) lua_rotate(L, (idx), 1)
#define lua_replace(L,idx) (lua_copy(L, -1, (idx)), lua_pop(L, 1))
#define lua_remove(L,idx) (lua_rotate(L, (idx), -1), lua_pop(L, 1))
#define LUA_HOOKCALL 0 #define LUA_HOOKCALL 0
#define LUA_HOOKRET 1 #define LUA_HOOKRET 1
#define LUA_HOOKLINE 2 #define LUA_HOOKLINE 2
@ -409,9 +417,6 @@ static int __lite_xl_fallback_lua_absindex (lua_State *L, int idx) { fputs("war
static int __lite_xl_fallback_lua_gettop (lua_State *L) { fputs("warning: lua_gettop is a stub", stderr); } static int __lite_xl_fallback_lua_gettop (lua_State *L) { fputs("warning: lua_gettop is a stub", stderr); }
static void __lite_xl_fallback_lua_settop (lua_State *L, int idx) { fputs("warning: lua_settop is a stub", stderr); } static void __lite_xl_fallback_lua_settop (lua_State *L, int idx) { fputs("warning: lua_settop is a stub", stderr); }
static void __lite_xl_fallback_lua_pushvalue (lua_State *L, int idx) { fputs("warning: lua_pushvalue is a stub", stderr); } static void __lite_xl_fallback_lua_pushvalue (lua_State *L, int idx) { fputs("warning: lua_pushvalue is a stub", stderr); }
static void __lite_xl_fallback_lua_remove (lua_State *L, int idx) { fputs("warning: lua_remove is a stub", stderr); }
static void __lite_xl_fallback_lua_insert (lua_State *L, int idx) { fputs("warning: lua_insert is a stub", stderr); }
static void __lite_xl_fallback_lua_replace (lua_State *L, int idx) { fputs("warning: lua_replace is a stub", stderr); }
static void __lite_xl_fallback_lua_copy (lua_State *L, int fromidx, int toidx) { fputs("warning: lua_copy is a stub", stderr); } static void __lite_xl_fallback_lua_copy (lua_State *L, int fromidx, int toidx) { fputs("warning: lua_copy is a stub", stderr); }
static int __lite_xl_fallback_lua_checkstack (lua_State *L, int sz) { fputs("warning: lua_checkstack is a stub", stderr); } static int __lite_xl_fallback_lua_checkstack (lua_State *L, int sz) { fputs("warning: lua_checkstack is a stub", stderr); }
static void __lite_xl_fallback_lua_xmove (lua_State *from, lua_State *to, int n) { fputs("warning: lua_xmove is a stub", stderr); } static void __lite_xl_fallback_lua_xmove (lua_State *from, lua_State *to, int n) { fputs("warning: lua_xmove is a stub", stderr); }
@ -454,8 +459,10 @@ static void __lite_xl_fallback_lua_rawgeti (lua_State *L, int idx, int n) { fpu
static void __lite_xl_fallback_lua_rawgetp (lua_State *L, int idx, const void *p) { fputs("warning: lua_rawgetp is a stub", stderr); } static void __lite_xl_fallback_lua_rawgetp (lua_State *L, int idx, const void *p) { fputs("warning: lua_rawgetp is a stub", stderr); }
static void __lite_xl_fallback_lua_createtable (lua_State *L, int narr, int nrec) { fputs("warning: lua_createtable is a stub", stderr); } static void __lite_xl_fallback_lua_createtable (lua_State *L, int narr, int nrec) { fputs("warning: lua_createtable is a stub", stderr); }
static void * __lite_xl_fallback_lua_newuserdata (lua_State *L, size_t sz) { fputs("warning: lua_newuserdata is a stub", stderr); } static void * __lite_xl_fallback_lua_newuserdata (lua_State *L, size_t sz) { fputs("warning: lua_newuserdata is a stub", stderr); }
static void * __lite_xl_fallback_lua_newuserdatauv (lua_State *L, size_t sz, int nuvalue) { fputs("warning: lua_newuserdatauv is a stub", stderr); }
static int __lite_xl_fallback_lua_getmetatable (lua_State *L, int objindex) { fputs("warning: lua_getmetatable is a stub", stderr); } static int __lite_xl_fallback_lua_getmetatable (lua_State *L, int objindex) { fputs("warning: lua_getmetatable is a stub", stderr); }
static void __lite_xl_fallback_lua_getuservalue (lua_State *L, int idx) { fputs("warning: lua_getuservalue is a stub", stderr); } static void __lite_xl_fallback_lua_getuservalue (lua_State *L, int idx) { fputs("warning: lua_getuservalue is a stub", stderr); }
static void __lite_xl_fallback_lua_getiuservalue (lua_State *L, int idx, int n) { fputs("warning: lua_getiuservalue is a stub", stderr); }
static void __lite_xl_fallback_lua_setglobal (lua_State *L, const char *var) { fputs("warning: lua_setglobal is a stub", stderr); } static void __lite_xl_fallback_lua_setglobal (lua_State *L, const char *var) { fputs("warning: lua_setglobal is a stub", stderr); }
static void __lite_xl_fallback_lua_settable (lua_State *L, int idx) { fputs("warning: lua_settable is a stub", stderr); } static void __lite_xl_fallback_lua_settable (lua_State *L, int idx) { fputs("warning: lua_settable is a stub", stderr); }
static void __lite_xl_fallback_lua_setfield (lua_State *L, int idx, const char *k) { fputs("warning: lua_setfield is a stub", stderr); } static void __lite_xl_fallback_lua_setfield (lua_State *L, int idx, const char *k) { fputs("warning: lua_setfield is a stub", stderr); }
@ -464,11 +471,12 @@ static void __lite_xl_fallback_lua_rawseti (lua_State *L, int idx, int n) { fpu
static void __lite_xl_fallback_lua_rawsetp (lua_State *L, int idx, const void *p) { fputs("warning: lua_rawsetp is a stub", stderr); } static void __lite_xl_fallback_lua_rawsetp (lua_State *L, int idx, const void *p) { fputs("warning: lua_rawsetp is a stub", stderr); }
static int __lite_xl_fallback_lua_setmetatable (lua_State *L, int objindex) { fputs("warning: lua_setmetatable is a stub", stderr); } static int __lite_xl_fallback_lua_setmetatable (lua_State *L, int objindex) { fputs("warning: lua_setmetatable is a stub", stderr); }
static void __lite_xl_fallback_lua_setuservalue (lua_State *L, int idx) { fputs("warning: lua_setuservalue is a stub", stderr); } static void __lite_xl_fallback_lua_setuservalue (lua_State *L, int idx) { fputs("warning: lua_setuservalue is a stub", stderr); }
static void __lite_xl_fallback_lua_setiuservalue (lua_State *L, int idx, int n) { fputs("warning: lua_setiuservalue is a stub", stderr); }
static void __lite_xl_fallback_lua_callk (lua_State *L, int nargs, int nresults, int ctx, lua_CFunction k) { fputs("warning: lua_callk is a stub", stderr); } static void __lite_xl_fallback_lua_callk (lua_State *L, int nargs, int nresults, int ctx, lua_CFunction k) { fputs("warning: lua_callk is a stub", stderr); }
static int __lite_xl_fallback_lua_getctx (lua_State *L, int *ctx) { fputs("warning: lua_getctx is a stub", stderr); } static int __lite_xl_fallback_lua_getctx (lua_State *L, int *ctx) { fputs("warning: lua_getctx is a stub", stderr); }
static int __lite_xl_fallback_lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k) { fputs("warning: lua_pcallk is a stub", stderr); } static int __lite_xl_fallback_lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k) { fputs("warning: lua_pcallk is a stub", stderr); }
static int __lite_xl_fallback_lua_load (lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode) { fputs("warning: lua_load is a stub", stderr); } static int __lite_xl_fallback_lua_load (lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode) { fputs("warning: lua_load is a stub", stderr); }
static int __lite_xl_fallback_lua_dump (lua_State *L, lua_Writer writer, void *data) { fputs("warning: lua_dump is a stub", stderr); } static int __lite_xl_fallback_lua_dump (lua_State *L, lua_Writer writer, void *data, int strip) { fputs("warning: lua_dump is a stub", stderr); }
static int __lite_xl_fallback_lua_yieldk (lua_State *L, int nresults, int ctx, lua_CFunction k) { fputs("warning: lua_yieldk is a stub", stderr); } static int __lite_xl_fallback_lua_yieldk (lua_State *L, int nresults, int ctx, lua_CFunction k) { fputs("warning: lua_yieldk is a stub", stderr); }
static int __lite_xl_fallback_lua_resume (lua_State *L, lua_State *from, int narg) { fputs("warning: lua_resume is a stub", stderr); } static int __lite_xl_fallback_lua_resume (lua_State *L, lua_State *from, int narg) { fputs("warning: lua_resume is a stub", stderr); }
static int __lite_xl_fallback_lua_status (lua_State *L) { fputs("warning: lua_status is a stub", stderr); } static int __lite_xl_fallback_lua_status (lua_State *L) { fputs("warning: lua_status is a stub", stderr); }
@ -492,6 +500,7 @@ static lua_Hook __lite_xl_fallback_lua_gethook (lua_State *L) { fputs("warning:
static int __lite_xl_fallback_lua_gethookmask (lua_State *L) { fputs("warning: lua_gethookmask is a stub", stderr); } static int __lite_xl_fallback_lua_gethookmask (lua_State *L) { fputs("warning: lua_gethookmask is a stub", stderr); }
static int __lite_xl_fallback_lua_gethookcount (lua_State *L) { fputs("warning: lua_gethookcount is a stub", stderr); } static int __lite_xl_fallback_lua_gethookcount (lua_State *L) { fputs("warning: lua_gethookcount is a stub", stderr); }
/** lauxlib.h **/ /** lauxlib.h **/
typedef struct luaL_Reg { typedef struct luaL_Reg {
@ -531,6 +540,7 @@ static void *(*luaL_testudata) (lua_State *L, int ud, const char *tname);
static void *(*luaL_checkudata) (lua_State *L, int ud, const char *tname); static void *(*luaL_checkudata) (lua_State *L, int ud, const char *tname);
static void (*luaL_where) (lua_State *L, int lvl); static void (*luaL_where) (lua_State *L, int lvl);
static int (*luaL_error) (lua_State *L, const char *fmt, ...); static int (*luaL_error) (lua_State *L, const char *fmt, ...);
static int (*luaL_typeerror) (lua_State *L, int narg, const char *tname);
static int (*luaL_checkoption) (lua_State *L, int narg, const char *def, const char *const lst[]); static int (*luaL_checkoption) (lua_State *L, int narg, const char *def, const char *const lst[]);
static int (*luaL_fileresult) (lua_State *L, int stat, const char *fname); static int (*luaL_fileresult) (lua_State *L, int stat, const char *fname);
static int (*luaL_execresult) (lua_State *L, int stat); static int (*luaL_execresult) (lua_State *L, int stat);
@ -554,6 +564,7 @@ static void (*luaL_addvalue) (luaL_Buffer *B);
static void (*luaL_pushresult) (luaL_Buffer *B); static void (*luaL_pushresult) (luaL_Buffer *B);
static void (*luaL_pushresultsize) (luaL_Buffer *B, size_t sz); static void (*luaL_pushresultsize) (luaL_Buffer *B, size_t sz);
static char *(*luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz); static char *(*luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz);
static void (*luaL_openlibs) (lua_State *L);
#define lauxlib_h #define lauxlib_h
#define LUA_ERRFILE (LUA_ERRERR+1) #define LUA_ERRFILE (LUA_ERRERR+1)
#define luaL_checkversion(L) luaL_checkversion_(L, LUA_VERSION_NUM) #define luaL_checkversion(L) luaL_checkversion_(L, LUA_VERSION_NUM)
@ -601,6 +612,7 @@ static void * __lite_xl_fallback_luaL_testudata (lua_State *L, int ud, const cha
static void * __lite_xl_fallback_luaL_checkudata (lua_State *L, int ud, const char *tname) { fputs("warning: luaL_checkudata is a stub", stderr); } static void * __lite_xl_fallback_luaL_checkudata (lua_State *L, int ud, const char *tname) { fputs("warning: luaL_checkudata is a stub", stderr); }
static void __lite_xl_fallback_luaL_where (lua_State *L, int lvl) { fputs("warning: luaL_where is a stub", stderr); } static void __lite_xl_fallback_luaL_where (lua_State *L, int lvl) { fputs("warning: luaL_where is a stub", stderr); }
static int __lite_xl_fallback_luaL_error (lua_State *L, const char *fmt, ...) { fputs("warning: luaL_error is a stub", stderr); } static int __lite_xl_fallback_luaL_error (lua_State *L, const char *fmt, ...) { fputs("warning: luaL_error is a stub", stderr); }
static int __lite_xl_fallback_luaL_typeerror (lua_State *L, int narg, const char *tname) { fputs("warning: luaL_typeerror is a stub", stderr); }
static int __lite_xl_fallback_luaL_checkoption (lua_State *L, int narg, const char *def, const char *const lst[]) { fputs("warning: luaL_checkoption is a stub", stderr); } static int __lite_xl_fallback_luaL_checkoption (lua_State *L, int narg, const char *def, const char *const lst[]) { fputs("warning: luaL_checkoption is a stub", stderr); }
static int __lite_xl_fallback_luaL_fileresult (lua_State *L, int stat, const char *fname) { fputs("warning: luaL_fileresult is a stub", stderr); } static int __lite_xl_fallback_luaL_fileresult (lua_State *L, int stat, const char *fname) { fputs("warning: luaL_fileresult is a stub", stderr); }
static int __lite_xl_fallback_luaL_execresult (lua_State *L, int stat) { fputs("warning: luaL_execresult is a stub", stderr); } static int __lite_xl_fallback_luaL_execresult (lua_State *L, int stat) { fputs("warning: luaL_execresult is a stub", stderr); }
@ -624,6 +636,7 @@ static void __lite_xl_fallback_luaL_addvalue (luaL_Buffer *B) { fputs("warning:
static void __lite_xl_fallback_luaL_pushresult (luaL_Buffer *B) { fputs("warning: luaL_pushresult is a stub", stderr); } static void __lite_xl_fallback_luaL_pushresult (luaL_Buffer *B) { fputs("warning: luaL_pushresult is a stub", stderr); }
static void __lite_xl_fallback_luaL_pushresultsize (luaL_Buffer *B, size_t sz) { fputs("warning: luaL_pushresultsize is a stub", stderr); } static void __lite_xl_fallback_luaL_pushresultsize (luaL_Buffer *B, size_t sz) { fputs("warning: luaL_pushresultsize is a stub", stderr); }
static char * __lite_xl_fallback_luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz) { fputs("warning: luaL_buffinitsize is a stub", stderr); } static char * __lite_xl_fallback_luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz) { fputs("warning: luaL_buffinitsize is a stub", stderr); }
static void __lite_xl_fallback_luaL_openlibs (lua_State *L) { fputs("warning: luaL_openlibs is a stub", stderr); }
#define IMPORT_SYMBOL(name, ret, ...) name = (name = (ret (*) (__VA_ARGS__)) symbol(#name), name == NULL ? &__lite_xl_fallback_##name : name) #define IMPORT_SYMBOL(name, ret, ...) name = (name = (ret (*) (__VA_ARGS__)) symbol(#name), name == NULL ? &__lite_xl_fallback_##name : name)
static void lite_xl_plugin_init(void *XL) { static void lite_xl_plugin_init(void *XL) {
@ -637,9 +650,6 @@ static void lite_xl_plugin_init(void *XL) {
IMPORT_SYMBOL(lua_gettop, int , lua_State *L); IMPORT_SYMBOL(lua_gettop, int , lua_State *L);
IMPORT_SYMBOL(lua_settop, void , lua_State *L, int idx); IMPORT_SYMBOL(lua_settop, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_pushvalue, void , lua_State *L, int idx); IMPORT_SYMBOL(lua_pushvalue, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_remove, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_insert, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_replace, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_copy, void , lua_State *L, int fromidx, int toidx); IMPORT_SYMBOL(lua_copy, void , lua_State *L, int fromidx, int toidx);
IMPORT_SYMBOL(lua_checkstack, int , lua_State *L, int sz); IMPORT_SYMBOL(lua_checkstack, int , lua_State *L, int sz);
IMPORT_SYMBOL(lua_xmove, void , lua_State *from, lua_State *to, int n); IMPORT_SYMBOL(lua_xmove, void , lua_State *from, lua_State *to, int n);
@ -682,8 +692,10 @@ static void lite_xl_plugin_init(void *XL) {
IMPORT_SYMBOL(lua_rawgetp, void , lua_State *L, int idx, const void *p); IMPORT_SYMBOL(lua_rawgetp, void , lua_State *L, int idx, const void *p);
IMPORT_SYMBOL(lua_createtable, void , lua_State *L, int narr, int nrec); IMPORT_SYMBOL(lua_createtable, void , lua_State *L, int narr, int nrec);
IMPORT_SYMBOL(lua_newuserdata, void *, lua_State *L, size_t sz); IMPORT_SYMBOL(lua_newuserdata, void *, lua_State *L, size_t sz);
IMPORT_SYMBOL(lua_newuserdatauv, void *, lua_State *L, size_t sz, int nuvalue);
IMPORT_SYMBOL(lua_getmetatable, int , lua_State *L, int objindex); IMPORT_SYMBOL(lua_getmetatable, int , lua_State *L, int objindex);
IMPORT_SYMBOL(lua_getuservalue, void , lua_State *L, int idx); IMPORT_SYMBOL(lua_getuservalue, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_getiuservalue, void , lua_State *L, int idx, int n);
IMPORT_SYMBOL(lua_setglobal, void , lua_State *L, const char *var); IMPORT_SYMBOL(lua_setglobal, void , lua_State *L, const char *var);
IMPORT_SYMBOL(lua_settable, void , lua_State *L, int idx); IMPORT_SYMBOL(lua_settable, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_setfield, void , lua_State *L, int idx, const char *k); IMPORT_SYMBOL(lua_setfield, void , lua_State *L, int idx, const char *k);
@ -692,11 +704,12 @@ static void lite_xl_plugin_init(void *XL) {
IMPORT_SYMBOL(lua_rawsetp, void , lua_State *L, int idx, const void *p); IMPORT_SYMBOL(lua_rawsetp, void , lua_State *L, int idx, const void *p);
IMPORT_SYMBOL(lua_setmetatable, int , lua_State *L, int objindex); IMPORT_SYMBOL(lua_setmetatable, int , lua_State *L, int objindex);
IMPORT_SYMBOL(lua_setuservalue, void , lua_State *L, int idx); IMPORT_SYMBOL(lua_setuservalue, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_setiuservalue, void , lua_State *L, int idx, int n);
IMPORT_SYMBOL(lua_callk, void , lua_State *L, int nargs, int nresults, int ctx, lua_CFunction k); IMPORT_SYMBOL(lua_callk, void , lua_State *L, int nargs, int nresults, int ctx, lua_CFunction k);
IMPORT_SYMBOL(lua_getctx, int , lua_State *L, int *ctx); IMPORT_SYMBOL(lua_getctx, int , lua_State *L, int *ctx);
IMPORT_SYMBOL(lua_pcallk, int , lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k); IMPORT_SYMBOL(lua_pcallk, int , lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k);
IMPORT_SYMBOL(lua_load, int , lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode); IMPORT_SYMBOL(lua_load, int , lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode);
IMPORT_SYMBOL(lua_dump, int , lua_State *L, lua_Writer writer, void *data); IMPORT_SYMBOL(lua_dump, int , lua_State *L, lua_Writer writer, void *data, int strip);
IMPORT_SYMBOL(lua_yieldk, int , lua_State *L, int nresults, int ctx, lua_CFunction k); IMPORT_SYMBOL(lua_yieldk, int , lua_State *L, int nresults, int ctx, lua_CFunction k);
IMPORT_SYMBOL(lua_resume, int , lua_State *L, lua_State *from, int narg); IMPORT_SYMBOL(lua_resume, int , lua_State *L, lua_State *from, int narg);
IMPORT_SYMBOL(lua_status, int , lua_State *L); IMPORT_SYMBOL(lua_status, int , lua_State *L);
@ -741,6 +754,7 @@ static void lite_xl_plugin_init(void *XL) {
IMPORT_SYMBOL(luaL_checkudata, void *, lua_State *L, int ud, const char *tname); IMPORT_SYMBOL(luaL_checkudata, void *, lua_State *L, int ud, const char *tname);
IMPORT_SYMBOL(luaL_where, void , lua_State *L, int lvl); IMPORT_SYMBOL(luaL_where, void , lua_State *L, int lvl);
IMPORT_SYMBOL(luaL_error, int , lua_State *L, const char *fmt, ...); IMPORT_SYMBOL(luaL_error, int , lua_State *L, const char *fmt, ...);
IMPORT_SYMBOL(luaL_typeerror, int , lua_State *L, int narg, const char *tname);
IMPORT_SYMBOL(luaL_checkoption, int , lua_State *L, int narg, const char *def, const char *const lst[]); IMPORT_SYMBOL(luaL_checkoption, int , lua_State *L, int narg, const char *def, const char *const lst[]);
IMPORT_SYMBOL(luaL_fileresult, int , lua_State *L, int stat, const char *fname); IMPORT_SYMBOL(luaL_fileresult, int , lua_State *L, int stat, const char *fname);
IMPORT_SYMBOL(luaL_execresult, int , lua_State *L, int stat); IMPORT_SYMBOL(luaL_execresult, int , lua_State *L, int stat);
@ -764,5 +778,6 @@ static void lite_xl_plugin_init(void *XL) {
IMPORT_SYMBOL(luaL_pushresult, void , luaL_Buffer *B); IMPORT_SYMBOL(luaL_pushresult, void , luaL_Buffer *B);
IMPORT_SYMBOL(luaL_pushresultsize, void , luaL_Buffer *B, size_t sz); IMPORT_SYMBOL(luaL_pushresultsize, void , luaL_Buffer *B, size_t sz);
IMPORT_SYMBOL(luaL_buffinitsize, char *, lua_State *L, luaL_Buffer *B, size_t sz); IMPORT_SYMBOL(luaL_buffinitsize, char *, lua_State *L, luaL_Buffer *B, size_t sz);
IMPORT_SYMBOL(luaL_openlibs, void, lua_State* L);
} }
#endif #endif

View File

@ -27,7 +27,7 @@
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>@PROJECT_VERSION@</string> <string>@PROJECT_VERSION@</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>© 2019-2021 Francesco Abbate</string> <string>© 2019-2022 Lite XL Team</string>
</dict> </dict>
</plist> </plist>

View File

@ -0,0 +1,24 @@
[host_machine]
system = 'darwin'
cpu_family = 'aarch64'
cpu = 'arm64'
endian = 'little'
[binaries]
c = ['clang']
cpp = ['clang++']
objc = ['clang']
objcpp = ['clang++']
ar = ['ar']
strip = ['strip']
pkgconfig = ['pkg-config']
[built-in options]
c_args = ['-arch', 'arm64']
cpp_args = ['-stdlib=libc++', '-arch', 'arm64']
objc_args = ['-arch', 'arm64']
objcpp_args = ['-stdlib=libc++', '-arch', 'arm64']
c_link_args = ['-arch', 'arm64']
cpp_link_args = ['-arch', 'arm64']
objc_link_args = ['-arch', 'arm64']
objcpp_link_args = ['-arch', 'arm64']

View File

@ -1,22 +1,21 @@
diff -ruN lua-5.4.3/meson.build newlua/meson.build diff -ruN lua-5.4.4/meson.build lua-5.4.4-mod/meson.build
--- lua-5.4.3/meson.build 2022-05-29 21:04:17.850449500 +0800 --- lua-5.4.4/meson.build 2022-11-16 10:33:38.424383300 +0800
+++ newlua/meson.build 2022-06-10 19:23:55.685139800 +0800 +++ lua-5.4.4-mod/meson.build 2022-11-16 09:40:57.697918000 +0800
@@ -82,6 +82,7 @@ @@ -85,6 +85,7 @@
'src/lutf8lib.c', 'src/lutf8lib.c',
'src/lvm.c', 'src/lvm.c',
'src/lzio.c', 'src/lzio.c',
+ 'src/utf8_wrappers.c', + 'src/utf8_wrappers.c',
dependencies: lua_lib_deps, dependencies: lua_lib_deps,
override_options: project_options, version: meson.project_version(),
implicit_include_directories: false, soversion: lua_versions[0] + '.' + lua_versions[1],
Binary files lua-5.4.3/src/lua54.dll and newlua/src/lua54.dll differ diff -ruN lua-5.4.4/src/luaconf.h lua-5.4.4-mod/src/luaconf.h
diff -ruN lua-5.4.3/src/luaconf.h newlua/src/luaconf.h --- lua-5.4.4/src/luaconf.h 2022-01-13 19:24:43.000000000 +0800
--- lua-5.4.3/src/luaconf.h 2021-03-15 21:32:52.000000000 +0800 +++ lua-5.4.4-mod/src/luaconf.h 2022-11-16 09:40:57.703926000 +0800
+++ newlua/src/luaconf.h 2022-06-10 19:15:03.014745300 +0800 @@ -782,5 +782,15 @@
@@ -786,5 +786,15 @@
+#if defined(lua_c) || defined(luac_c) || (defined(LUA_LIB) && \ +#if defined(lua_c) || defined(luac_c) || (defined(LUA_LIB) && \
+ (defined(lauxlib_c) || defined(liolib_c) || \ + (defined(lauxlib_c) || defined(liolib_c) || \
+ defined(loadlib_c) || defined(loslib_c))) + defined(loadlib_c) || defined(loslib_c)))
@ -28,23 +27,22 @@ diff -ruN lua-5.4.3/src/luaconf.h newlua/src/luaconf.h
+ +
+ +
#endif #endif
diff -ruN lua-5.4.3/src/Makefile newlua/src/Makefile diff -ruN lua-5.4.4/src/Makefile lua-5.4.4-mod/src/Makefile
--- lua-5.4.3/src/Makefile 2021-02-10 02:47:17.000000000 +0800 --- lua-5.4.4/src/Makefile 2021-07-15 22:01:52.000000000 +0800
+++ newlua/src/Makefile 2022-06-10 19:22:45.267931400 +0800 +++ lua-5.4.4-mod/src/Makefile 2022-11-16 09:40:57.708921800 +0800
@@ -33,7 +33,7 @@ @@ -33,7 +33,7 @@
PLATS= guess aix bsd c89 freebsd generic linux linux-readline macosx mingw posix solaris PLATS= guess aix bsd c89 freebsd generic linux linux-readline macosx mingw posix solaris
LUA_A= liblua.a LUA_A= liblua.a
-CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o lundump.o lvm.o lzio.o -CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o lundump.o lvm.o lzio.o
+CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o lundump.o lvm.o lzio.o utf8_wrappers.o +CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o lundump.o lvm.o lzio.o utf8_wrappers.o
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 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) BASE_O= $(CORE_O) $(LIB_O) $(MYOBJS)
diff -ruN lua-5.4.4/src/utf8_wrappers.c lua-5.4.4-mod/src/utf8_wrappers.c
diff -ruN lua-5.4.3/src/utf8_wrappers.c newlua/src/utf8_wrappers.c --- lua-5.4.4/src/utf8_wrappers.c 1970-01-01 07:30:00.000000000 +0730
--- lua-5.4.3/src/utf8_wrappers.c 1970-01-01 07:30:00.000000000 +0730 +++ lua-5.4.4-mod/src/utf8_wrappers.c 2022-11-16 10:09:04.583866600 +0800
+++ newlua/src/utf8_wrappers.c 2022-06-10 19:13:11.904613300 +0800
@@ -0,0 +1,101 @@ @@ -0,0 +1,101 @@
+/** +/**
+ * Wrappers to provide Unicode (UTF-8) support on Windows. + * Wrappers to provide Unicode (UTF-8) support on Windows.
@ -147,10 +145,10 @@ diff -ruN lua-5.4.3/src/utf8_wrappers.c newlua/src/utf8_wrappers.c
+ return LoadLibraryExW(pathname_w, hFile, dwFlags); + return LoadLibraryExW(pathname_w, hFile, dwFlags);
+} +}
+#endif +#endif
diff -ruN lua-5.4.3/src/utf8_wrappers.h newlua/src/utf8_wrappers.h diff -ruN lua-5.4.4/src/utf8_wrappers.h lua-5.4.4-mod/src/utf8_wrappers.h
--- lua-5.4.3/src/utf8_wrappers.h 1970-01-01 07:30:00.000000000 +0730 --- lua-5.4.4/src/utf8_wrappers.h 1970-01-01 07:30:00.000000000 +0730
+++ newlua/src/utf8_wrappers.h 2022-06-10 19:22:53.554879400 +0800 +++ lua-5.4.4-mod/src/utf8_wrappers.h 2022-11-16 10:29:46.044102000 +0800
@@ -0,0 +1,42 @@ @@ -0,0 +1,44 @@
+/** +/**
+ * Wrappers to provide Unicode (UTF-8) support on Windows. + * Wrappers to provide Unicode (UTF-8) support on Windows.
+ * + *
@ -167,6 +165,7 @@ diff -ruN lua-5.4.3/src/utf8_wrappers.h newlua/src/utf8_wrappers.h
+#endif +#endif
+ +
+#ifdef lauxlib_c +#ifdef lauxlib_c
+#include <stdio.h>
+FILE *freopen_utf8(const char *pathname, const char *mode, FILE *stream); +FILE *freopen_utf8(const char *pathname, const char *mode, FILE *stream);
+#define freopen freopen_utf8 +#define freopen freopen_utf8
+#endif +#endif
@ -177,6 +176,7 @@ diff -ruN lua-5.4.3/src/utf8_wrappers.h newlua/src/utf8_wrappers.h
+#endif +#endif
+ +
+#ifdef loslib_c +#ifdef loslib_c
+#include <stdio.h>
+int remove_utf8(const char *pathname); +int remove_utf8(const char *pathname);
+int rename_utf8(const char *oldpath, const char *newpath); +int rename_utf8(const char *oldpath, const char *newpath);
+int system_utf8(const char *command); +int system_utf8(const char *command);

View File

@ -31,6 +31,7 @@ show_help() {
main() { main() {
local platform="$(get_platform_name)" local platform="$(get_platform_name)"
local arch="$(get_platform_arch)"
local build_dir="$(get_default_build_dir)" local build_dir="$(get_default_build_dir)"
local build_type="debug" local build_type="debug"
local prefix=/ local prefix=/
@ -106,11 +107,27 @@ main() {
portable="" portable=""
fi fi
if [[ $CROSS_ARCH != "" ]]; then
if [[ $platform == "macos" ]]; then
macos_version_min=10.11
if [[ $CROSS_ARCH == "arm64" ]]; then
cross_file="--cross-file resources/macos/macos_arm64.conf"
macos_version_min=11.0
fi
export MACOSX_DEPLOYMENT_TARGET=$macos_version_min
export MIN_SUPPORTED_MACOSX_DEPLOYMENT_TARGET=$macos_version_min
export CFLAGS=-mmacosx-version-min=$macos_version_min
export CXXFLAGS=-mmacosx-version-min=$macos_version_min
export LDFLAGS=-mmacosx-version-min=$macos_version_min
fi
fi
rm -rf "${build_dir}" rm -rf "${build_dir}"
CFLAGS=$CFLAGS LDFLAGS=$LDFLAGS meson setup \ CFLAGS=$CFLAGS LDFLAGS=$LDFLAGS meson setup \
--buildtype=$build_type \ --buildtype=$build_type \
--prefix "$prefix" \ --prefix "$prefix" \
$cross_file \
$force_fallback \ $force_fallback \
$bundle \ $bundle \
$portable \ $portable \
@ -124,7 +141,7 @@ main() {
meson compile -C "${build_dir}" meson compile -C "${build_dir}"
if [ ! -z ${pgo+x} ]; then if [[ $pgo != "" ]]; then
cp -r data "${build_dir}/src" cp -r data "${build_dir}/src"
"${build_dir}/src/lite-xl" "${build_dir}/src/lite-xl"
meson configure -Db_pgo=use "${build_dir}" meson configure -Db_pgo=use "${build_dir}"

View File

@ -27,16 +27,17 @@ addons_download() {
-o "${build_dir}/lite-xl-widgets.zip" -o "${build_dir}/lite-xl-widgets.zip"
unzip "${build_dir}/lite-xl-widgets.zip" -d "${build_dir}" unzip "${build_dir}/lite-xl-widgets.zip" -d "${build_dir}"
mv "${build_dir}/lite-xl-widgets-master" "${build_dir}/third/data/widget" mkdir -p "${build_dir}/third/data/libraries"
mv "${build_dir}/lite-xl-widgets-master" "${build_dir}/third/data/libraries/widget"
# Downlaod thirdparty plugins # Downlaod thirdparty plugins
curl --insecure \ curl --insecure \
-L "https://github.com/lite-xl/lite-xl-plugins/archive/2.1.zip" \ -L "https://github.com/lite-xl/lite-xl-plugins/archive/master.zip" \
-o "${build_dir}/lite-xl-plugins.zip" -o "${build_dir}/lite-xl-plugins.zip"
unzip "${build_dir}/lite-xl-plugins.zip" -d "${build_dir}" unzip "${build_dir}/lite-xl-plugins.zip" -d "${build_dir}"
mv "${build_dir}/lite-xl-plugins-2.1/plugins" "${build_dir}/third/data" mv "${build_dir}/lite-xl-plugins-master/plugins" "${build_dir}/third/data"
rm -rf "${build_dir}/lite-xl-plugins-2.1" rm -rf "${build_dir}/lite-xl-plugins-master"
} }
# Addons installation: some distributions forbid external downloads # Addons installation: some distributions forbid external downloads
@ -45,7 +46,7 @@ addons_install() {
local build_dir="$1" local build_dir="$1"
local data_dir="$2" local data_dir="$2"
for module_name in colors widget; do for module_name in colors libraries; do
cp -r "${build_dir}/third/data/$module_name" "${data_dir}" cp -r "${build_dir}/third/data/$module_name" "${data_dir}"
done done
@ -81,6 +82,8 @@ get_platform_arch() {
else else
arch=i686 arch=i686
fi fi
elif [[ $CROSS_ARCH != "" ]]; then
arch=$CROSS_ARCH
fi fi
echo "$arch" echo "$arch"
} }

View File

@ -125,6 +125,76 @@
"css": "right-open", "css": "right-open",
"code": 62, "code": 62,
"src": "fontawesome" "src": "fontawesome"
},
{
"uid": "ebf4bfe82c54f9beb94c5221a7bdd975",
"css": "lite-xlbg",
"code": 53,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M83.3 0H916.7C962.7 0 1000 37.3 1000 83.3V916.7C1000 962.7 962.7 1000 916.7 1000H83.3C37.3 1000 0 962.7 0 916.7V83.3C0 37.3 37.3 0 83.3 0Z",
"width": 1000
},
"search": [
"lite-xlbg"
]
},
{
"uid": "cde50fad6c2cd7a805a8d5026939d645",
"css": "lite-xl1",
"code": 54,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M395.8 562.5V312.5C395.8 289.5 377.2 270.8 354.2 270.8H270.8V625C270.8 682.5 317.5 729.2 375 729.2H729.2V645.8C729.2 622.8 710.5 604.2 687.5 604.2H437.5C414.5 604.2 395.8 585.5 395.8 562.5Z",
"width": 1000
},
"search": [
"lite-xl1"
]
},
{
"uid": "526539ec7fcda0b3e7e7ceaa6ae416e6",
"css": "lite-xl2",
"code": 55,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M729.2 270.8H437.5L729.2 562.5Z",
"width": 1000
},
"search": [
"lite-xl2"
]
},
{
"uid": "cccbe047c5cb17c63c41a1fb1fc022f4",
"css": "lite-xl3",
"code": 57,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M666.7 333.3H500L666.7 500Z",
"width": 1000
},
"search": [
"lite-xl3"
]
},
{
"uid": "09d781dca1f50fcae90e67df9cc2f8dd",
"css": "lite-xl4",
"code": 56,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M500 458.3V333.3L604.2 395.8 666.7 500H541.7C518.7 500 500 481.3 500 458.3Z",
"width": 1000
},
"search": [
"lite-xl4"
]
} }
] ]
} }

View File

@ -3,7 +3,7 @@
##### CONFIG ##### CONFIG
# symbols to ignore # symbols to ignore
IGNORE_SYM='luaL_pushmodule\|luaL_openlib' IGNORE_SYM='luaL_pushmodule'
##### CONFIG ##### CONFIG
@ -76,7 +76,7 @@ generate_header() {
echo "The lite_xl plugin API is quite simple. Any shared library can be a plugin file, so long" echo "The lite_xl plugin API is quite simple. Any shared library can be a plugin file, so long"
echo "as it has an entrypoint that looks like the following, where xxxxx is the plugin name:" echo "as it has an entrypoint that looks like the following, where xxxxx is the plugin name:"
echo '#include "lite_xl_plugin_api.h"' echo '#include "lite_xl_plugin_api.h"'
echo "int lua_open_lite_xl_xxxxx(lua_State* L, void* XL) {" echo "int luaopen_lite_xl_xxxxx(lua_State* L, void* XL) {"
echo " lite_xl_plugin_init(XL);" echo " lite_xl_plugin_init(XL);"
echo " ..." echo " ..."
echo " return 1;" echo " return 1;"
@ -98,6 +98,8 @@ generate_header() {
decl "$LUA_PATH/lua.h" decl "$LUA_PATH/lua.h"
echo echo
decl "$LUA_PATH/lauxlib.h" decl "$LUA_PATH/lauxlib.h"
echo "static void (*luaL_openlibs) (lua_State *L);"
echo 'static void __lite_xl_fallback_luaL_openlibs (lua_State *L) { fputs("warning: luaL_openlibs is a stub", stderr); }'
echo echo
echo "#define IMPORT_SYMBOL(name, ret, ...) name = (name = (ret (*) (__VA_ARGS__)) symbol(#name), name == NULL ? &__lite_xl_fallback_##name : name)" echo "#define IMPORT_SYMBOL(name, ret, ...) name = (name = (ret (*) (__VA_ARGS__)) symbol(#name), name == NULL ? &__lite_xl_fallback_##name : name)"
@ -106,6 +108,7 @@ generate_header() {
decl_import "$LUA_PATH/lua.h" decl_import "$LUA_PATH/lua.h"
decl_import "$LUA_PATH/lauxlib.h" decl_import "$LUA_PATH/lauxlib.h"
echo -e "\tIMPORT_SYMBOL(luaL_openlibs, void, lua_State* L);"
echo "}" echo "}"
echo "#endif" echo "#endif"

View File

@ -8,6 +8,7 @@
#define API_TYPE_FONT "Font" #define API_TYPE_FONT "Font"
#define API_TYPE_PROCESS "Process" #define API_TYPE_PROCESS "Process"
#define API_TYPE_DIRMONITOR "Dirmonitor" #define API_TYPE_DIRMONITOR "Dirmonitor"
#define API_TYPE_NATIVE_PLUGIN "NativePlugin"
#define API_CONSTANT_DEFINE(L, idx, key, n) (lua_pushnumber(L, n), lua_setfield(L, idx - 1, key)) #define API_CONSTANT_DEFINE(L, idx, key, n) (lua_pushnumber(L, n), lua_setfield(L, idx - 1, key))

View File

@ -1,8 +1,6 @@
#include "api.h" #include "api.h"
#include <SDL.h> #include <SDL.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
@ -23,6 +21,7 @@ int get_changes_dirmonitor(struct dirmonitor_internal*, char*, int);
int translate_changes_dirmonitor(struct dirmonitor_internal*, char*, int, int (*)(int, const char*, void*), void*); int translate_changes_dirmonitor(struct dirmonitor_internal*, char*, int, int (*)(int, const char*, void*), void*);
int add_dirmonitor(struct dirmonitor_internal*, const char*); int add_dirmonitor(struct dirmonitor_internal*, const char*);
void remove_dirmonitor(struct dirmonitor_internal*, int); void remove_dirmonitor(struct dirmonitor_internal*, int);
int get_mode_dirmonitor();
static int f_check_dir_callback(int watch_id, const char* path, void* L) { static int f_check_dir_callback(int watch_id, const char* path, void* L) {
@ -62,6 +61,7 @@ static int f_dirmonitor_new(lua_State* L) {
struct dirmonitor* monitor = lua_newuserdata(L, sizeof(struct dirmonitor)); struct dirmonitor* monitor = lua_newuserdata(L, sizeof(struct dirmonitor));
luaL_setmetatable(L, API_TYPE_DIRMONITOR); luaL_setmetatable(L, API_TYPE_DIRMONITOR);
memset(monitor, 0, sizeof(struct dirmonitor)); memset(monitor, 0, sizeof(struct dirmonitor));
monitor->mutex = SDL_CreateMutex();
monitor->internal = init_dirmonitor(); monitor->internal = init_dirmonitor();
return 1; return 1;
} }
@ -111,12 +111,23 @@ static int f_dirmonitor_check(lua_State* L) {
} }
static int f_dirmonitor_mode(lua_State* L) {
int mode = get_mode_dirmonitor();
if (mode == 1)
lua_pushstring(L, "single");
else
lua_pushstring(L, "multiple");
return 1;
}
static const luaL_Reg dirmonitor_lib[] = { static const luaL_Reg dirmonitor_lib[] = {
{ "new", f_dirmonitor_new }, { "new", f_dirmonitor_new },
{ "__gc", f_dirmonitor_gc }, { "__gc", f_dirmonitor_gc },
{ "watch", f_dirmonitor_watch }, { "watch", f_dirmonitor_watch },
{ "unwatch", f_dirmonitor_unwatch }, { "unwatch", f_dirmonitor_unwatch },
{ "check", f_dirmonitor_check }, { "check", f_dirmonitor_check },
{ "mode", f_dirmonitor_mode },
{NULL, NULL} {NULL, NULL}
}; };

View File

@ -5,4 +5,5 @@ void deinit_dirmonitor(struct dirmonitor_internal* monitor) { }
int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, size_t len) { return -1; } int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, size_t len) { return -1; }
int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int size, int (*callback)(int, const char*, void*), void* data) { return -1; } int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int size, int (*callback)(int, const char*, void*), void* data) { return -1; }
int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { return -1; } int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { return -1; }
void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { } void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { }
int get_mode_dirmonitor() { return 1; }

View File

@ -0,0 +1,193 @@
#include <SDL.h>
#include <CoreServices/CoreServices.h>
struct dirmonitor_internal {
SDL_mutex* lock;
char** changes;
size_t count;
FSEventStreamRef stream;
int fds[2];
};
CFRunLoopRef main_run_loop;
struct dirmonitor_internal* init_dirmonitor() {
static bool mainloop_registered = false;
if (!mainloop_registered) {
main_run_loop = CFRunLoopGetCurrent();
mainloop_registered = true;
}
struct dirmonitor_internal* monitor = malloc(sizeof(struct dirmonitor_internal));
monitor->stream = NULL;
monitor->changes = NULL;
monitor->count = 0;
monitor->lock = NULL;
return monitor;
}
static void stop_monitor_stream(struct dirmonitor_internal* monitor) {
if (monitor->stream) {
FSEventStreamStop(monitor->stream);
FSEventStreamUnscheduleFromRunLoop(
monitor->stream, main_run_loop, kCFRunLoopDefaultMode
);
FSEventStreamInvalidate(monitor->stream);
FSEventStreamRelease(monitor->stream);
monitor->stream = NULL;
SDL_LockMutex(monitor->lock);
write(monitor->fds[1], "", 1);
close(monitor->fds[0]);
close(monitor->fds[1]);
if (monitor->count > 0) {
for (size_t i = 0; i<monitor->count; i++) {
free(monitor->changes[i]);
}
free(monitor->changes);
monitor->changes = NULL;
monitor->count = 0;
}
SDL_UnlockMutex(monitor->lock);
SDL_DestroyMutex(monitor->lock);
}
}
void deinit_dirmonitor(struct dirmonitor_internal* monitor) {
stop_monitor_stream(monitor);
}
static void stream_callback(
ConstFSEventStreamRef streamRef,
void* monitor_ptr,
size_t numEvents,
void* eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]
)
{
if (numEvents <= 0) {
return;
}
struct dirmonitor_internal* monitor = monitor_ptr;
char** path_list = eventPaths;
SDL_LockMutex(monitor->lock);
size_t total = 0;
if (monitor->count == 0) {
total = numEvents;
monitor->changes = calloc(numEvents, sizeof(char*));
} else {
total = monitor->count + numEvents;
monitor->changes = realloc(
monitor->changes,
sizeof(char*) * total
);
}
for (size_t idx = monitor->count; idx < total; idx++) {
size_t pidx = idx - monitor->count;
monitor->changes[idx] = malloc(strlen(path_list[pidx])+1);
strcpy(monitor->changes[idx], path_list[pidx]);
}
monitor->count = total;
if (total > 0)
write(monitor->fds[1], "", 1);
SDL_UnlockMutex(monitor->lock);
}
int get_changes_dirmonitor(
struct dirmonitor_internal* monitor,
char* buffer,
int buffer_size
) {
char response[1];
read(monitor->fds[0], response, 1);
size_t results = 0;
SDL_LockMutex(monitor->lock);
results = monitor->count;
SDL_UnlockMutex(monitor->lock);
return results;
}
int translate_changes_dirmonitor(
struct dirmonitor_internal* monitor,
char* buffer,
int buffer_size,
int (*change_callback)(int, const char*, void*),
void* L
) {
SDL_LockMutex(monitor->lock);
if (monitor->count > 0) {
for (size_t i = 0; i<monitor->count; i++) {
change_callback(strlen(monitor->changes[i]), monitor->changes[i], L);
free(monitor->changes[i]);
}
free(monitor->changes);
monitor->changes = NULL;
monitor->count = 0;
}
SDL_UnlockMutex(monitor->lock);
return 0;
}
int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) {
stop_monitor_stream(monitor);
monitor->lock = SDL_CreateMutex();
pipe(monitor->fds);
FSEventStreamContext context = {
.info = monitor,
.retain = NULL,
.release = NULL,
.copyDescription = NULL,
.version = 0
};
CFStringRef paths[] = {
CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8)
};
monitor->stream = FSEventStreamCreate(
NULL,
stream_callback,
&context,
CFArrayCreate(NULL, (const void **)&paths, 1, NULL),
kFSEventStreamEventIdSinceNow,
0,
kFSEventStreamCreateFlagNone
| kFSEventStreamCreateFlagWatchRoot
| kFSEventStreamCreateFlagFileEvents
);
FSEventStreamScheduleWithRunLoop(
monitor->stream, main_run_loop, kCFRunLoopDefaultMode
);
if (!FSEventStreamStart(monitor->stream)) {
stop_monitor_stream(monitor);
return -1;
}
return 1;
}
void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) {
stop_monitor_stream(monitor);
}
int get_mode_dirmonitor() { return 1; }

View File

@ -13,7 +13,7 @@ struct dirmonitor_internal {
struct dirmonitor_internal* init_dirmonitor() { struct dirmonitor_internal* init_dirmonitor() {
struct dirmonitor_internal* monitor = calloc(sizeof(struct dirmonitor_internal), 1); struct dirmonitor_internal* monitor = calloc(1, sizeof(struct dirmonitor_internal));
monitor->fd = inotify_init(); monitor->fd = inotify_init();
pipe(monitor->sig); pipe(monitor->sig);
fcntl(monitor->sig[0], F_SETFD, FD_CLOEXEC); fcntl(monitor->sig[0], F_SETFD, FD_CLOEXEC);
@ -51,3 +51,6 @@ int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) {
void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) {
inotify_rm_watch(monitor->fd, fd); inotify_rm_watch(monitor->fd, fd);
} }
int get_mode_dirmonitor() { return 2; }

View File

@ -4,6 +4,7 @@
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <time.h>
struct dirmonitor_internal { struct dirmonitor_internal {
int fd; int fd;
@ -11,7 +12,7 @@ struct dirmonitor_internal {
struct dirmonitor_internal* init_dirmonitor() { struct dirmonitor_internal* init_dirmonitor() {
struct dirmonitor_internal* monitor = calloc(sizeof(struct dirmonitor_internal), 1); struct dirmonitor_internal* monitor = calloc(1, sizeof(struct dirmonitor_internal));
monitor->fd = kqueue(); monitor->fd = kqueue();
return monitor; return monitor;
} }
@ -23,7 +24,9 @@ void deinit_dirmonitor(struct dirmonitor_internal* monitor) {
int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size) { int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size) {
int nev = kevent(monitor->fd, NULL, 0, (struct kevent*)buffer, buffer_size / sizeof(kevent), NULL); struct timespec ts = { 0, 100 * 1000000 }; // 100 ms
int nev = kevent(monitor->fd, NULL, 0, (struct kevent*)buffer, buffer_size / sizeof(kevent), &ts);
if (nev == -1) if (nev == -1)
return -1; return -1;
if (nev <= 0) if (nev <= 0)
@ -43,8 +46,11 @@ int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) {
int fd = open(path, O_RDONLY); int fd = open(path, O_RDONLY);
struct kevent change; struct kevent change;
// a timeout of zero should make kevent return immediately
struct timespec ts = { 0, 0 }; // 0 s
EV_SET(&change, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME, 0, (void*)path); EV_SET(&change, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME, 0, (void*)path);
kevent(monitor->fd, &change, 1, NULL, 0, NULL); kevent(monitor->fd, &change, 1, NULL, 0, &ts);
return fd; return fd;
} }
@ -53,3 +59,6 @@ int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) {
void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) {
close(fd); close(fd);
} }
int get_mode_dirmonitor() { return 2; }

View File

@ -19,7 +19,7 @@ int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, in
struct dirmonitor* init_dirmonitor() { struct dirmonitor* init_dirmonitor() {
return calloc(sizeof(struct dirmonitor_internal), 1); return calloc(1, sizeof(struct dirmonitor_internal));
} }
@ -40,8 +40,8 @@ void deinit_dirmonitor(struct dirmonitor_internal* monitor) {
int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size, int (*change_callback)(int, const char*, void*), void* data) { int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size, int (*change_callback)(int, const char*, void*), void* data) {
for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)buffer; (char*)info < buffer + buffer_size; info = (FILE_NOTIFY_INFORMATION*)(((char*)info) + info->NextEntryOffset)) { for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)buffer; (char*)info < buffer + buffer_size; info = (FILE_NOTIFY_INFORMATION*)(((char*)info) + info->NextEntryOffset)) {
char transform_buffer[PATH_MAX*4]; char transform_buffer[MAX_PATH*4];
int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)info->FileName, info->FileNameLength, transform_buffer, PATH_MAX*4 - 1, NULL, NULL); int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)info->FileName, info->FileNameLength / 2, transform_buffer, MAX_PATH*4 - 1, NULL, NULL);
change_callback(count, transform_buffer, data); change_callback(count, transform_buffer, data);
if (!info->NextEntryOffset) if (!info->NextEntryOffset)
break; break;
@ -60,3 +60,6 @@ int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) {
void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) {
close_monitor_handle(monitor); close_monitor_handle(monitor);
} }
int get_mode_dirmonitor() { return 1; }

View File

@ -29,7 +29,7 @@ typedef int process_handle;
#endif #endif
typedef struct { typedef struct {
bool running; bool running, detached;
int returncode, deadline; int returncode, deadline;
long pid; long pid;
#if _WIN32 #if _WIN32
@ -38,7 +38,7 @@ typedef struct {
bool reading[2]; bool reading[2];
char buffer[2][READ_BUF_SIZE]; char buffer[2][READ_BUF_SIZE];
#endif #endif
process_handle child_pipes[3][2]; process_handle child_pipes[3][2];
} process_t; } process_t;
typedef enum { typedef enum {
@ -65,7 +65,7 @@ typedef enum {
#ifdef _WIN32 #ifdef _WIN32
static volatile long PipeSerialNumber; static volatile long PipeSerialNumber;
static void close_fd(HANDLE* handle) { if (*handle) CloseHandle(*handle); *handle = NULL; } static void close_fd(HANDLE* handle) { if (*handle) CloseHandle(*handle); *handle = INVALID_HANDLE_VALUE; }
#else #else
static void close_fd(int* fd) { if (*fd) close(*fd); *fd = 0; } static void close_fd(int* fd) { if (*fd) close(*fd); *fd = 0; }
#endif #endif
@ -125,22 +125,26 @@ static int process_start(lua_State* L) {
int retval = 1; int retval = 1;
size_t env_len = 0, key_len, val_len; size_t env_len = 0, key_len, val_len;
const char *cmd[256] = { NULL }, *env_names[256] = { NULL }, *env_values[256] = { NULL }, *cwd = NULL; const char *cmd[256] = { NULL }, *env_names[256] = { NULL }, *env_values[256] = { NULL }, *cwd = NULL;
bool detach = false; bool detach = false, literal = false;
int deadline = 10, new_fds[3] = { STDIN_FD, STDOUT_FD, STDERR_FD }; int deadline = 10, new_fds[3] = { STDIN_FD, STDOUT_FD, STDERR_FD };
luaL_checktype(L, 1, LUA_TTABLE); size_t arg_len = lua_gettop(L), cmd_len;
#if LUA_VERSION_NUM > 501 if (lua_type(L, 1) == LUA_TTABLE) {
lua_len(L, 1); #if LUA_VERSION_NUM > 501
#else lua_len(L, 1);
lua_pushinteger(L, (int)lua_objlen(L, 1)); #else
#endif lua_pushinteger(L, (int)lua_objlen(L, 1));
size_t cmd_len = luaL_checknumber(L, -1); lua_pop(L, 1); #endif
size_t arg_len = lua_gettop(L); cmd_len = luaL_checknumber(L, -1); lua_pop(L, 1);
for (size_t i = 1; i <= cmd_len; ++i) { for (size_t i = 1; i <= cmd_len; ++i) {
lua_pushinteger(L, i); lua_pushinteger(L, i);
lua_rawget(L, 1); lua_rawget(L, 1);
cmd[i-1] = luaL_checkstring(L, -1); cmd[i-1] = luaL_checkstring(L, -1);
}
} else {
literal = true;
cmd[0] = luaL_checkstring(L, 1);
cmd_len = 1;
} }
// this should never trip // this should never trip
// but if it does we are in deep trouble // but if it does we are in deep trouble
assert(cmd[0]); assert(cmd[0]);
@ -169,11 +173,8 @@ static int process_start(lua_State* L) {
lua_getfield(L, 2, "stderr"); new_fds[STDERR_FD] = luaL_optnumber(L, -1, STDERR_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) { for (int stream = STDIN_FD; stream <= STDERR_FD; ++stream) {
if (new_fds[stream] > STDERR_FD || new_fds[stream] < REDIRECT_PARENT) { if (new_fds[stream] > STDERR_FD || new_fds[stream] < REDIRECT_PARENT) {
for (size_t i = 0; i < env_len; ++i) { lua_pushfstring(L, "redirect to handles, FILE* and paths are not supported");
free((char*)env_names[i]); retval = -1;
free((char*)env_values[i]);
}
retval = luaL_error(L, "redirect to handles, FILE* and paths are not supported");
goto cleanup; goto cleanup;
} }
} }
@ -183,6 +184,7 @@ static int process_start(lua_State* L) {
memset(self, 0, sizeof(process_t)); memset(self, 0, sizeof(process_t));
luaL_setmetatable(L, API_TYPE_PROCESS); luaL_setmetatable(L, API_TYPE_PROCESS);
self->deadline = deadline; self->deadline = deadline;
self->detached = detach;
#if _WIN32 #if _WIN32
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
switch (new_fds[i]) { switch (new_fds[i]) {
@ -205,18 +207,21 @@ static int process_start(lua_State* L) {
self->child_pipes[i][0] = CreateNamedPipeA(pipeNameBuffer, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, self->child_pipes[i][0] = CreateNamedPipeA(pipeNameBuffer, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_WAIT, 1, READ_BUF_SIZE, READ_BUF_SIZE, 0, NULL); PIPE_TYPE_BYTE | PIPE_WAIT, 1, READ_BUF_SIZE, READ_BUF_SIZE, 0, NULL);
if (self->child_pipes[i][0] == INVALID_HANDLE_VALUE) { if (self->child_pipes[i][0] == INVALID_HANDLE_VALUE) {
retval = luaL_error(L, "Error creating read pipe: %d.", GetLastError()); lua_pushfstring(L, "Error creating read pipe: %d.", GetLastError());
retval = -1;
goto cleanup; goto cleanup;
} }
self->child_pipes[i][1] = CreateFileA(pipeNameBuffer, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); self->child_pipes[i][1] = CreateFileA(pipeNameBuffer, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (self->child_pipes[i][1] == INVALID_HANDLE_VALUE) { if (self->child_pipes[i][1] == INVALID_HANDLE_VALUE) {
CloseHandle(self->child_pipes[i][0]); CloseHandle(self->child_pipes[i][0]);
retval = luaL_error(L, "Error creating write pipe: %d.", GetLastError()); lua_pushfstring(L, "Error creating write pipe: %d.", GetLastError());
retval = -1;
goto cleanup; goto cleanup;
} }
if (!SetHandleInformation(self->child_pipes[i][i == STDIN_FD ? 1 : 0], HANDLE_FLAG_INHERIT, 0) || if (!SetHandleInformation(self->child_pipes[i][i == STDIN_FD ? 1 : 0], HANDLE_FLAG_INHERIT, 0) ||
!SetHandleInformation(self->child_pipes[i][i == STDIN_FD ? 0 : 1], HANDLE_FLAG_INHERIT, 1)) { !SetHandleInformation(self->child_pipes[i][i == STDIN_FD ? 0 : 1], HANDLE_FLAG_INHERIT, 1)) {
retval = luaL_error(L, "Error inheriting pipes: %d.", GetLastError()); lua_pushfstring(L, "Error inheriting pipes: %d.", GetLastError());
retval = -1;
goto cleanup; goto cleanup;
} }
} }
@ -238,15 +243,35 @@ static int process_start(lua_State* L) {
siStartInfo.hStdOutput = self->child_pipes[STDOUT_FD][1]; siStartInfo.hStdOutput = self->child_pipes[STDOUT_FD][1];
siStartInfo.hStdError = self->child_pipes[STDERR_FD][1]; siStartInfo.hStdError = self->child_pipes[STDERR_FD][1];
char commandLine[32767] = { 0 }, environmentBlock[32767], wideEnvironmentBlock[32767*2]; char commandLine[32767] = { 0 }, environmentBlock[32767], wideEnvironmentBlock[32767*2];
strcpy(commandLine, cmd[0]);
int offset = 0; int offset = 0;
for (size_t i = 1; i < cmd_len; ++i) { if (!literal) {
size_t len = strlen(cmd[i]); for (size_t i = 0; i < cmd_len; ++i) {
offset += len + 1; size_t len = strlen(cmd[i]);
if (offset >= sizeof(commandLine)) if (offset + len + 2 >= sizeof(commandLine)) break;
break; if (i > 0)
strcat(commandLine, " "); commandLine[offset++] = ' ';
strcat(commandLine, cmd[i]); 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; offset = 0;
for (size_t i = 0; i < env_len; ++i) { for (size_t i = 0; i < env_len; ++i) {
@ -259,7 +284,8 @@ static int process_start(lua_State* L) {
if (env_len > 0) if (env_len > 0)
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, environmentBlock, offset, (LPWSTR)wideEnvironmentBlock, sizeof(wideEnvironmentBlock)); 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)) { 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)) {
retval = luaL_error(L, "Error creating a process: %d.", GetLastError()); lua_pushfstring(L, "Error creating a process: %d.", GetLastError());
retval = -1;
goto cleanup; goto cleanup;
} }
self->pid = (long)self->process_information.dwProcessId; self->pid = (long)self->process_information.dwProcessId;
@ -269,16 +295,19 @@ static int process_start(lua_State* L) {
#else #else
for (int i = 0; i < 3; ++i) { // Make only the parents fd's non-blocking. Children should block. for (int i = 0; i < 3; ++i) { // Make only the parents fd's non-blocking. Children should block.
if (pipe(self->child_pipes[i]) || fcntl(self->child_pipes[i][i == STDIN_FD ? 1 : 0], F_SETFL, O_NONBLOCK) == -1) { if (pipe(self->child_pipes[i]) || fcntl(self->child_pipes[i][i == STDIN_FD ? 1 : 0], F_SETFL, O_NONBLOCK) == -1) {
retval = luaL_error(L, "Error creating pipes: %s", strerror(errno)); lua_pushfstring(L, "Error creating pipes: %s", strerror(errno));
retval = -1;
goto cleanup; goto cleanup;
} }
} }
self->pid = (long)fork(); self->pid = (long)fork();
if (self->pid < 0) { if (self->pid < 0) {
retval = luaL_error(L, "Error running fork: %s.", strerror(errno)); lua_pushfstring(L, "Error running fork: %s.", strerror(errno));
retval = -1;
goto cleanup; goto cleanup;
} else if (!self->pid) { } else if (!self->pid) {
setpgid(0,0); if (!detach)
setpgid(0,0);
for (int stream = 0; stream < 3; ++stream) { for (int stream = 0; stream < 3; ++stream) {
if (new_fds[stream] == REDIRECT_DISCARD) { // Close the stream if we don't want it. if (new_fds[stream] == REDIRECT_DISCARD) { // Close the stream if we don't want it.
close(self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]); close(self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]);
@ -307,6 +336,9 @@ static int process_start(lua_State* L) {
close_fd(pipe); close_fd(pipe);
} }
} }
if (retval == -1)
return lua_error(L);
self->running = true; self->running = true;
return retval; return retval;
} }
@ -454,7 +486,8 @@ static int f_kill(lua_State* L) { return self_signal(L, SIGNAL_KILL); }
static int f_interrupt(lua_State* L) { return self_signal(L, SIGNAL_INTERRUPT); } static int f_interrupt(lua_State* L) { return self_signal(L, SIGNAL_INTERRUPT); }
static int f_gc(lua_State* L) { static int f_gc(lua_State* L) {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
signal_process(self, SIGNAL_TERM); if (!self->detached)
signal_process(self, SIGNAL_TERM);
close_fd(&self->child_pipes[STDIN_FD ][1]); close_fd(&self->child_pipes[STDIN_FD ][1]);
close_fd(&self->child_pipes[STDOUT_FD][0]); close_fd(&self->child_pipes[STDOUT_FD][0]);
close_fd(&self->child_pipes[STDERR_FD][0]); close_fd(&self->child_pipes[STDERR_FD][0]);

View File

@ -4,6 +4,126 @@
#include <string.h> #include <string.h>
#include <pcre2.h> #include <pcre2.h>
#include <stdbool.h>
typedef struct RegexState {
pcre2_code* re;
pcre2_match_data* match_data;
const char* subject;
size_t subject_len;
size_t offset;
bool regex_compiled;
bool found;
} RegexState;
static pcre2_code* regex_get_pattern(lua_State *L, bool* should_free) {
pcre2_code* re = NULL;
*should_free = false;
if (lua_type(L, 1) == LUA_TTABLE) {
lua_rawgeti(L, 1, 1);
re = (pcre2_code*)lua_touserdata(L, -1);
lua_settop(L, -2);
} else {
int errornumber;
PCRE2_SIZE erroroffset;
size_t pattern_len = 0;
const char* pattern = luaL_checklstring(L, 1, &pattern_len);
re = pcre2_compile(
(PCRE2_SPTR)pattern,
pattern_len, PCRE2_UTF,
&errornumber, &erroroffset, NULL
);
if (re == NULL) {
PCRE2_UCHAR errmsg[256];
pcre2_get_error_message(errornumber, errmsg, sizeof(errmsg));
luaL_error(
L, "regex pattern error at offset %d: %s",
(int)erroroffset, errmsg
);
return NULL;
}
pcre2_jit_compile(re, PCRE2_JIT_COMPLETE);
*should_free = true;
}
return re;
}
static int regex_gmatch_iterator(lua_State *L) {
RegexState *state = (RegexState*)lua_touserdata(L, lua_upvalueindex(3));
if (state->found) {
int rc = pcre2_match(
state->re,
(PCRE2_SPTR)state->subject, state->subject_len,
state->offset, 0, state->match_data, NULL
);
if (rc < 0) {
if (rc != PCRE2_ERROR_NOMATCH) {
PCRE2_UCHAR buffer[120];
pcre2_get_error_message(rc, buffer, sizeof(buffer));
luaL_error(L, "regex matching error %d: %s", rc, buffer);
}
goto clean;
} else {
size_t ovector_count = pcre2_get_ovector_count(state->match_data);
if (ovector_count > 0) {
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(state->match_data);
if (ovector[0] > ovector[1]) {
/* We must guard against patterns such as /(?=.\K)/ that use \K in an
assertion to set the start of a match later than its end. In the editor,
we just detect this case and give up. */
luaL_error(L, "regex matching error: \\K was used in an assertion to "
" set the match start after its end");
goto clean;
}
int index = 0;
if (ovector_count > 1) index = 2;
int total = 0;
int total_results = ovector_count * 2;
size_t last_offset = 0;
for (int i = index; i < total_results; i+=2) {
lua_pushlstring(L, state->subject+ovector[i], ovector[i+1] - ovector[i]);
last_offset = ovector[i+1];
total++;
}
if (last_offset - 1 < state->subject_len)
state->offset = last_offset;
else
state->found = false;
return total;
} else {
state->found = false;
}
}
}
clean:
if (state->regex_compiled) pcre2_code_free(state->re);
pcre2_match_data_free(state->match_data);
return 0; /* not found */
}
static size_t regex_offset_relative(lua_Integer pos, size_t len) {
if (pos > 0)
return (size_t)pos;
else if (pos == 0)
return 1;
else if (pos < -(lua_Integer)len) /* inverted comparison */
return 1; /* clip to 1 */
else return len + (size_t)pos + 1;
}
static int f_pcre_gc(lua_State* L) { static int f_pcre_gc(lua_State* L) {
lua_rawgeti(L, -1, 1); lua_rawgeti(L, -1, 1);
@ -37,6 +157,7 @@ static int f_pcre_compile(lua_State *L) {
NULL NULL
); );
if (re) { if (re) {
pcre2_jit_compile(re, PCRE2_JIT_COMPLETE);
lua_newtable(L); lua_newtable(L);
lua_pushlightuserdata(L, re); lua_pushlightuserdata(L, re);
lua_rawseti(L, -2, 1); lua_rawseti(L, -2, 1);
@ -56,19 +177,21 @@ static int f_pcre_compile(lua_State *L) {
// (including the whole match), if a match was found. // (including the whole match), if a match was found.
static int f_pcre_match(lua_State *L) { static int f_pcre_match(lua_State *L) {
size_t len, offset = 1, opts = 0; size_t len, offset = 1, opts = 0;
luaL_checktype(L, 1, LUA_TTABLE); bool regex_compiled = false;
pcre2_code* re = regex_get_pattern(L, &regex_compiled);
if (!re) return 0 ;
const char* str = luaL_checklstring(L, 2, &len); const char* str = luaL_checklstring(L, 2, &len);
if (lua_gettop(L) > 2) if (lua_gettop(L) > 2)
offset = luaL_checknumber(L, 3); offset = regex_offset_relative(luaL_checknumber(L, 3), len);
offset -= 1; offset -= 1;
len -= offset; len -= offset;
if (lua_gettop(L) > 3) if (lua_gettop(L) > 3)
opts = luaL_checknumber(L, 4); opts = luaL_checknumber(L, 4);
lua_rawgeti(L, 1, 1); lua_rawgeti(L, 1, 1);
pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1);
pcre2_match_data* md = pcre2_match_data_create_from_pattern(re, NULL); pcre2_match_data* md = pcre2_match_data_create_from_pattern(re, NULL);
int rc = pcre2_match(re, (PCRE2_SPTR)&str[offset], len, 0, opts, md, NULL); int rc = pcre2_match(re, (PCRE2_SPTR)&str[offset], len, 0, opts, md, NULL);
if (rc < 0) { if (rc < 0) {
if (regex_compiled) pcre2_code_free(re);
pcre2_match_data_free(md); pcre2_match_data_free(md);
if (rc != PCRE2_ERROR_NOMATCH) { if (rc != PCRE2_ERROR_NOMATCH) {
PCRE2_UCHAR buffer[120]; PCRE2_UCHAR buffer[120];
@ -84,18 +207,155 @@ static int f_pcre_match(lua_State *L) {
we just detect this case and give up. */ we just detect this case and give up. */
luaL_error(L, "regex matching error: \\K was used in an assertion to " luaL_error(L, "regex matching error: \\K was used in an assertion to "
" set the match start after its end"); " set the match start after its end");
if (regex_compiled) pcre2_code_free(re);
pcre2_match_data_free(md); pcre2_match_data_free(md);
return 0; return 0;
} }
for (int i = 0; i < rc*2; i++) for (int i = 0; i < rc*2; i++)
lua_pushinteger(L, ovector[i]+offset+1); lua_pushinteger(L, ovector[i]+offset+1);
if (regex_compiled) pcre2_code_free(re);
pcre2_match_data_free(md); pcre2_match_data_free(md);
return rc*2; return rc*2;
} }
static int f_pcre_gmatch(lua_State *L) {
/* pattern param */
bool regex_compiled = false;
pcre2_code* re = regex_get_pattern(L, &regex_compiled);
if (!re) return 0;
size_t subject_len = 0;
/* subject param */
const char* subject = luaL_checklstring(L, 2, &subject_len);
/* offset param */
size_t offset = regex_offset_relative(
luaL_optnumber(L, 3, 1), subject_len
) - 1;
/* keep strings on closure to avoid being collected */
lua_settop(L, 2);
RegexState *state;
state = (RegexState*)lua_newuserdata(L, sizeof(RegexState));
state->re = re;
state->match_data = pcre2_match_data_create_from_pattern(re, NULL);
state->subject = subject;
state->subject_len = subject_len;
state->offset = offset;
state->found = true;
state->regex_compiled = regex_compiled;
lua_pushcclosure(L, regex_gmatch_iterator, 3);
return 1;
}
static int f_pcre_gsub(lua_State *L) {
size_t subject_len = 0, replacement_len = 0;
bool regex_compiled = false;
pcre2_code* re = regex_get_pattern(L, &regex_compiled);
if (!re) return 0 ;
char* subject = (char*) luaL_checklstring(L, 2, &subject_len);
const char* replacement = luaL_checklstring(L, 3, &replacement_len);
int limit = luaL_optinteger(L, 4, 0);
if (limit < 0 ) limit = 0;
pcre2_match_data* match_data = pcre2_match_data_create_from_pattern(re, NULL);
size_t buffer_size = 1024;
char *output = (char *)malloc(buffer_size);
int options = PCRE2_SUBSTITUTE_OVERFLOW_LENGTH | PCRE2_SUBSTITUTE_EXTENDED;
if (limit == 0) options |= PCRE2_SUBSTITUTE_GLOBAL;
int results_count = 0;
int limit_count = 0;
bool done = false;
size_t offset = 0;
PCRE2_SIZE outlen = buffer_size;
while (!done) {
results_count = pcre2_substitute(
re,
(PCRE2_SPTR)subject, subject_len,
offset, options,
match_data, NULL,
(PCRE2_SPTR)replacement, replacement_len,
(PCRE2_UCHAR*)output, &outlen
);
if (results_count != PCRE2_ERROR_NOMEMORY || buffer_size >= outlen) {
/* PCRE2_SUBSTITUTE_GLOBAL code path (fastest) */
if(limit == 0) {
done = true;
/* non PCRE2_SUBSTITUTE_GLOBAL with limit code path (slower) */
} else {
size_t ovector_count = pcre2_get_ovector_count(match_data);
if (results_count > 0 && ovector_count > 0) {
limit_count++;
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data);
if (outlen > subject_len) {
offset = ovector[1] + (outlen - subject_len);
} else {
offset = ovector[1] - (subject_len - outlen);
}
if (limit_count > 1) free(subject);
if (limit_count == limit || offset-1 == outlen) {
done = true;
results_count = limit_count;
} else {
subject = output;
subject_len = outlen;
output = (char *)malloc(buffer_size);
outlen = buffer_size;
}
} else {
if (limit_count > 1) {
free(subject);
}
done = true;
results_count = limit_count;
}
}
} else {
buffer_size = outlen;
output = (char *)realloc(output, buffer_size);
}
}
int return_count = 0;
if (results_count > 0) {
lua_pushlstring(L, (const char*) output, outlen);
lua_pushinteger(L, results_count);
return_count = 2;
} else if (results_count == 0) {
lua_pushlstring(L, subject, subject_len);
lua_pushinteger(L, 0);
return_count = 2;
}
free(output);
pcre2_match_data_free(match_data);
if (regex_compiled)
pcre2_code_free(re);
if (results_count < 0) {
PCRE2_UCHAR errmsg[256];
pcre2_get_error_message(results_count, errmsg, sizeof(errmsg));
return luaL_error(L, "regex substitute error: %s", errmsg);
}
return return_count;
}
static const luaL_Reg lib[] = { static const luaL_Reg lib[] = {
{ "compile", f_pcre_compile }, { "compile", f_pcre_compile },
{ "cmatch", f_pcre_match }, { "cmatch", f_pcre_match },
{ "gmatch", f_pcre_gmatch },
{ "gsub", f_pcre_gsub },
{ "__gc", f_pcre_gc }, { "__gc", f_pcre_gc },
{ NULL, NULL } { NULL, NULL }
}; };

View File

@ -1,7 +1,16 @@
#include <string.h> #include <string.h>
#include "api.h" #include "api.h"
<<<<<<< HEAD
#include "renderer.h" #include "renderer.h"
#include "rencache.h" #include "rencache.h"
=======
#include "../renderer.h"
#include "../rencache.h"
#include "lua.h"
// a reference index to a table that stores the fonts
static int RENDERER_FONT_REF = LUA_NOREF;
>>>>>>> master
static int font_get_options( static int font_get_options(
lua_State *L, lua_State *L,
@ -137,7 +146,22 @@ static int f_font_copy(lua_State *L) {
} }
static int f_font_group(lua_State* L) { static int f_font_group(lua_State* L) {
int table_size;
luaL_checktype(L, 1, LUA_TTABLE); luaL_checktype(L, 1, LUA_TTABLE);
table_size = lua_rawlen(L, 1);
if (table_size <= 0)
return luaL_error(L, "failed to create font group: table is empty");
if (table_size > FONT_FALLBACK_MAX)
return luaL_error(L, "failed to create font group: table size too large");
// we also need to ensure that there are no fontgroups inside it
for (int i = 1; i <= table_size; i++) {
if (lua_rawgeti(L, 1, i) != LUA_TUSERDATA)
return luaL_typeerror(L, -1, API_TYPE_FONT "(userdata)");
lua_pop(L, 1);
}
luaL_setmetatable(L, API_TYPE_FONT); luaL_setmetatable(L, API_TYPE_FONT);
return 1; return 1;
} }
@ -176,7 +200,10 @@ static int f_font_gc(lua_State *L) {
static int f_font_get_width(lua_State *L) { static int f_font_get_width(lua_State *L) {
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1); RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
lua_pushnumber(L, ren_font_group_get_width(fonts, luaL_checkstring(L, 2))); size_t len;
const char *text = luaL_checklstring(L, 2, &len);
lua_pushnumber(L, ren_font_group_get_width(fonts, text, len));
return 1; return 1;
} }
@ -199,19 +226,47 @@ static int f_font_set_size(lua_State *L) {
return 0; return 0;
} }
static int color_value_error(lua_State *L, int idx, int table_idx) {
const char *type, *msg;
// generate an appropriate error message
if (luaL_getmetafield(L, -1, "__name") == LUA_TSTRING) {
type = lua_tostring(L, -1); // metatable name
} else if (lua_type(L, -1) == LUA_TLIGHTUSERDATA) {
type = "light userdata"; // special name for light userdata
} else {
type = lua_typename(L, lua_type(L, -1)); // default name
}
// the reason it went through so much hoops is to generate the correct error
// message (with function name and proper index).
msg = lua_pushfstring(L, "table[%d]: %s expected, got %s", table_idx, lua_typename(L, LUA_TNUMBER), type);
return luaL_argerror(L, idx, msg);
}
static int get_color_value(lua_State *L, int idx, int table_idx) {
lua_rawgeti(L, idx, table_idx);
return lua_isnumber(L, -1) ? lua_tonumber(L, -1) : color_value_error(L, idx, table_idx);
}
static int get_color_value_opt(lua_State *L, int idx, int table_idx, int default_value) {
lua_rawgeti(L, idx, table_idx);
if (lua_isnoneornil(L, -1))
return default_value;
else if (lua_isnumber(L, -1))
return lua_tonumber(L, -1);
else
return color_value_error(L, idx, table_idx);
}
static RenColor checkcolor(lua_State *L, int idx, int def) { static RenColor checkcolor(lua_State *L, int idx, int def) {
RenColor color; RenColor color;
if (lua_isnoneornil(L, idx)) { if (lua_isnoneornil(L, idx)) {
return (RenColor) { def, def, def, 255 }; return (RenColor) { def, def, def, 255 };
} }
lua_rawgeti(L, idx, 1); luaL_checktype(L, idx, LUA_TTABLE);
lua_rawgeti(L, idx, 2); color.r = get_color_value(L, idx, 1);
lua_rawgeti(L, idx, 3); color.g = get_color_value(L, idx, 2);
lua_rawgeti(L, idx, 4); color.b = get_color_value(L, idx, 3);
color.r = luaL_checknumber(L, -4); color.a = get_color_value_opt(L, idx, 4, 255);
color.g = luaL_checknumber(L, -3);
color.b = luaL_checknumber(L, -2);
color.a = luaL_optnumber(L, -1, 255);
lua_pop(L, 4); lua_pop(L, 4);
return color; return color;
} }
@ -241,6 +296,9 @@ static int f_begin_frame(UNUSED lua_State *L) {
static int f_end_frame(UNUSED lua_State *L) { static int f_end_frame(UNUSED lua_State *L) {
rencache_end_frame(); rencache_end_frame();
// clear the font reference table
lua_newtable(L);
lua_rawseti(L, LUA_REGISTRYINDEX, RENDERER_FONT_REF);
return 0; return 0;
} }
@ -277,11 +335,25 @@ static int f_draw_rect(lua_State *L) {
static int f_draw_text(lua_State *L) { static int f_draw_text(lua_State *L) {
RenFont* fonts[FONT_FALLBACK_MAX]; RenFont* fonts[FONT_FALLBACK_MAX];
font_retrieve(L, fonts, 1); font_retrieve(L, fonts, 1);
const char *text = luaL_checkstring(L, 2);
// stores a reference to this font to the reference table
lua_rawgeti(L, LUA_REGISTRYINDEX, RENDERER_FONT_REF);
if (lua_istable(L, -1))
{
lua_pushvalue(L, 1);
lua_pushboolean(L, 1);
lua_rawset(L, -3);
} else {
fprintf(stderr, "warning: failed to reference count fonts\n");
}
lua_pop(L, 1);
size_t len;
const char *text = luaL_checklstring(L, 2, &len);
float x = luaL_checknumber(L, 3); float x = luaL_checknumber(L, 3);
int y = luaL_checknumber(L, 4); int y = luaL_checknumber(L, 4);
RenColor color = checkcolor(L, 5, 255); RenColor color = checkcolor(L, 5, 255);
x = rencache_draw_text(fonts, text, x, y, color); x = rencache_draw_text(fonts, text, len, x, y, color);
lua_pushnumber(L, x); lua_pushnumber(L, x);
return 1; return 1;
} }
@ -312,6 +384,10 @@ static const luaL_Reg fontLib[] = {
}; };
int luaopen_renderer(lua_State *L) { int luaopen_renderer(lua_State *L) {
// gets a reference on the registry to store font data
lua_newtable(L);
RENDERER_FONT_REF = luaL_ref(L, LUA_REGISTRYINDEX);
luaL_newlib(L, lib); luaL_newlib(L, lib);
luaL_newmetatable(L, API_TYPE_FONT); luaL_newmetatable(L, API_TYPE_FONT);
luaL_setfuncs(L, fontLib, 0); luaL_setfuncs(L, fontLib, 0);

View File

@ -12,6 +12,20 @@
#include <windows.h> #include <windows.h>
#include <fileapi.h> #include <fileapi.h>
#include "../utfconv.h" #include "../utfconv.h"
// Windows does not define the S_ISREG and S_ISDIR macros in stat.h, so we do.
// We have to define _CRT_INTERNAL_NONSTDC_NAMES 1 before #including sys/stat.h
// in order for Microsoft's stat.h to define names like S_IFMT, S_IFREG, and S_IFDIR,
// rather than just defining _S_IFMT, _S_IFREG, and _S_IFDIR as it normally does.
#define _CRT_INTERNAL_NONSTDC_NAMES 1
#include <sys/types.h>
#include <sys/stat.h>
#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
#if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif
#else #else
#include <dirent.h> #include <dirent.h>
@ -169,8 +183,9 @@ static void push_win32_error(lua_State *L, DWORD rc) {
static int f_poll_event(lua_State *L) { static int f_poll_event(lua_State *L) {
char buf[16]; char buf[16];
int mx, my, wx, wy; int mx, my, w, h;
SDL_Event e; SDL_Event e;
SDL_Event event_plus;
top: top:
if ( !SDL_PollEvent(&e) ) { if ( !SDL_PollEvent(&e) ) {
@ -220,12 +235,11 @@ top:
goto top; goto top;
case SDL_DROPFILE: case SDL_DROPFILE:
SDL_GetGlobalMouseState(&mx, &my); SDL_GetMouseState(&mx, &my);
SDL_GetWindowPosition(window, &wx, &wy);
lua_pushstring(L, "filedropped"); lua_pushstring(L, "filedropped");
lua_pushstring(L, e.drop.file); lua_pushstring(L, e.drop.file);
lua_pushinteger(L, mx - wx); lua_pushinteger(L, mx);
lua_pushinteger(L, my - wy); lua_pushinteger(L, my);
SDL_free(e.drop.file); SDL_free(e.drop.file);
return 4; return 4;
@ -261,6 +275,23 @@ top:
lua_pushstring(L, e.text.text); lua_pushstring(L, e.text.text);
return 2; return 2;
case SDL_TEXTEDITING:
lua_pushstring(L, "textediting");
lua_pushstring(L, e.edit.text);
lua_pushinteger(L, e.edit.start);
lua_pushinteger(L, e.edit.length);
return 4;
#if SDL_VERSION_ATLEAST(2, 0, 22)
case SDL_TEXTEDITING_EXT:
lua_pushstring(L, "textediting");
lua_pushstring(L, e.editExt.text);
lua_pushinteger(L, e.editExt.start);
lua_pushinteger(L, e.editExt.length);
SDL_free(e.editExt.text);
return 4;
#endif
case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONDOWN:
if (e.button.button == 1) { SDL_CaptureMouse(1); } if (e.button.button == 1) { SDL_CaptureMouse(1); }
lua_pushstring(L, "mousepressed"); lua_pushstring(L, "mousepressed");
@ -280,7 +311,6 @@ top:
case SDL_MOUSEMOTION: case SDL_MOUSEMOTION:
SDL_PumpEvents(); SDL_PumpEvents();
SDL_Event event_plus;
while (SDL_PeepEvents(&event_plus, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) { while (SDL_PeepEvents(&event_plus, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) {
e.motion.x = event_plus.motion.x; e.motion.x = event_plus.motion.x;
e.motion.y = event_plus.motion.y; e.motion.y = event_plus.motion.y;
@ -296,8 +326,51 @@ top:
case SDL_MOUSEWHEEL: case SDL_MOUSEWHEEL:
lua_pushstring(L, "mousewheel"); lua_pushstring(L, "mousewheel");
#if SDL_VERSION_ATLEAST(2, 0, 18)
lua_pushnumber(L, e.wheel.preciseY);
// Use -x to keep consistency with vertical scrolling values (e.g. shift+scroll)
lua_pushnumber(L, -e.wheel.preciseX);
#else
lua_pushinteger(L, e.wheel.y); lua_pushinteger(L, e.wheel.y);
return 2; lua_pushinteger(L, -e.wheel.x);
#endif
return 3;
case SDL_FINGERDOWN:
SDL_GetWindowSize(window, &w, &h);
lua_pushstring(L, "touchpressed");
lua_pushinteger(L, (lua_Integer)(e.tfinger.x * w));
lua_pushinteger(L, (lua_Integer)(e.tfinger.y * h));
lua_pushinteger(L, e.tfinger.fingerId);
return 4;
case SDL_FINGERUP:
SDL_GetWindowSize(window, &w, &h);
lua_pushstring(L, "touchreleased");
lua_pushinteger(L, (lua_Integer)(e.tfinger.x * w));
lua_pushinteger(L, (lua_Integer)(e.tfinger.y * h));
lua_pushinteger(L, e.tfinger.fingerId);
return 4;
case SDL_FINGERMOTION:
SDL_PumpEvents();
while (SDL_PeepEvents(&event_plus, 1, SDL_GETEVENT, SDL_FINGERMOTION, SDL_FINGERMOTION) > 0) {
e.tfinger.x = event_plus.tfinger.x;
e.tfinger.y = event_plus.tfinger.y;
e.tfinger.dx += event_plus.tfinger.dx;
e.tfinger.dy += event_plus.tfinger.dy;
}
SDL_GetWindowSize(window, &w, &h);
lua_pushstring(L, "touchmoved");
lua_pushinteger(L, (lua_Integer)(e.tfinger.x * w));
lua_pushinteger(L, (lua_Integer)(e.tfinger.y * h));
lua_pushinteger(L, (lua_Integer)(e.tfinger.dx * w));
lua_pushinteger(L, (lua_Integer)(e.tfinger.dy * h));
lua_pushinteger(L, e.tfinger.fingerId);
return 6;
default: default:
goto top; goto top;
@ -441,6 +514,36 @@ static int f_get_window_mode(lua_State *L) {
return 1; return 1;
} }
static int f_set_text_input_rect(lua_State *L) {
SDL_Rect rect;
rect.x = luaL_checknumber(L, 1);
rect.y = luaL_checknumber(L, 2);
rect.w = luaL_checknumber(L, 3);
rect.h = luaL_checknumber(L, 4);
SDL_SetTextInputRect(&rect);
return 0;
}
static int f_clear_ime(lua_State *L) {
#if SDL_VERSION_ATLEAST(2, 0, 22)
SDL_ClearComposition();
#endif
return 0;
}
static int f_raise_window(lua_State *L) {
/*
SDL_RaiseWindow should be enough but on some window managers like the
one used on Gnome the window needs to first have input focus in order
to allow the window to be focused. Also on wayland the raise window event
may not always be obeyed.
*/
SDL_SetWindowInputFocus(window);
SDL_RaiseWindow(window);
return 0;
}
static int f_show_fatal_error(lua_State *L) { static int f_show_fatal_error(lua_State *L) {
const char *title = luaL_checkstring(L, 1); const char *title = luaL_checkstring(L, 1);
@ -448,19 +551,8 @@ static int f_show_fatal_error(lua_State *L) {
#ifdef _WIN32 #ifdef _WIN32
MessageBox(0, msg, title, MB_OK | MB_ICONERROR); MessageBox(0, msg, title, MB_OK | MB_ICONERROR);
#else #else
SDL_MessageBoxButtonData buttons[] = { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, msg, NULL);
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Ok" },
};
SDL_MessageBoxData data = {
.title = title,
.message = msg,
.numbuttons = 1,
.buttons = buttons,
};
int buttonid;
SDL_ShowMessageBox(&data, &buttonid);
#endif #endif
return 0; return 0;
} }
@ -517,7 +609,7 @@ static int f_list_dir(lua_State *L) {
#ifdef _WIN32 #ifdef _WIN32
lua_settop(L, 1); lua_settop(L, 1);
if (strchr("\\/", path[strlen(path) - 2]) != NULL) if (path[0] == 0 || strchr("\\/", path[strlen(path) - 1]) != NULL)
lua_pushstring(L, "*"); lua_pushstring(L, "*");
else else
lua_pushstring(L, "/*"); lua_pushstring(L, "/*");
@ -843,7 +935,7 @@ typedef struct lua_function_node {
#define P(FUNC) { "lua_" #FUNC, (fptr)(lua_##FUNC) } #define P(FUNC) { "lua_" #FUNC, (fptr)(lua_##FUNC) }
#define U(FUNC) { "luaL_" #FUNC, (fptr)(luaL_##FUNC) } #define U(FUNC) { "luaL_" #FUNC, (fptr)(luaL_##FUNC) }
static void* api_require(const char* symbol) { static void* api_require(const char* symbol) {
static lua_function_node nodes[] = { static const lua_function_node nodes[] = {
P(atpanic), P(checkstack), P(atpanic), P(checkstack),
P(close), P(concat), P(copy), P(createtable), P(dump), P(close), P(concat), P(copy), P(createtable), P(dump),
P(error), P(gc), P(getallocf), P(getfield), P(error), P(gc), P(getallocf), P(getfield),
@ -866,16 +958,21 @@ static void* api_require(const char* symbol) {
U(newmetatable), U(setmetatable), U(testudata), U(checkudata), U(where), U(newmetatable), U(setmetatable), U(testudata), U(checkudata), U(where),
U(error), U(fileresult), U(execresult), U(ref), U(unref), U(loadstring), U(error), U(fileresult), U(execresult), U(ref), U(unref), U(loadstring),
U(newstate), U(setfuncs), U(buffinit), U(addlstring), U(addstring), U(newstate), U(setfuncs), U(buffinit), U(addlstring), U(addstring),
U(addvalue), U(pushresult), {"api_load_libs", (void*)(api_load_libs)}, U(addvalue), U(pushresult), U(openlibs), {"api_load_libs", (void*)(api_load_libs)},
#if LUA_VERSION_NUM >= 502 #if LUA_VERSION_NUM >= 502
P(absindex), P(arith), P(callk), P(compare), P(getglobal), P(absindex), P(arith), P(callk), P(compare), P(getglobal),
P(len), P(pcallk), P(rawgetp), P(rawlen), P(rawsetp), P(setglobal), P(len), P(pcallk), P(rawgetp), P(rawlen), P(rawsetp), P(setglobal),
P(iscfunction), P(yieldk), P(iscfunction), P(yieldk),
U(checkversion_), U(tolstring), U(len), U(getsubtable), U(prepbuffsize), U(checkversion_), U(tolstring), U(len), U(getsubtable), U(prepbuffsize),
U(pushresultsize), U(buffinitsize), U(checklstring), U(checkoption), U(gsub), U(loadbufferx), U(pushresultsize), U(buffinitsize), U(checklstring), U(checkoption), U(gsub), U(loadbufferx),
U(loadfilex), U(optinteger), U(optlstring), U(requiref), U(traceback) U(loadfilex), U(optinteger), U(optlstring), U(requiref), U(traceback),
#else #else
P(objlen) P(objlen),
#endif
#if LUA_VERSION_NUM >= 504
P(newuserdatauv), P(setiuservalue), P(getiuservalue)
#else
P(newuserdata), P(setuservalue), P(getuservalue)
#endif #endif
}; };
@ -886,6 +983,14 @@ static void* api_require(const char* symbol) {
return NULL; return NULL;
} }
static int f_library_gc(lua_State *L) {
lua_getfield(L, 1, "handle");
void* handle = lua_touserdata(L, -1);
SDL_UnloadObject(handle);
return 0;
}
static int f_load_native_plugin(lua_State *L) { static int f_load_native_plugin(lua_State *L) {
char entrypoint_name[512]; entrypoint_name[sizeof(entrypoint_name) - 1] = '\0'; char entrypoint_name[512]; entrypoint_name[sizeof(entrypoint_name) - 1] = '\0';
int result; int result;
@ -898,9 +1003,12 @@ static int f_load_native_plugin(lua_State *L) {
lua_getglobal(L, "package"); lua_getglobal(L, "package");
lua_getfield(L, -1, "native_plugins"); lua_getfield(L, -1, "native_plugins");
lua_newtable(L);
lua_pushlightuserdata(L, library); lua_pushlightuserdata(L, library);
lua_setfield(L, -2, "handle");
luaL_setmetatable(L, API_TYPE_NATIVE_PLUGIN);
lua_setfield(L, -2, name); lua_setfield(L, -2, name);
lua_pop(L, 1); lua_pop(L, 2);
const char *basename = strrchr(name, '.'); const char *basename = strrchr(name, '.');
basename = !basename ? name : basename + 1; basename = !basename ? name : basename + 1;
@ -993,7 +1101,10 @@ static const luaL_Reg lib[] = {
{ "set_window_hit_test", f_set_window_hit_test }, { "set_window_hit_test", f_set_window_hit_test },
{ "get_window_size", f_get_window_size }, { "get_window_size", f_get_window_size },
{ "set_window_size", f_set_window_size }, { "set_window_size", f_set_window_size },
{ "set_text_input_rect", f_set_text_input_rect },
{ "clear_ime", f_clear_ime },
{ "window_has_focus", f_window_has_focus }, { "window_has_focus", f_window_has_focus },
{ "raise_window", f_raise_window },
{ "show_fatal_error", f_show_fatal_error }, { "show_fatal_error", f_show_fatal_error },
{ "rmdir", f_rmdir }, { "rmdir", f_rmdir },
{ "chdir", f_chdir }, { "chdir", f_chdir },
@ -1017,6 +1128,9 @@ static const luaL_Reg lib[] = {
int luaopen_system(lua_State *L) { int luaopen_system(lua_State *L) {
luaL_newmetatable(L, API_TYPE_NATIVE_PLUGIN);
lua_pushcfunction(L, f_library_gc);
lua_setfield(L, -2, "__gc");
luaL_newlib(L, lib); luaL_newlib(L, lib);
return 1; return 1;
} }

View File

@ -1,19 +1,27 @@
/* /*
* Integration of https://github.com/starwing/luautf8 * Integration of https://github.com/starwing/luautf8
* *
* MIT License
*
* Copyright (c) 2018 Xavier Wang * Copyright (c) 2018 Xavier Wang
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission is hereby granted, free of charge, to any person obtaining a copy
* purpose with or without fee is hereby granted, provided that the above * of this software and associated documentation files (the "Software"), to deal
* copyright notice and this permission notice appear in all copies. * in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* *
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * The above copyright notice and this permission notice shall be included in all
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * copies or substantial portions of the Software.
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR *
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/ */
#include <lua.h> #include <lua.h>
@ -369,7 +377,7 @@ static int Lutf8_codepoint (lua_State *L) {
luaL_checkstack(L, n, "string slice too long"); luaL_checkstack(L, n, "string slice too long");
n = 0; /* count the number of returns */ n = 0; /* count the number of returns */
se = s + pose; /* string end */ se = s + pose; /* string end */
for (n = 0, s += posi - 1; s < se;) { for (s += posi - 1; s < se;) {
utfint code = 0; utfint code = 0;
s = utf8_safe_decode(L, s, &code); s = utf8_safe_decode(L, s, &code);
if (!lax && utf8_invalid(code)) if (!lax && utf8_invalid(code))

View File

@ -5,6 +5,8 @@
#include "rencache.h" #include "rencache.h"
#include "renderer.h" #include "renderer.h"
#include <signal.h>
#if defined(__amigaos4__) || defined(__morphos__) #if defined(__amigaos4__) || defined(__morphos__)
#define VSTRING "Lite XL 2.1.0r1 (10.10.2022)" #define VSTRING "Lite XL 2.1.0r1 (10.10.2022)"
#define VERSTAG "\0$VER: " VSTRING #define VERSTAG "\0$VER: " VSTRING
@ -12,19 +14,20 @@
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#elif __linux__ || __FreeBSD__ #elif defined(__linux__)
#include <unistd.h> #include <unistd.h>
#include <signal.h> #elif defined(__APPLE__)
#elif __APPLE__
#include <mach-o/dyld.h> #include <mach-o/dyld.h>
#elif __amigaos4__ #elif defined(__amigaos4__)
#include "platform/amigaos4.h" #include "platform/amigaos4.h"
static CONST_STRPTR stack USED = "$STACK:102400"; static CONST_STRPTR stack USED = "$STACK:102400";
static CONST_STRPTR version USED = VERSTAG; static CONST_STRPTR version USED = VERSTAG;
#elif __morphos__ #elif defined(__morphos__)
#include "platform/morphos.h" #include "platform/morphos.h"
unsigned long __stack = 1000000; unsigned long __stack = 1000000;
UBYTE VString[] = VERSTAG; UBYTE VString[] = VERSTAG;
#elif defined(__FreeBSD__)
#include <sys/sysctl.h>
#endif #endif
@ -46,8 +49,9 @@ static void get_exe_filename(char *buf, int sz) {
buf[len] = '\0'; buf[len] = '\0';
#elif __linux__ #elif __linux__
char path[] = "/proc/self/exe"; char path[] = "/proc/self/exe";
int len = readlink(path, buf, sz - 1); ssize_t len = readlink(path, buf, sz - 1);
buf[len] = '\0'; if (len > 0)
buf[len] = '\0';
#elif __APPLE__ #elif __APPLE__
/* use realpath to resolve a symlink if the process was launched from one. /* use realpath to resolve a symlink if the process was launched from one.
** This happens when Homebrew installs a cack and creates a symlink in ** This happens when Homebrew installs a cack and creates a symlink in
@ -58,8 +62,12 @@ static void get_exe_filename(char *buf, int sz) {
realpath(exepath, buf); realpath(exepath, buf);
#elif defined(__amigaos4__) || defined(__morphos__) #elif defined(__amigaos4__) || defined(__morphos__)
strcpy(buf, _fullpath("./lite")); strcpy(buf, _fullpath("./lite"));
#elif __FreeBSD__
size_t len = sz;
const int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
sysctl(mib, 4, buf, &len, NULL, 0);
#else #else
strcpy(buf, "./lite"); *buf = 0;
#endif #endif
} }
@ -98,30 +106,39 @@ void set_macos_bundle_resources(lua_State *L);
#endif #endif
#ifndef LITE_ARCH_TUPLE #ifndef LITE_ARCH_TUPLE
#if __x86_64__ || _WIN64 || __MINGW64__ // https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-140
#if defined(__x86_64__) || defined(_M_AMD64) || defined(__MINGW64__)
#define ARCH_PROCESSOR "x86_64" #define ARCH_PROCESSOR "x86_64"
#elif __aarch64__
#define ARCH_PROCESSOR "aarch64"
#elif __arm__ #elif __arm__
#define ARCH_PROCESSOR "arm" #define ARCH_PROCESSOR "arm"
#elif __amigaos4__ || __morphos__ #elif defined(__amigaos4__) || defined(__morphos__)
#define ARCH_PROCESSOR "ppc" #define ARCH_PROCESSOR "ppc"
#else #elif defined(__i386__) || defined(_M_IX86) || defined(__MINGW32__)
#define ARCH_PROCESSOR "x86" #define ARCH_PROCESSOR "x86"
#elif defined(__aarch64__) || defined(_M_ARM64) || defined (_M_ARM64EC)
#define ARCH_PROCESSOR "aarch64"
#elif defined(__arm__) || defined(_M_ARM)
#define ARCH_PROCESSOR "arm"
#endif #endif
#if _WIN32 #if _WIN32
#define ARCH_PLATFORM "windows" #define ARCH_PLATFORM "windows"
#elif __linux__ #elif __linux__
#define ARCH_PLATFORM "linux" #define ARCH_PLATFORM "linux"
#elif __FreeBSD__
#define ARCH_PLATFORM "freebsd"
#elif __APPLE__ #elif __APPLE__
#define ARCH_PLATFORM "darwin" #define ARCH_PLATFORM "darwin"
#elif __amigaos4__ #elif __amigaos4__
#define ARCH_PLATFORM "amigaos4" #define ARCH_PLATFORM "amigaos4"
#elif __morphos__ #elif __morphos__
#define ARCH_PLATFORM "morphos" #define ARCH_PLATFORM "morphos"
#else #endif
#if !defined(ARCH_PROCESSOR) || !defined(ARCH_PLATFORM)
#error "Please define -DLITE_ARCH_TUPLE." #error "Please define -DLITE_ARCH_TUPLE."
#endif #endif
#define LITE_ARCH_TUPLE ARCH_PROCESSOR "-" ARCH_PLATFORM #define LITE_ARCH_TUPLE ARCH_PROCESSOR "-" ARCH_PLATFORM
#endif #endif
@ -130,7 +147,7 @@ int main(int argc, char **argv) {
HINSTANCE lib = LoadLibrary("user32.dll"); HINSTANCE lib = LoadLibrary("user32.dll");
int (*SetProcessDPIAware)() = (void*) GetProcAddress(lib, "SetProcessDPIAware"); int (*SetProcessDPIAware)() = (void*) GetProcAddress(lib, "SetProcessDPIAware");
SetProcessDPIAware(); SetProcessDPIAware();
#elif defined(__morphos__) #elif defined(__amigaos4__) || defined(__morphos__)
setlocale(LC_ALL, "C"); setlocale(LC_ALL, "C");
#else #else
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
@ -150,6 +167,12 @@ int main(int argc, char **argv) {
#if SDL_VERSION_ATLEAST(2, 0, 5) #if SDL_VERSION_ATLEAST(2, 0, 5)
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
#endif #endif
#if SDL_VERSION_ATLEAST(2, 0, 18)
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
#endif
#if SDL_VERSION_ATLEAST(2, 0, 22)
SDL_SetHint(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, "1");
#endif
#if SDL_VERSION_ATLEAST(2, 0, 8) #if SDL_VERSION_ATLEAST(2, 0, 8)
/* This hint tells SDL to respect borderless window as a normal window. /* This hint tells SDL to respect borderless window as a normal window.
@ -206,7 +229,12 @@ init_lua:
char exename[2048]; char exename[2048];
get_exe_filename(exename, sizeof(exename)); get_exe_filename(exename, sizeof(exename));
lua_pushstring(L, exename); if (*exename) {
lua_pushstring(L, exename);
} else {
// get_exe_filename failed
lua_pushstring(L, argv[0]);
}
lua_setglobal(L, "EXEFILE"); lua_setglobal(L, "EXEFILE");
#ifdef __APPLE__ #ifdef __APPLE__

View File

@ -15,6 +15,8 @@ lite_sources = [
if get_option('dirmonitor_backend') == '' if get_option('dirmonitor_backend') == ''
if cc.has_function('inotify_init', prefix : '#include<sys/inotify.h>') if cc.has_function('inotify_init', prefix : '#include<sys/inotify.h>')
dirmonitor_backend = 'inotify' dirmonitor_backend = 'inotify'
elif host_machine.system() == 'darwin' and cc.check_header('CoreServices/CoreServices.h')
dirmonitor_backend = 'fsevents'
elif cc.has_function('kqueue', prefix : '#include<sys/event.h>') elif cc.has_function('kqueue', prefix : '#include<sys/event.h>')
dirmonitor_backend = 'kqueue' dirmonitor_backend = 'kqueue'
elif dependency('libkqueue', required : false).found() elif dependency('libkqueue', required : false).found()
@ -63,5 +65,5 @@ executable('lite-xl',
link_args: lite_link_args, link_args: lite_link_args,
install_dir: lite_bindir, install_dir: lite_bindir,
install: true, install: true,
gui_app: true, win_subsystem: 'windows',
) )

View File

@ -2,9 +2,19 @@
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdalign.h>
#include <string.h> #include <string.h>
#ifdef _MSC_VER
#ifndef alignof
#define alignof _Alignof
#endif
/* max_align_t is a compiler defined type, but
** MSVC doesn't provide it, so we'll have to improvise */
typedef long double max_align_t;
#else
#include <stdalign.h>
#endif
#include <lauxlib.h> #include <lauxlib.h>
#include "rencache.h" #include "rencache.h"
@ -16,7 +26,8 @@
#define CELLS_X 80 #define CELLS_X 80
#define CELLS_Y 50 #define CELLS_Y 50
#define CELL_SIZE 96 #define CELL_SIZE 96
#define COMMAND_BUF_SIZE (1024 * 512) #define CMD_BUF_RESIZE_RATE 1.2
#define CMD_BUF_INIT_SIZE (1024 * 512)
#define COMMAND_BARE_SIZE offsetof(Command, text) #define COMMAND_BARE_SIZE offsetof(Command, text)
enum { SET_CLIP, DRAW_TEXT, DRAW_RECT }; enum { SET_CLIP, DRAW_TEXT, DRAW_RECT };
@ -29,6 +40,7 @@ typedef struct {
RenColor color; RenColor color;
RenFont *fonts[FONT_FALLBACK_MAX]; RenFont *fonts[FONT_FALLBACK_MAX];
float text_x; float text_x;
size_t len;
char text[]; char text[];
} Command; } Command;
@ -37,13 +49,15 @@ static unsigned cells_buf2[CELLS_X * CELLS_Y];
static unsigned *cells_prev = cells_buf1; static unsigned *cells_prev = cells_buf1;
static unsigned *cells = cells_buf2; static unsigned *cells = cells_buf2;
static RenRect rect_buf[CELLS_X * CELLS_Y / 2]; static RenRect rect_buf[CELLS_X * CELLS_Y / 2];
static char command_buf[COMMAND_BUF_SIZE]; size_t command_buf_size = 0;
uint8_t *command_buf = NULL;
static bool resize_issue;
static int command_buf_idx; static int command_buf_idx;
static RenRect screen_rect; static RenRect screen_rect;
static bool show_debug; static bool show_debug;
static inline int min(int a, int b) { return a < b ? a : b; } static inline int rencache_min(int a, int b) { return a < b ? a : b; }
static inline int max(int a, int b) { return a > b ? a : b; } static inline int rencache_max(int a, int b) { return a > b ? a : b; }
/* 32bit fnv-1a hash */ /* 32bit fnv-1a hash */
@ -69,32 +83,54 @@ static inline bool rects_overlap(RenRect a, RenRect b) {
static RenRect intersect_rects(RenRect a, RenRect b) { static RenRect intersect_rects(RenRect a, RenRect b) {
int x1 = max(a.x, b.x); int x1 = rencache_max(a.x, b.x);
int y1 = max(a.y, b.y); int y1 = rencache_max(a.y, b.y);
int x2 = min(a.x + a.width, b.x + b.width); int x2 = rencache_min(a.x + a.width, b.x + b.width);
int y2 = min(a.y + a.height, b.y + b.height); int y2 = rencache_min(a.y + a.height, b.y + b.height);
return (RenRect) { x1, y1, max(0, x2 - x1), max(0, y2 - y1) }; return (RenRect) { x1, y1, rencache_max(0, x2 - x1), rencache_max(0, y2 - y1) };
} }
static RenRect merge_rects(RenRect a, RenRect b) { static RenRect merge_rects(RenRect a, RenRect b) {
int x1 = min(a.x, b.x); int x1 = rencache_min(a.x, b.x);
int y1 = min(a.y, b.y); int y1 = rencache_min(a.y, b.y);
int x2 = max(a.x + a.width, b.x + b.width); int x2 = rencache_max(a.x + a.width, b.x + b.width);
int y2 = max(a.y + a.height, b.y + b.height); int y2 = rencache_max(a.y + a.height, b.y + b.height);
return (RenRect) { x1, y1, x2 - x1, y2 - y1 }; return (RenRect) { x1, y1, x2 - x1, y2 - y1 };
} }
static bool expand_command_buffer() {
size_t new_size = command_buf_size * CMD_BUF_RESIZE_RATE;
if (new_size == 0) {
new_size = CMD_BUF_INIT_SIZE;
}
uint8_t *new_command_buf = realloc(command_buf, new_size);
if (!new_command_buf) {
return false;
}
command_buf_size = new_size;
command_buf = new_command_buf;
return true;
}
static Command* push_command(int type, int size) { static Command* push_command(int type, int size) {
size_t alignment = alignof(max_align_t) - 1; if (resize_issue) {
size = (size + alignment) & ~alignment; // Don't push new commands as we had problems resizing the command buffer.
Command *cmd = (Command*) (command_buf + command_buf_idx); // Let's wait for the next frame.
int n = command_buf_idx + size;
if (n > COMMAND_BUF_SIZE) {
fprintf(stderr, "Warning: (" __FILE__ "): exhausted command buffer\n");
return NULL; return NULL;
} }
size_t alignment = alignof(max_align_t) - 1;
size = (size + alignment) & ~alignment;
int n = command_buf_idx + size;
while (n > command_buf_size) {
if (!expand_command_buffer()) {
fprintf(stderr, "Warning: (" __FILE__ "): unable to resize command buffer (%ld)\n",
(size_t)(command_buf_size * CMD_BUF_RESIZE_RATE));
resize_issue = true;
return NULL;
}
}
Command *cmd = (Command*) (command_buf + command_buf_idx);
command_buf_idx = n; command_buf_idx = n;
memset(cmd, 0, size); memset(cmd, 0, size);
cmd->type = type; cmd->type = type;
@ -135,12 +171,12 @@ void rencache_draw_rect(RenRect rect, RenColor color) {
} }
} }
float rencache_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor color) float rencache_draw_text(RenFont **fonts, const char *text, size_t len, float x, int y, RenColor color)
{ {
float width = ren_font_group_get_width(fonts, text); float width = ren_font_group_get_width(fonts, text, len);
RenRect rect = { x, y, (int)width, ren_font_group_get_height(fonts) }; RenRect rect = { x, y, (int)width, ren_font_group_get_height(fonts) };
if (rects_overlap(screen_rect, rect)) { if (rects_overlap(screen_rect, rect)) {
int sz = strlen(text) + 1; int sz = len + 1;
Command *cmd = push_command(DRAW_TEXT, COMMAND_BARE_SIZE + sz); Command *cmd = push_command(DRAW_TEXT, COMMAND_BARE_SIZE + sz);
if (cmd) { if (cmd) {
memcpy(cmd->text, text, sz); memcpy(cmd->text, text, sz);
@ -148,6 +184,7 @@ float rencache_draw_text(RenFont **fonts, const char *text, float x, int y, RenC
memcpy(cmd->fonts, fonts, sizeof(RenFont*)*FONT_FALLBACK_MAX); memcpy(cmd->fonts, fonts, sizeof(RenFont*)*FONT_FALLBACK_MAX);
cmd->rect = rect; cmd->rect = rect;
cmd->text_x = x; cmd->text_x = x;
cmd->len = len;
cmd->tab_size = ren_font_group_get_tab_size(fonts); cmd->tab_size = ren_font_group_get_tab_size(fonts);
} }
} }
@ -163,6 +200,7 @@ void rencache_invalidate(void) {
void rencache_begin_frame() { void rencache_begin_frame() {
/* reset all cells if the screen width/height has changed */ /* reset all cells if the screen width/height has changed */
int w, h; int w, h;
resize_issue = false;
ren_get_size(&w, &h); ren_get_size(&w, &h);
if (screen_rect.width != w || h != screen_rect.height) { if (screen_rect.width != w || h != screen_rect.height) {
screen_rect.width = w; screen_rect.width = w;
@ -256,7 +294,7 @@ void rencache_end_frame() {
break; break;
case DRAW_TEXT: case DRAW_TEXT:
ren_font_group_set_tab_size(cmd->fonts, cmd->tab_size); ren_font_group_set_tab_size(cmd->fonts, cmd->tab_size);
ren_draw_text(cmd->fonts, cmd->text, cmd->text_x, cmd->rect.y, cmd->color); ren_draw_text(cmd->fonts, cmd->text, cmd->len, cmd->text_x, cmd->rect.y, cmd->color);
break; break;
} }
} }

View File

@ -8,7 +8,7 @@
void rencache_show_debug(bool enable); void rencache_show_debug(bool enable);
void rencache_set_clip_rect(RenRect rect); void rencache_set_clip_rect(RenRect rect);
void rencache_draw_rect(RenRect rect, RenColor color); void rencache_draw_rect(RenRect rect, RenColor color);
float rencache_draw_text(RenFont **font, const char *text, float x, int y, RenColor color); float rencache_draw_text(RenFont **font, const char *text, size_t len, float x, int y, RenColor color);
void rencache_invalidate(void); void rencache_invalidate(void);
void rencache_begin_frame(); void rencache_begin_frame();
void rencache_end_frame(); void rencache_end_frame();

View File

@ -8,6 +8,11 @@
#include <freetype/ftoutln.h> #include <freetype/ftoutln.h>
#include FT_FREETYPE_H #include FT_FREETYPE_H
#ifdef _WIN32
#include <windows.h>
#include "utfconv.h"
#endif
#include "renderer.h" #include "renderer.h"
#include "renwindow.h" #include "renwindow.h"
@ -51,7 +56,11 @@ typedef struct RenFont {
ERenFontHinting hinting; ERenFontHinting hinting;
unsigned char style; unsigned char style;
unsigned short underline_thickness; unsigned short underline_thickness;
char path[1]; #ifdef _WIN32
unsigned char *file;
HANDLE file_handle;
#endif
char path[];
} RenFont; } RenFont;
static const char* utf8_to_codepoint(const char *p, unsigned *dst) { static const char* utf8_to_codepoint(const char *p, unsigned *dst) {
@ -206,9 +215,48 @@ static void font_clear_glyph_cache(RenFont* font) {
} }
RenFont* ren_font_load(const char* path, float size, ERenFontAntialiasing antialiasing, ERenFontHinting hinting, unsigned char style) { RenFont* ren_font_load(const char* path, float size, ERenFontAntialiasing antialiasing, ERenFontHinting hinting, unsigned char style) {
FT_Face face; FT_Face face = NULL;
if (FT_New_Face( library, path, 0, &face))
#ifdef _WIN32
HANDLE file = INVALID_HANDLE_VALUE;
DWORD read;
int font_file_len = 0;
unsigned char *font_file = NULL;
wchar_t *wpath = NULL;
if ((wpath = utfconv_utf8towc(path)) == NULL)
return NULL; return NULL;
if ((file = CreateFileW(wpath,
GENERIC_READ,
FILE_SHARE_READ, // or else we can't copy fonts
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL)) == INVALID_HANDLE_VALUE)
goto failure;
if ((font_file_len = GetFileSize(file, NULL)) == INVALID_FILE_SIZE)
goto failure;
font_file = check_alloc(malloc(font_file_len * sizeof(unsigned char)));
if (!ReadFile(file, font_file, font_file_len, &read, NULL) || read != font_file_len)
goto failure;
free(wpath);
wpath = NULL;
if (FT_New_Memory_Face(library, font_file, read, 0, &face))
goto failure;
#else
if (FT_New_Face(library, path, 0, &face))
return NULL;
#endif
const int surface_scale = renwin_surface_scale(&window_renderer); const int surface_scale = renwin_surface_scale(&window_renderer);
if (FT_Set_Pixel_Sizes(face, 0, (int)(size*surface_scale))) if (FT_Set_Pixel_Sizes(face, 0, (int)(size*surface_scale)))
goto failure; goto failure;
@ -223,17 +271,32 @@ RenFont* ren_font_load(const char* path, float size, ERenFontAntialiasing antial
font->hinting = hinting; font->hinting = hinting;
font->style = style; font->style = style;
#ifdef _WIN32
// we need to keep this for freetype
font->file = font_file;
font->file_handle = file;
#endif
if(FT_IS_SCALABLE(face)) if(FT_IS_SCALABLE(face))
font->underline_thickness = (unsigned short)((face->underline_thickness / (float)face->units_per_EM) * font->size); font->underline_thickness = (unsigned short)((face->underline_thickness / (float)face->units_per_EM) * font->size);
if(!font->underline_thickness) font->underline_thickness = ceil((double) font->height / 14.0); if(!font->underline_thickness) font->underline_thickness = ceil((double) font->height / 14.0);
if (FT_Load_Char(face, ' ', font_set_load_options(font))) if (FT_Load_Char(face, ' ', font_set_load_options(font))) {
free(font);
goto failure; goto failure;
}
font->space_advance = face->glyph->advance.x / 64.0f; font->space_advance = face->glyph->advance.x / 64.0f;
font->tab_advance = font->space_advance * 2; font->tab_advance = font->space_advance * 2;
return font; return font;
failure:
FT_Done_Face(face); failure:
#ifdef _WIN32
free(wpath);
free(font_file);
if (file != INVALID_HANDLE_VALUE) CloseHandle(file);
#endif
if (face != NULL)
FT_Done_Face(face);
return NULL; return NULL;
} }
@ -252,6 +315,10 @@ const char* ren_font_get_path(RenFont *font) {
void ren_font_free(RenFont* font) { void ren_font_free(RenFont* font) {
font_clear_glyph_cache(font); font_clear_glyph_cache(font);
FT_Done_Face(font->face); FT_Done_Face(font->face);
#ifdef _WIN32
free(font->file);
CloseHandle(font->file_handle);
#endif
free(font); free(font);
} }
@ -293,9 +360,9 @@ int ren_font_group_get_height(RenFont **fonts) {
return fonts[0]->height; return fonts[0]->height;
} }
float ren_font_group_get_width(RenFont **fonts, const char *text) { float ren_font_group_get_width(RenFont **fonts, const char *text, size_t len) {
float width = 0; float width = 0;
const char* end = text + strlen(text); const char* end = text + len;
GlyphMetric* metric = NULL; GlyphSet* set = NULL; GlyphMetric* metric = NULL; GlyphSet* set = NULL;
while (text < end) { while (text < end) {
unsigned int codepoint; unsigned int codepoint;
@ -309,7 +376,7 @@ float ren_font_group_get_width(RenFont **fonts, const char *text) {
return width / surface_scale; return width / surface_scale;
} }
float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor color) { float ren_draw_text(RenFont **fonts, const char *text, size_t len, float x, int y, RenColor color) {
SDL_Surface *surface = renwin_get_surface(&window_renderer); SDL_Surface *surface = renwin_get_surface(&window_renderer);
const RenRect clip = window_renderer.clip; const RenRect clip = window_renderer.clip;
@ -317,11 +384,11 @@ float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor
float pen_x = x * surface_scale; float pen_x = x * surface_scale;
y *= surface_scale; y *= surface_scale;
int bytes_per_pixel = surface->format->BytesPerPixel; int bytes_per_pixel = surface->format->BytesPerPixel;
const char* end = text + strlen(text); const char* end = text + len;
uint8_t* destination_pixels = surface->pixels; uint8_t* destination_pixels = surface->pixels;
int clip_end_x = clip.x + clip.width, clip_end_y = clip.y + clip.height; int clip_end_x = clip.x + clip.width, clip_end_y = clip.y + clip.height;
RenFont* last; RenFont* last = NULL;
float last_pen_x = x; float last_pen_x = x;
bool underline = fonts[0]->style & FONT_STYLE_UNDERLINE; bool underline = fonts[0]->style & FONT_STYLE_UNDERLINE;
bool strikethrough = fonts[0]->style & FONT_STYLE_STRIKETHROUGH; bool strikethrough = fonts[0]->style & FONT_STYLE_STRIKETHROUGH;
@ -458,8 +525,13 @@ void ren_draw_rect(RenRect rect, RenColor color) {
/*************** Window Management ****************/ /*************** Window Management ****************/
void ren_free_window_resources() { void ren_free_window_resources() {
extern uint8_t *command_buf;
extern size_t command_buf_size;
renwin_free(&window_renderer); renwin_free(&window_renderer);
SDL_FreeSurface(draw_rect_surface); SDL_FreeSurface(draw_rect_surface);
free(command_buf);
command_buf = NULL;
command_buf_size = 0;
} }
void ren_init(SDL_Window *win) { void ren_init(SDL_Window *win) {

View File

@ -11,7 +11,7 @@
#define UNUSED #define UNUSED
#endif #endif
#define FONT_FALLBACK_MAX 4 #define FONT_FALLBACK_MAX 10
typedef struct RenFont RenFont; typedef struct RenFont RenFont;
typedef enum { FONT_HINTING_NONE, FONT_HINTING_SLIGHT, FONT_HINTING_FULL } ERenFontHinting; typedef enum { FONT_HINTING_NONE, FONT_HINTING_SLIGHT, FONT_HINTING_FULL } ERenFontHinting;
typedef enum { FONT_ANTIALIASING_NONE, FONT_ANTIALIASING_GRAYSCALE, FONT_ANTIALIASING_SUBPIXEL } ERenFontAntialiasing; typedef enum { FONT_ANTIALIASING_NONE, FONT_ANTIALIASING_GRAYSCALE, FONT_ANTIALIASING_SUBPIXEL } ERenFontAntialiasing;
@ -28,8 +28,8 @@ int ren_font_group_get_height(RenFont **font);
float ren_font_group_get_size(RenFont **font); float ren_font_group_get_size(RenFont **font);
void ren_font_group_set_size(RenFont **font, float size); void ren_font_group_set_size(RenFont **font, float size);
void ren_font_group_set_tab_size(RenFont **font, int n); void ren_font_group_set_tab_size(RenFont **font, int n);
float ren_font_group_get_width(RenFont **font, const char *text); float ren_font_group_get_width(RenFont **font, const char *text, size_t len);
float ren_draw_text(RenFont **font, const char *text, float x, int y, RenColor color); float ren_draw_text(RenFont **font, const char *text, size_t len, float x, int y, RenColor color);
void ren_draw_rect(RenRect rect, RenColor color); void ren_draw_rect(RenRect rect, RenColor color);

View File

@ -1,6 +1,12 @@
#ifndef MBSEC_H #ifndef MBSEC_H
#define MBSEC_H #define MBSEC_H
#ifdef __GNUC__
#define UNUSED __attribute__((__unused__))
#else
#define UNUSED
#endif
#ifdef _WIN32 #ifdef _WIN32
#include <stdlib.h> #include <stdlib.h>
@ -8,7 +14,7 @@
#define UTFCONV_ERROR_INVALID_CONVERSION "Input contains invalid byte sequences." #define UTFCONV_ERROR_INVALID_CONVERSION "Input contains invalid byte sequences."
LPWSTR utfconv_utf8towc(const char *str) { static UNUSED LPWSTR utfconv_utf8towc(const char *str) {
LPWSTR output; LPWSTR output;
int len; int len;
@ -30,7 +36,7 @@ LPWSTR utfconv_utf8towc(const char *str) {
return output; return output;
} }
char *utfconv_wctoutf8(LPCWSTR str) { static UNUSED char *utfconv_wctoutf8(LPCWSTR str) {
char *output; char *output;
int len; int len;

View File

@ -1,9 +1,10 @@
[wrap-file] [wrap-file]
directory = freetype-2.11.1 directory = freetype-2.12.1
source_url = https://download.savannah.gnu.org/releases/freetype/freetype-2.11.1.tar.gz source_url = https://download.savannah.gnu.org/releases/freetype/freetype-2.12.1.tar.xz
source_filename = freetype-2.11.1.tar.gz source_filename = freetype-2.12.1.tar.xz
source_hash = f8db94d307e9c54961b39a1cc799a67d46681480696ed72ecf78d4473770f09b source_hash = 4766f20157cc4cf0cd292f80bf917f92d1c439b243ac3018debf6b9140c41a7f
wrapdb_version = 2.12.1-2
[provide] [provide]
freetype2 = freetype_dep freetype2 = freetype_dep
freetype = freetype_dep

View File

@ -1,11 +1,11 @@
[wrap-file] [wrap-file]
directory = lua-5.4.3 directory = lua-5.4.4
source_url = https://www.lua.org/ftp/lua-5.4.3.tar.gz source_url = https://www.lua.org/ftp/lua-5.4.4.tar.gz
source_filename = lua-5.4.3.tar.gz source_filename = lua-5.4.4.tar.gz
source_hash = f8612276169e3bfcbcfb8f226195bfc6e466fe13042f1076cbde92b7ec96bbfb source_hash = 164c7849653b80ae67bec4b7473b884bf5cc8d2dca05653475ec2ed27b9ebf61
patch_filename = lua_5.4.3-2_patch.zip patch_filename = lua_5.4.4-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/lua_5.4.3-2/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/lua_5.4.4-1/get_patch
patch_hash = 3c23ec14a3f000d80fe2e2fdddba63a56e13c758d74195daa4ff0da7bfdb02da patch_hash = e61cd965c629d6543176f41a9f1cb9050edfd1566cf00ce768ff211086e40bdc
[provide] [provide]
lua-5.4 = lua_dep lua-5.4 = lua_dep

View File

@ -1,15 +1,15 @@
[wrap-file] [wrap-file]
directory = pcre2-10.39 directory = pcre2-10.42
source_url = https://github.com/PhilipHazel/pcre2/releases/download/pcre2-10.39/pcre2-10.39.tar.bz2 source_url = https://github.com/PhilipHazel/pcre2/releases/download/pcre2-10.42/pcre2-10.42.tar.bz2
source_filename = pcre2-10.39.tar.bz2 source_filename = pcre2-10.42.tar.bz2
source_hash = 0f03caf57f81d9ff362ac28cd389c055ec2bf0678d277349a1a4bee00ad6d440 source_hash = 8d36cd8cb6ea2a4c2bb358ff6411b0c788633a2a45dabbf1aeb4b701d1b5e840
patch_filename = pcre2_10.39-2_patch.zip patch_filename = pcre2_10.42-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/pcre2_10.39-2/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/pcre2_10.42-1/get_patch
patch_hash = c4cfffff83e7bb239c8c330339b08f4367b019f79bf810f10c415e35fb09cf14 patch_hash = 06969e916dfee663c189810df57d98574f15e0754a44cd93f3f0bc7234b05d89
wrapdb_version = 10.42-1
[provide] [provide]
libpcre2-8 = -libpcre2_8 libpcre2-8 = libpcre2_8
libpcre2-16 = -libpcre2_16 libpcre2-16 = libpcre2_16
libpcre2-32 = -libpcre2_32 libpcre2-32 = libpcre2_32
libpcre2-posix = -libpcre2_posix libpcre2-posix = libpcre2_posix

View File

@ -1,12 +1,12 @@
[wrap-file] [wrap-file]
directory = SDL2-2.24.0 directory = SDL2-2.26.0
source_url = https://libsdl.org/release/SDL2-2.24.0.tar.gz source_url = https://github.com/libsdl-org/SDL/releases/download/release-2.26.0/SDL2-2.26.0.tar.gz
source_filename = SDL2-2.24.0.tar.gz source_filename = SDL2-2.26.0.tar.gz
source_hash = 91e4c34b1768f92d399b078e171448c6af18cafda743987ed2064a28954d6d97 source_hash = 8000d7169febce93c84b6bdf376631f8179132fd69f7015d4dadb8b9c2bdb295
patch_filename = sdl2_2.24.0-2_patch.zip patch_filename = sdl2_2.26.0-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/sdl2_2.24.0-2/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/sdl2_2.26.0-1/get_patch
patch_hash = ec296ed9a577b42131d2fdbfe5ca73a0cf133793c0290e1ccd825675464bfe32 patch_hash = 6fcfd727d71cf7837332723518d5e47ffd64f1e7630681cf4b50e99f2bf7676f
wrapdb_version = 2.24.0-2 wrapdb_version = 2.26.0-1
[provide] [provide]
sdl2 = sdl2_dep sdl2 = sdl2_dep