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

View File

@ -1,6 +1,166 @@
# 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
* Make distinction between
@ -124,6 +284,12 @@
* Added in ability to have init.so as a require for cpath.
([#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
* [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
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
([#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
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
[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.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

View File

@ -3,12 +3,9 @@ local command = require "core.command"
local common = require "core.common"
local config = require "core.config"
local translate = require "core.doc.translate"
local style = require "core.style"
local DocView = require "core.docview"
local function dv()
return core.active_view
end
local tokenizer = require "core.tokenizer"
local function doc()
@ -40,9 +37,24 @@ local function save(filename)
filename = core.normalize_to_project_dir(filename)
abs_filename = core.project_absolute_path(filename)
end
doc():save(filename, abs_filename)
local ok, err = pcall(doc().save, doc(), filename, abs_filename)
if ok then
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
local function cut_or_copy(delete)
@ -59,8 +71,9 @@ local function cut_or_copy(delete)
doc():delete_to_cursor(idx, 0)
end
else -- Cut/copy whole line
text = doc().lines[line1]
full_text = full_text == "" and text or (full_text .. text)
-- Remove newline from the text. It will be added as needed on paste.
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
if delete then
if line1 < #doc().lines then
@ -85,7 +98,15 @@ local function split_cursor(direction)
table.insert(new_cursors, { line1 + direction, col1 })
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()
end
@ -177,10 +198,30 @@ local function block_comment(comment, line1, col1, line2, col2)
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 = {
["doc:select-none"] = function(dv)
local line, col = dv.doc:get_selection()
dv.doc:set_selection(line, col)
local l1, c1 = dv.doc:get_selection_idx(dv.doc.last_selection)
if not l1 then
l1, c1 = dv.doc:get_selection_idx(1)
end
dv.doc:set_selection(l1, c1)
end,
["doc:cut"] = function()
@ -202,27 +243,51 @@ local commands = {
["doc:paste"] = function(dv)
local clipboard = system.get_clipboard()
-- If the clipboard has changed since our last look, use that instead
local external_paste = core.cursor_clipboard["full"] ~= clipboard
if external_paste then
if core.cursor_clipboard["full"] ~= clipboard then
core.cursor_clipboard = {}
core.cursor_clipboard_whole_line = {}
for idx in dv.doc:get_selections() do
insert_paste(dv.doc, clipboard, false, idx)
end
return
end
-- Use internal clipboard(s)
-- 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
local value, whole_line
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
if whole_line then
dv.doc:insert(line1, 1, value:gsub("\r", ""))
if col1 == 1 then
dv.doc:move_to_cursor(idx, #value)
-- 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
dv.doc:text_input(value:gsub("\r", ""), idx)
-- 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
end
end,
@ -234,7 +299,7 @@ local commands = {
indent = indent:sub(#indent + 2 - col)
end
-- 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)
end
dv.doc:text_input("\n" .. indent, idx)
@ -380,15 +445,28 @@ local commands = {
end,
["doc:toggle-block-comments"] = function(dv)
local comment = dv.doc.syntax.block_comment
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
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
-- if nothing is selected, toggle the whole line
if line1 == line2 and col1 == col2 then
col1 = 1
@ -399,9 +477,23 @@ local commands = {
end,
["doc:toggle-line-comments"] = function(dv)
local comment = dv.doc.syntax.comment or dv.doc.syntax.block_comment
if comment then
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 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))
end
end
@ -538,7 +630,7 @@ local commands = {
}
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 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

View File

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

View File

@ -123,5 +123,13 @@ command.add(nil, {
return true
end
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
})

View File

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

View File

@ -6,8 +6,19 @@ config.message_timeout = 5
config.mouse_wheel_scroll = 50 * SCALE
config.animate_drag_scroll = false
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.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.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
config.undo_merge_timeout = 0.3
@ -19,6 +30,7 @@ config.highlight_current_line = true
config.line_height = 1.2
config.indent_size = 2
config.tab_type = "soft"
config.keep_newline_whitespace = false
config.line_limit = 80
config.max_project_files = 2000
config.transitions = true
@ -47,8 +59,9 @@ config.plugins = {}
-- Allow you to set plugin configs even if we haven't seen the plugin before.
setmetatable(config.plugins, {
__index = function(t, k)
if rawget(t, k) == nil then rawset(t, k, {}) end
return rawget(t, k)
local v = rawget(t, k)
if v == true or v == nil then v = {} rawset(t, k, v) end
return v
end
})

View File

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

View File

@ -14,8 +14,8 @@ function dirwatch.new()
watched = {},
reverse_watched = {},
monitor = dirmonitor.new(),
windows_watch_top = nil,
windows_watch_count = 0
single_watch_top = nil,
single_watch_count = 0
}
setmetatable(t, dirwatch)
return t
@ -38,23 +38,23 @@ function dirwatch:watch(directory, bool)
local info = system.get_file_info(directory)
if not info then return end
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 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.
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)
end
if target ~= self.windows_watch_top then
if target ~= self.single_watch_top then
local value = self.monitor:watch(target)
if value and value < 0 then
return self:scan(directory)
end
self.windows_watch_top = target
self.single_watch_top = target
end
end
self.windows_watch_count = self.windows_watch_count + 1
self.single_watch_count = self.single_watch_count + 1
self.watched[directory] = true
else
local value = self.monitor:watch(directory)
@ -72,13 +72,13 @@ end
-- this should be an absolute path
function dirwatch:unwatch(directory)
if self.watched[directory] then
if PLATFORM ~= "Windows" then
if self.monitor:mode() == "multiple" then
self.monitor:unwatch(self.watched[directory])
self.reverse_watched[directory] = nil
else
self.windows_watch_count = self.windows_watch_count - 1
if self.windows_watch_count == 0 then
self.windows_watch_top = nil
self.single_watch_count = self.single_watch_count - 1
if self.single_watch_count == 0 then
self.single_watch_top = nil
self.monitor:unwatch(directory)
end
end
@ -93,8 +93,12 @@ function dirwatch:check(change_callback, scan_time, wait_time)
local had_change = false
self.monitor:check(function(id)
had_change = true
if PLATFORM == "Windows" then
change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id))
if self.monitor:mode() == "single" then
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
change_callback(self.reverse_watched[id])
end
@ -162,7 +166,7 @@ end
local function compare_file(a, b)
return a.filename < b.filename
return system.path_compare(a.filename, a.type, b.filename, b.type)
end

View File

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

View File

@ -33,6 +33,7 @@ end
function Doc:reset()
self.lines = { "\n" }
self.selections = { 1, 1, 1, 1 }
self.last_selection = 1
self.undo_stack = { idx = 1 }
self.redo_stack = { idx = 1 }
self.clean_change_id = 1
@ -141,15 +142,39 @@ function Doc:get_change_id()
return self.undo_stack.idx
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.
-- 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
-- order.
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
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)
limit = limit or math.huge
local result = {}
@ -181,13 +206,6 @@ function Doc:sanitize_selection()
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)
assert(not line2 == not col2, "expected 3 or 5 arguments")
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
self:set_selections(target, line1, col1, line2, col2, swap, 0)
self.last_selection = target
end
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)
end
@ -217,6 +239,7 @@ end
function Doc:set_selection(line1, col1, line2, col2, swap)
self.selections = {}
self:set_selections(1, line1, col1, line2, col2, swap)
self.last_selection = 1
end
function Doc:merge_cursors(idx)
@ -225,6 +248,9 @@ function Doc:merge_cursors(idx)
if self.selections[i] == self.selections[j] and
self.selections[i+1] == self.selections[j+1] then
common.splice(self.selections, i, 4)
if self.last_selection >= (i+3)/4 then
self.last_selection = self.last_selection - 1
end
break
end
end
@ -456,6 +482,18 @@ function Doc:text_input(text, idx)
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)
local old_text = self:get_text(line1, col1, line2, col2)
local new_text, res = fn(old_text)

View File

@ -4,6 +4,7 @@ local config = require "core.config"
local style = require "core.style"
local keymap = require "core.keymap"
local translate = require "core.doc.translate"
local ime = require "core.ime"
local View = require "core.view"
---@class core.docview : core.view
@ -60,6 +61,11 @@ function DocView:new(doc)
self.doc = assert(doc)
self.font = "code_font"
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
@ -111,6 +117,10 @@ function DocView:get_scrollable_size()
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
end
function DocView:get_h_scrollable_size()
return math.huge
end
function DocView:get_font()
return style[self.font]
@ -239,8 +249,14 @@ end
function DocView:on_mouse_moved(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"
elseif gw > 0 and x >= self.position.x and x <= (self.position.x + gw) then
self.cursor = "arrow"
self.hovering_gutter = true
else
self.cursor = "ibeam"
end
@ -282,6 +298,29 @@ function DocView:mouse_selection(doc, snap_type, line1, col1, line2, col2)
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(...)
DocView.super.on_mouse_released(self, ...)
self.mouse_selecting = nil
@ -292,13 +331,53 @@ function DocView:on_text_input(text)
self.doc:text_input(text)
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()
-- scroll to make caret visible and reset blink timer if it moved
local line1, col1, line2, col2 = self.doc:get_selection()
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
if core.active_view == self then
if core.active_view == self and not ime.editing then
self:scroll_to_make_visible(line1, col1)
end
core.blink_reset()
@ -316,6 +395,8 @@ function DocView:update()
core.blink_timer = tb
end
self:update_ime_location()
DocView.super.update(self)
end
@ -329,9 +410,17 @@ end
function DocView:draw_line_text(line, x, y)
local default_font = self:get_font()
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 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)
end
return self:get_line_height()
@ -399,17 +488,45 @@ function DocView:draw_line_gutter(line, x, y, width)
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()
if core.active_view == self then
local minline, maxline = self:get_visible_line_range()
-- draw caret if it overlaps this line
local T = config.blink_period
for _, line, col in self.doc:get_selections() do
if line >= minline and line <= maxline
for _, line1, col1, line2, col2 in self.doc:get_selections() do
if line1 >= minline and line1 <= maxline
and system.window_has_focus() then
if ime.editing then
self:draw_ime_decoration(line1, col1, line2, col2)
else
if config.disable_blink
or (core.blink_timer - core.blink_start) % T < T / 2 then
self:draw_caret(self:get_line_screen_position(line, col))
self:draw_caret(self:get_line_screen_position(line1, col1))
end
end
end
end

View File

@ -7,9 +7,15 @@ local View = require "core.view"
local EmptyView = View:extend()
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 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 title = "Lite XL"
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)
x = x + style.padding.x
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
local lines = {
{ fmt = "%s to run a command", cmd = "core:find-command" },
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
}
th = style.font:get_height()
y = y + (dh - (th + style.padding.y) * #lines) / 2
local w = 0

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 keymap
local dirwatch
local ime
local RootView
local StatusView
local TitleView
@ -295,7 +296,19 @@ function core.add_project_directory(path)
end
return refresh_directory(topdir, dirpath)
end, 0.01, 0.01)
-- 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)
@ -361,6 +374,11 @@ end
function core.update_project_subdir(dir, filename, expanded)
assert(dir.files_limit, "function should be called only when directory is in files limit mode")
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)
end
@ -477,6 +495,9 @@ local style = require "core.style"
-- key binding:
-- 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 ----------------------------------------
@ -512,11 +533,26 @@ local style = require "core.style"
-- 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
--
-- disable detectindent, otherwise it is enabled by default
-- 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()
end
@ -558,7 +594,7 @@ local config = require "core.config"
-- "^/build.*/" match any top level directory whose name begins with "build"
-- "^/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
]])
init_file:close()
@ -640,6 +676,7 @@ function core.init()
command = require "core.command"
keymap = require "core.keymap"
dirwatch = require "core.dirwatch"
ime = require "core.ime"
RootView = require "core.rootview"
StatusView = require "core.statusview"
TitleView = require "core.titleview"
@ -1034,6 +1071,8 @@ end
function core.set_active_view(view)
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 core.active_view and core.active_view.force_focus then
core.next_active_view = view
@ -1136,7 +1175,7 @@ function core.custom_log(level, show, backtrace, fmt, ...)
text = text,
time = os.time(),
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)
if #core.log_items > config.max_log_items then
@ -1185,7 +1224,7 @@ function core.try(fn, ...)
local err
local ok, res = xpcall(fn, function(msg)
local item = core.error("%s", msg)
item.info = debug.traceback(nil, 2):gsub("\t", "")
item.info = debug.traceback("", 2):gsub("\t", "")
err = msg
end, ...)
if ok then
@ -1198,6 +1237,8 @@ function core.on_event(type, ...)
local did_keymap = false
if type == "textinput" then
core.root_view:on_text_input(...)
elseif type == "textediting" then
ime.on_text_editing(...)
elseif type == "keypressed" then
did_keymap = keymap.on_key_pressed(...)
elseif type == "keyreleased" then
@ -1384,7 +1425,7 @@ function core.on_error(err)
-- write error to file
local fp = io.open(USERDIR .. "/error.txt", "wb")
fp:write("Error: " .. tostring(err) .. "\n")
fp:write(debug.traceback(nil, 4) .. "\n")
fp:write(debug.traceback("", 4) .. "\n")
fp:close()
-- save copy of all unsaved documents
for _, doc in ipairs(core.docs) do

View File

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

View File

@ -1,6 +1,7 @@
local core = require "core"
local command = require "core.command"
local config = require "core.config"
local ime = require "core.ime"
local keymap = {}
---@alias keymap.shortcut string
@ -179,6 +180,10 @@ end
-- Events listening
--------------------------------------------------------------------------------
function keymap.on_key_pressed(k, ...)
-- In MacOS and Windows during IME composition input is still sent to us
-- so we just ignore it
if PLATFORM ~= "Linux" and ime.editing then return false end
local mk = modkey_map[k]
if mk then
keymap.modkeys[mk] = true
@ -209,9 +214,32 @@ function keymap.on_key_pressed(k, ...)
return false
end
function keymap.on_mouse_wheel(delta, ...)
return not (keymap.on_key_pressed("wheel" .. (delta > 0 and "up" or "down"), delta, ...)
or keymap.on_key_pressed("wheel", delta, ...))
function keymap.on_mouse_wheel(delta_y, delta_x, ...)
local y_direction = delta_y > 0 and "up" or "down"
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
function keymap.on_mouse_pressed(button, x, y, clicks)
@ -274,6 +302,8 @@ keymap.add_direct {
["alt+8"] = "root:switch-to-tab-8",
["alt+9"] = "root:switch-to-tab-9",
["wheel"] = "root:scroll",
["hwheel"] = "root:horizontal-scroll",
["shift+wheel"] = "root:horizontal-scroll",
["ctrl+f"] = "find-replace:find",
["ctrl+r"] = "find-replace:replace",

View File

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

View File

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

View File

@ -334,6 +334,9 @@ function RootView:on_text_input(...)
core.active_view:on_text_input(...)
end
function RootView:on_ime_text_editing(...)
core.active_view:on_ime_text_editing(...)
end
function RootView:on_focus_lost(...)
-- 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
else
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
USERDIR = (system.get_file_info(EXEDIR .. '/user') and (EXEDIR .. '/user'))
or ((os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl"))
or (HOME and (HOME .. '/.config/lite-xl'))
USERDIR = (system.get_file_info(EXEDIR .. PATHSEP .. 'user') and (EXEDIR .. PATHSEP .. 'user'))
or os.getenv("LITE_USERDIR")
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 .. '/?/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.position '"left"' | '"right"'
---A status bar implementation for lite, check core.status_view.
---@class core.statusview : core.view
---@field public super core.view
---@field private items core.statusview.item[]
---@field private active_items core.statusview.item[]
---@field private hovered_item core.statusview.item
---@field private message_timeout number
---@field private message core.statusview.styledtext
---@field private tooltip_mode boolean
---@field private tooltip core.statusview.styledtext
---@field private left_width number
---@field private right_width number
---@field private r_left_width number
---@field private r_right_width number
---@field private left_xoffset number
---@field private right_xoffset number
---@field private dragged_panel '"left"' | '"right"'
---@field private hovered_panel '"left"' | '"right"'
---@field private hide_messages boolean
---@field super core.view
---@field items core.statusview.item[]
---@field active_items core.statusview.item[]
---@field hovered_item core.statusview.item
---@field message_timeout number
---@field message core.statusview.styledtext
---@field tooltip_mode boolean
---@field tooltip core.statusview.styledtext
---@field left_width number
---@field right_width number
---@field r_left_width number
---@field r_right_width number
---@field left_xoffset number
---@field right_xoffset number
---@field dragged_panel '""' | core.statusview.position
---@field hovered_panel '""' | core.statusview.position
---@field hide_messages boolean
local StatusView = View:extend()
---Space separator
@ -42,81 +43,73 @@ StatusView.separator = " "
StatusView.separator2 = " | "
---@alias core.statusview.item.separator
---|>'core.statusview.separator' # Space separator
---| 'core.statusview.separator2' # Pipe separator
---|>`StatusView.separator`
---| `StatusView.separator2`
---@alias core.statusview.item.predicate fun():boolean
---@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
---@class core.statusview.item : core.object
---@field name string
---@field predicate core.statusview.item.predicate
---@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 on_click core.statusview.item.onclick | nil @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.
---Function called when item is clicked and no command is set.
---@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_hover renderer.color | nil
---@field visible boolean
---@field separator core.statusview.item.separator
---@field private active boolean
---@field private x number
---@field private w number
---@field private cached_item core.statusview.styledtext
---@field active boolean
---@field x number
---@field w number
---@field cached_item core.statusview.styledtext
local StatusViewItem = Object:extend()
---Available StatusViewItem options.
---@class core.statusview.item.options : table
---@field predicate string | table | core.statusview.item.predicate
---@field name string
---@field alignment core.statusview.item.alignment
---@field get_item core.statusview.item.get_item
---@field command? string | core.statusview.item.onclick
---@field position? integer
---@field tooltip? string
---@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,
---@field predicate string | table | core.statusview.item.predicate
---A unique name to identify the item on the status bar.
name = nil,
alignment = nil,
---@field name string @A unique name to identify the item on the status bar.
---@field alignment core.statusview.item.alignment
---A function that should return a core.statusview.styledtext element,
---returning empty table is allowed.
get_item = nil,
---returning an empty table is allowed.
---@field get_item core.statusview.item.get_item
---The name of a valid registered command or a callback function to execute
---when the item is clicked.
command = nil,
---@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.
position = nil,
---Displayed when mouse hovers the item
tooltip = nil,
separator = nil,
}
StatusViewItem.options = StatusViewItemOptions
---@field position? integer
---@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
---Flag to tell the item should me aligned on left side of status bar.
---@type number
---@type integer
StatusViewItem.LEFT = 1
---Flag to tell the item should me aligned on right side of status bar.
---@type number
---@type integer
StatusViewItem.RIGHT = 2
---@alias core.statusview.item.alignment
---|>'core.statusview.item.LEFT'
---| 'core.statusview.item.RIGHT'
---|>`StatusView.Item.LEFT`
---| `StatusView.Item.RIGHT`
---Constructor
---@param options core.statusview.item.options
@ -210,7 +203,7 @@ function StatusView:register_docview_items()
return {
dv.doc:is_dirty() and style.accent or style.text, style.icon_font, "f",
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
})
@ -470,7 +463,7 @@ end
---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)
if type(names) == "string" then
names = {names}
@ -489,7 +482,7 @@ end
---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)
if type(names) == "string" then
names = {names}
@ -607,8 +600,8 @@ function StatusView:draw_item_tooltip(item)
local x = self.pointer.x - (w / 2) - (style.padding.x * 2)
if x < 0 then x = 0 end
if x + w + (style.padding.x * 2) > self.size.x then
x = self.size.x - w - (style.padding.x * 2)
if (x + w + (style.padding.x * 3)) > self.size.x then
x = self.size.x - w - (style.padding.x * 3)
end
renderer.draw_rect(
@ -783,7 +776,7 @@ function StatusView:update_active_items()
item.cached_item = {}
if item.visible and item:predicate() then
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
remove_spacing(self, styled_text)
@ -881,7 +874,7 @@ end
---Drag the given panel if possible.
---@param panel '"left"' | '"right"'
---@param panel core.statusview.position
---@param dx number
function StatusView:drag_panel(panel, dx)
if panel == "left" and self.r_left_width > self.left_width then
@ -915,10 +908,8 @@ end
function StatusView:get_hovered_panel(x, y)
if y >= self.position.y and x <= self.left_width + style.padding.x then
return "left"
else
return "right"
end
return ""
return "right"
end
@ -1053,10 +1044,14 @@ function StatusView:on_mouse_released(button, x, y)
end
function StatusView:on_mouse_wheel(y)
if not self.visible then return end
function StatusView:on_mouse_wheel(y, x)
if not self.visible or self.hovered_panel == "" then return end
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
function StatusView:update()

View File

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

View File

@ -3,6 +3,14 @@ local common = require "core.common"
local style = require "core.style"
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 = {
symbol = "w", action = function() system.set_window_mode("normal") end
}
@ -43,6 +51,10 @@ function TitleView:configure_hit_test(borderless)
end
end
function TitleView:on_scale_change()
self:configure_hit_test(self.visible)
end
function TitleView:update()
self.size.y = self.visible and title_view_height() or 0
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 color = style.text
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)
common.draw_text(style.font, color, title, nil, x, y, 0, h)
end

View File

@ -1,6 +1,5 @@
local core = require "core"
local syntax = require "core.syntax"
local common = require "core.common"
local tokenizer = {}
local bad_patterns = {}
@ -51,31 +50,37 @@ local function push_tokens(t, syn, pattern, full_text, find_results)
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
-- differnet delimiters we have open, and which subsyntaxes we have active.
-- At most, there are 3 subsyntaxes active at the same time. Beyond that,
-- does not support further highlighting.
-- Calling `push_subsyntax` appends the current subsyntax pattern index to the
-- state and increases the stack depth. Calling `pop_subsyntax` clears the
-- last appended subsyntax and decreases the stack.
-- 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 current_syntax, subsyntax_info, current_pattern_idx, current_level =
incoming_syntax, nil, state, 0
if state > 0 and (state > 255 or current_syntax.patterns[state].syntax) then
-- If we have higher bits, then decode them one at a time, and find which
incoming_syntax, nil, state:byte(1) or 0, 1
if
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` each time, we could probably cache this in a single table.
for i = 0, 2 do
local target = bit32.extract(state, i*8, 8)
for i = 1, #state do
local target = state:byte(i)
if target ~= 0 then
if current_syntax.patterns[target].syntax then
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
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, ...)
if not bad_patterns[syntax] then
bad_patterns[syntax] = { }
@ -107,7 +127,7 @@ end
---@param incoming_syntax table
---@param text string
---@param state integer
---@param state string
function tokenizer.tokenize(incoming_syntax, text, state)
local res = {}
local i = 1
@ -116,9 +136,9 @@ function tokenizer.tokenize(incoming_syntax, text, state)
return { "normal", text }
end
state = state or 0
state = state or string.char(0)
-- 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.
-- 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.
local function set_subsyntax_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
@ -144,8 +175,8 @@ function tokenizer.tokenize(incoming_syntax, text, state)
end
local function pop_subsyntax()
set_subsyntax_pattern_idx(0)
current_level = current_level - 1
state = string.sub(state, 1, current_level)
set_subsyntax_pattern_idx(0)
current_syntax, subsyntax_info, current_pattern_idx, current_level =
retrieve_syntax_state(incoming_syntax, state)
@ -183,23 +214,12 @@ function tokenizer.tokenize(incoming_syntax, text, state)
return
end
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
local char_pos_1 = string.ulen(text:sub(1, res[1]))
local char_pos_2 = char_pos_1 + string.ulen(text:sub(res[1], res[2])) - 1
-- `regex.match` returns group results as a series of `begin, end`
-- we only want `begin`s
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
local char_pos_1 = res[1] > next and string.ulen(text:sub(1, res[1])) or next
local char_pos_2 = string.ulen(text:sub(1, res[2]))
for i=3,#res do
res[i] = string.ulen(text:sub(1, res[i] - 1)) + 1
end
res[1] = char_pos_1
res[2] = char_pos_2

View File

@ -1,8 +1,8 @@
local core = require "core"
local config = require "core.config"
local style = require "core.style"
local common = require "core.common"
local Object = require "core.object"
local Scrollbar = require "core.scrollbar"
---@class core.view.position
---@field x number
@ -28,10 +28,6 @@ local Object = require "core.object"
---@field w core.view.thumbtrackwidth
---@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.mousebutton "'left'" | "'right'"
@ -47,8 +43,9 @@ local Object = require "core.object"
---@field scroll core.view.scroll
---@field cursor core.view.cursor
---@field scrollable boolean
---@field scrollbar core.view.scrollbar
---@field scrollbar_alpha core.view.increment
---@field v_scrollbar core.scrollbar
---@field h_scrollbar core.scrollbar
---@field current_scale number
local View = Object:extend()
-- 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.cursor = "arrow"
self.scrollable = false
self.scrollbar = {
x = { thumb = 0, track = 0 },
y = { thumb = 0, track = 0 },
w = { thumb = 0, track = 0, to = { thumb = 0, track = 0 } },
h = { thumb = 0, track = 0 },
}
self.scrollbar_alpha = { value = 0, to = 0 }
self.v_scrollbar = Scrollbar({direction = "v", alignment = "e"})
self.h_scrollbar = Scrollbar({direction = "h", alignment = "e"})
self.current_scale = SCALE
end
function View:move_towards(t, k, dest, rate, name)
@ -109,47 +102,9 @@ function View:get_scrollable_size()
return math.huge
end
---@return number x
---@return number y
---@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
---@return number
function View:get_h_scrollable_size()
return 0
end
@ -157,16 +112,19 @@ end
---@param y number
---@return boolean
function View:scrollbar_overlaps_point(x, y)
local sx, sy, sw, sh = self:get_scrollbar_rect()
return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh
return not (not (self.v_scrollbar:overlaps(x, y) or self.h_scrollbar:overlaps(x, y)))
end
---@param x number
---@param y number
---@return boolean
function View:scrollbar_track_overlaps_point(x, y)
local sx, sy, sw, sh = self:get_scrollbar_track_rect()
return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh
function View:scrollbar_dragging()
return self.v_scrollbar.dragging or self.h_scrollbar.dragging
end
---@return boolean
function View:scrollbar_hovering()
return self.v_scrollbar.hovering.track or self.h_scrollbar.hovering.track
end
@ -176,14 +134,18 @@ end
---@param clicks integer
---return boolean
function View:on_mouse_pressed(button, x, y, clicks)
if self:scrollbar_track_overlaps_point(x, y) then
if self:scrollbar_overlaps_point(x, y) then
self.dragging_scrollbar = true
else
local _, _, _, sh = self:get_scrollbar_rect()
local ly = (y - self.position.y) - sh / 2
local pct = common.clamp(ly / self.size.y, 0, 100)
self.scroll.to.y = self:get_scrollable_size() * pct
if not self.scrollable then return end
local result = self.v_scrollbar:on_mouse_pressed(button, x, y, clicks)
if result then
if result ~= true then
self.scroll.to.y = result * self:get_scrollable_size()
end
return true
end
result = self.h_scrollbar:on_mouse_pressed(button, x, y, clicks)
if result then
if result ~= true then
self.scroll.to.x = result * self:get_h_scrollable_size()
end
return true
end
@ -194,7 +156,9 @@ end
---@param x number
---@param y number
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
@ -203,22 +167,41 @@ end
---@param dx number
---@param dy number
function View:on_mouse_moved(x, y, dx, dy)
if self.dragging_scrollbar then
local delta = self:get_scrollable_size() / self.size.y * dy
self.scroll.to.y = self.scroll.to.y + delta
if not self.scrollable then return end
local result
if self.h_scrollbar.dragging then goto skip_v_scrollbar end
result = self.v_scrollbar:on_mouse_moved(x, y, dx, dy)
if result then
if result ~= true then
self.scroll.to.y = result * self:get_scrollable_size()
if not config.animate_drag_scroll then
self:clamp_scroll_position()
self.scroll.y = self.scroll.to.y
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)
-- 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
function View:on_mouse_left()
self.hovered_scrollbar = false
self.hovered_scrollbar_track = false
if not self.scrollable then return end
self.v_scrollbar:on_mouse_left()
self.h_scrollbar:on_mouse_left()
end
@ -236,12 +219,25 @@ function View:on_text_input(text)
-- no-op
end
---@param y number
---@return boolean
function View:on_mouse_wheel(y)
function View:on_ime_text_editing(text, start, length)
-- no-op
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()
local x = self.scroll.x
local y = self.scroll.y
@ -261,35 +257,35 @@ end
function View:clamp_scroll_position()
local max = self:get_scrollable_size() - self.size.y
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
function View:update_scrollbar()
local x, y, w, h = self:get_scrollbar_rect()
self.scrollbar.w.to.thumb = w
self:move_towards(self.scrollbar.w, "thumb", self.scrollbar.w.to.thumb, 0.3, "scroll")
self.scrollbar.x.thumb = x + w - self.scrollbar.w.thumb
self.scrollbar.y.thumb = y
self.scrollbar.h.thumb = h
local v_scrollable = self:get_scrollable_size()
self.v_scrollbar:set_size(self.position.x, self.position.y, self.size.x, self.size.y, v_scrollable)
self.v_scrollbar:set_percent(self.scroll.y/v_scrollable)
self.v_scrollbar:update()
local x, y, w, h = self:get_scrollbar_track_rect()
self.scrollbar.w.to.track = w
self:move_towards(self.scrollbar.w, "track", self.scrollbar.w.to.track, 0.3, "scroll")
self.scrollbar.x.track = x + w - self.scrollbar.w.track
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")
local h_scrollable = self:get_h_scrollable_size()
self.h_scrollbar:set_size(self.position.x, self.position.y, self.size.x, self.size.y, h_scrollable)
self.h_scrollbar:set_percent(self.scroll.x/h_scrollable)
self.h_scrollbar:update()
end
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: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")
if not self.scrollable then return end
self:update_scrollbar()
end
@ -302,29 +298,9 @@ function View:draw_background(color)
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()
self:draw_scrollbar_track()
self:draw_scrollbar_thumb()
self.v_scrollbar:draw()
self.h_scrollbar:draw()
end

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -219,7 +219,7 @@ function LineWrapping.draw_guide(docview)
end
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))
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
@ -355,18 +355,34 @@ function DocView:get_scrollable_size()
return self:get_line_height() * (get_total_wrapped_lines(self) - 1) + self.size.y
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
function DocView:new(doc)
old_new(self, doc)
if not open_files[doc] then open_files[doc] = {} end
table.insert(open_files[doc], self)
if config.plugins.linewrapping.enable_by_default then
self.wrapping_enabled = true
LineWrapping.update_docview_breaks(self)
else
self.wrapping_enabled = false
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
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)
if self.wrapped_settings then self.scroll.to.x = 0 end
end
@ -557,11 +573,13 @@ end
command.add(nil, {
["line-wrapping:enable"] = function()
if core.active_view and core.active_view.doc then
core.active_view.wrapping_enabled = true
LineWrapping.update_docview_breaks(core.active_view)
end
end,
["line-wrapping:disable"] = function()
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)
end
end,

View File

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

View File

@ -39,6 +39,11 @@ end
function ToolbarView:toggle_visible()
self.visible = not self.visible
if self.tooltip then
core.status_view:remove_tooltip()
self.tooltip = false
end
self.hovered_item = nil
end
function ToolbarView:get_icon_width()
@ -73,6 +78,7 @@ end
function ToolbarView:draw()
if not self.visible then return end
self:draw_background(style.background2)
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)
if not self.visible then return end
local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks)
if caught then return caught end
core.set_active_view(core.last_active_view)
@ -94,6 +101,7 @@ end
function ToolbarView:on_mouse_moved(px, py, ...)
if not self.visible then return end
ToolbarView.super.on_mouse_moved(self, px, py, ...)
self.hovered_item = nil
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_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
@ -86,7 +72,7 @@ function TreeView:get_cached(dir, item, dirname)
-- used only to identify the entry into the cache.
local cache_name = item.filename .. (item.topdir and ":" or "")
local t = dir_cache[cache_name]
if not t then
if not t or t.type ~= item.type then
t = {}
local basename = common.basename(item.filename)
if item.topdir then
@ -209,10 +195,10 @@ end
function TreeView:on_mouse_moved(px, py, ...)
if not self.visible then return end
TreeView.super.on_mouse_moved(self, px, py, ...)
self.cursor_pos.x = px
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
return
end
@ -728,16 +714,8 @@ command.add(
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)
local old_filename = item.filename
local old_abs_filename = item.abs_filename

View File

@ -1,10 +1,53 @@
-- mod-version:3
local core = require "core"
local common = require "core.common"
local config = require "core.config"
local command = require "core.command"
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()
for i = 1, #doc.lines do
local old_text = doc:get_text(i, 1, i, math.huge)
@ -22,16 +65,54 @@ local function trim_trailing_whitespace(doc)
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", {
["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,
})
local save = Doc.save
local doc_save = Doc.save
Doc.save = function(self, ...)
trim_trailing_whitespace(self)
save(self, ...)
if
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
return trimwhitespace

View File

@ -92,7 +92,8 @@ local function save_view(view)
return {
type = "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
@ -162,6 +163,9 @@ local function load_node(node, t)
if t.active_view == i then
active_view = view
end
if not view:is(DocView) then
view.scroll = v.scroll
end
end
end
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"
---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"
---The current text or ui scale.

View File

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

View File

@ -31,9 +31,9 @@ regex.NOTEMPTY = 0x00000004
regex.NOTEMPTY_ATSTART = 0x00000008
---@alias regex.modifiers
---|>'"i"' # Case insesitive matching
---| '"m"' # Multiline matching
---| '"s"' # Match all characters with dot (.) metacharacter even new lines
---| "i" # Case insesitive matching
---| "m" # Multiline matching
---| "s" # Match all characters with dot (.) metacharacter even new lines
---
---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 options? regex.modifiers A string of one or more pattern modifiers.
---
---@return regex|string regex Ready to use regular expression object or error
---message if compiling the pattern failed.
---@return regex? regex Ready to use regular expression object or nil on error.
---@return string? error The error message if compiling the pattern failed.
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:
---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
---
---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 b number Blue
---@field public a number Alpha
renderer.color = {}
---
---Represent options that affect a font's rendering.
---@class renderer.fontoptions
---@field public antialiasing "'none'" | "'grayscale'" | "'subpixel'"
---@field public hinting "'slight'" | "'none'" | '"full"'
-- @field public bold boolean
-- @field public italic boolean
-- @field public underline boolean
-- @field public smoothing boolean
-- @field public strikethrough boolean
renderer.fontoptions = {}
---@field public antialiasing "none" | "grayscale" | "subpixel"
---@field public hinting "slight" | "none" | "full"
---@field public bold boolean
---@field public italic boolean
---@field public underline boolean
---@field public smoothing boolean
---@field public strikethrough boolean
---
---@class renderer.font
@ -154,3 +152,6 @@ function renderer.draw_rect(x, y, width, height, color) end
---
---@return number x
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 idx? integer
---@param substring string
---return string new_string
---@return string new_string
function string.uinsert(s, idx, substring) end
---Equivalent to utf8.remove()
---@param s string
---@param start? integer
---@param stop? integer
---return string new_string
---@return string new_string
function string.uremove(s, start, stop) end
---Equivalent to utf8.width()
@ -130,12 +130,12 @@ function string.uwidthindex(s, location, ambi_is_double, default_width) end
---Equivalent to utf8.title()
---@param s string
---return string new_string
---@return string new_string
function string.utitle(s) end
---Equivalent to utf8.fold()
---@param s string
---return string new_string
---@return string new_string
function string.ufold(s) end
---Equivalent to utf8.ncasecmp()

View File

@ -6,15 +6,15 @@
system = {}
---@alias system.fileinfotype
---|>'"file"' # It is a file.
---| '"dir"' # It is a directory.
---| "file" # It is a file.
---| "dir" # It is a directory.
---
---@class system.fileinfo
---@field public modified number A timestamp in seconds.
---@field public size number Size in bytes.
---@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.
@ -24,7 +24,7 @@ system.fileinfo = {}
---
---Window events:
--- * "quit"
--- * "resized" -> width, height
--- * "resized" -> width, height (in points)
--- * "exposed"
--- * "minimized"
--- * "maximized"
@ -38,12 +38,18 @@ system.fileinfo = {}
--- * "keypressed" -> key_name
--- * "keyreleased" -> key_name
--- * "textinput" -> text
--- * "textediting" -> text, start, length
---
---Mouse events:
--- * "mousepressed" -> button_name, x, y, amount_of_clicks
--- * "mousereleased" -> button_name, x, 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 any? arg1
@ -64,7 +70,7 @@ function system.wait_event(timeout) end
---
---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
---
@ -74,10 +80,10 @@ function system.set_cursor(type) end
function system.set_window_title(title) end
---@alias system.windowmode
---|>'"normal"'
---| '"minimized"'
---| '"maximized"'
---| '"fullscreen"'
---| "normal"
---| "minimized"
---| "maximized"
---| "fullscreen"
---
---Change the window mode.
@ -103,7 +109,7 @@ function system.set_window_bordered(bordered) end
---for custom window management.
---
---@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
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
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.
---
@ -138,6 +168,14 @@ function system.window_has_focus() end
---@param message string
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.
---This function raises an error if the path doesn't exists.
@ -152,6 +190,7 @@ function system.chdir(path) end
---@param directory_path string
---
---@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
---
@ -168,7 +207,7 @@ function system.list_dir(path) end
---
---@param path string
---
---@return string
---@return string? abspath
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.
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.
---
@ -215,7 +276,9 @@ function system.sleep(seconds) end
---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
---forking it to the background.
---Note: Do not use this function, use the Process API instead.
---
---@deprecated
---@param command string The command to execute.
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
---the less visible the window will be.
---@return boolean success True if the operation suceeded.
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 idx? integer
---@param substring string
---return string new_string
---@return string new_string
function utf8extra.insert(s, idx, substring) end
---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 start? integer
---@param stop? integer
---return string new_string
---@return string new_string
function utf8extra.remove(s, start, stop) end
---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
---(number). utf8.lower/utf8.pper has the same extension.
---@param s string
---return string new_string
---@return string new_string
function utf8extra.title(s) end
---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
---(number). utf8.lower/utf8.pper has the same extension.
---@param s string
---return string new_string
---@return string new_string
function utf8extra.fold(s) end
---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
---@return integer result
function utf8extra.ncasecmp(a, b) end
return utf8extra

View File

@ -1,8 +1,8 @@
project('lite-xl',
['c'],
version : '2.1.0',
version : '2.1.1',
license : 'MIT',
meson_version : '>= 0.47',
meson_version : '>= 0.56',
default_options : [
'c_std=gnu11',
'wrap_mode=nofallback'
@ -52,6 +52,13 @@ lite_cargs = ['-DSDL_MAIN_HANDLED', '-DPCRE2_STATIC']
if get_option('renderer') or host_machine.system() == 'darwin'
lite_cargs += '-DLITE_USE_SDL_RENDERER'
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
#===============================================================================
@ -82,13 +89,20 @@ if not get_option('source-only')
foreach lua : lua_names
last_lua = (lua == lua_names[-1] or get_option('wrap_mode') == 'forcefallback')
lua_dep = dependency(lua, fallback: last_lua ? ['lua', 'lua_dep'] : [], required : last_lua,
lua_dep = dependency(lua, fallback: last_lua ? ['lua', 'lua_dep'] : [], required : false,
version: '>= 5.4',
default_options: default_fallback_options + ['default_library=static', 'line_editing=false', 'interpreter=false']
)
if lua_dep.found()
break
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
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('portable', type : 'boolean', value : false, description: 'Portable install')
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>
<releases>
<release version="2.0.1" date="2021-08-28" />
<release version="2.1.1" date="2022-12-29" />
</releases>
</component>

View File

@ -7,4 +7,4 @@ Icon=lite-xl
Terminal=false
StartupWMClass=lite-xl
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
as it has an entrypoint that looks like the following, where xxxxx is the plugin name:
#include "lite_xl_plugin_api.h"
int lua_open_lite_xl_xxxxx(lua_State* L, void* XL) {
int luaopen_lite_xl_xxxxx(lua_State* L, void* XL) {
lite_xl_plugin_init(XL);
...
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!
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.
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 void (*lua_settop) (lua_State *L, int idx);
static void (*lua_pushvalue) (lua_State *L, int idx);
static void (*lua_remove) (lua_State *L, int idx);
static void (*lua_insert) (lua_State *L, int idx);
static void (*lua_replace) (lua_State *L, int idx);
static void (*lua_copy) (lua_State *L, int fromidx, int toidx);
static int (*lua_checkstack) (lua_State *L, int sz);
static void (*lua_xmove) (lua_State *from, lua_State *to, int n);
@ -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_createtable) (lua_State *L, int narr, int nrec);
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 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_settable) (lua_State *L, int idx);
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 int (*lua_setmetatable) (lua_State *L, int objindex);
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 int (*lua_getctx) (lua_State *L, int *ctx);
static int (*lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k);
static int (*lua_load) (lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode);
static int (*lua_dump) (lua_State *L, lua_Writer writer, void *data);
static int (*lua_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_resume) (lua_State *L, lua_State *from, int narg);
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_pushglobaltable(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)
#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_HOOKRET 1
#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 void __lite_xl_fallback_lua_settop (lua_State *L, int idx) { fputs("warning: lua_settop is a stub", stderr); }
static void __lite_xl_fallback_lua_pushvalue (lua_State *L, int idx) { fputs("warning: lua_pushvalue is a stub", stderr); }
static void __lite_xl_fallback_lua_remove (lua_State *L, int idx) { fputs("warning: lua_remove is a stub", stderr); }
static void __lite_xl_fallback_lua_insert (lua_State *L, int idx) { fputs("warning: lua_insert is a stub", stderr); }
static void __lite_xl_fallback_lua_replace (lua_State *L, int idx) { fputs("warning: lua_replace is a stub", stderr); }
static void __lite_xl_fallback_lua_copy (lua_State *L, int fromidx, int toidx) { fputs("warning: lua_copy is a stub", stderr); }
static int __lite_xl_fallback_lua_checkstack (lua_State *L, int sz) { fputs("warning: lua_checkstack is a stub", stderr); }
static void __lite_xl_fallback_lua_xmove (lua_State *from, lua_State *to, int n) { fputs("warning: lua_xmove is a stub", stderr); }
@ -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_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_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 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_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); }
@ -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 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_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 int __lite_xl_fallback_lua_getctx (lua_State *L, int *ctx) { fputs("warning: lua_getctx is a stub", stderr); }
static int __lite_xl_fallback_lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k) { fputs("warning: lua_pcallk is a stub", stderr); }
static int __lite_xl_fallback_lua_load (lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode) { fputs("warning: lua_load is a stub", stderr); }
static int __lite_xl_fallback_lua_dump (lua_State *L, lua_Writer writer, void *data) { fputs("warning: lua_dump is a stub", stderr); }
static int __lite_xl_fallback_lua_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_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); }
@ -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_gethookcount (lua_State *L) { fputs("warning: lua_gethookcount is a stub", stderr); }
/** lauxlib.h **/
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_where) (lua_State *L, int lvl);
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_fileresult) (lua_State *L, int stat, const char *fname);
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_pushresultsize) (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 LUA_ERRFILE (LUA_ERRERR+1)
#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_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_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_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); }
@ -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_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 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)
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_settop, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_pushvalue, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_remove, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_insert, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_replace, void , lua_State *L, int idx);
IMPORT_SYMBOL(lua_copy, void , lua_State *L, int fromidx, int toidx);
IMPORT_SYMBOL(lua_checkstack, int , lua_State *L, int sz);
IMPORT_SYMBOL(lua_xmove, void , lua_State *from, lua_State *to, int n);
@ -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_createtable, void , lua_State *L, int narr, int nrec);
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_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_settable, void , lua_State *L, int idx);
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_setmetatable, int , lua_State *L, int objindex);
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_getctx, int , lua_State *L, int *ctx);
IMPORT_SYMBOL(lua_pcallk, int , lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k);
IMPORT_SYMBOL(lua_load, int , lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode);
IMPORT_SYMBOL(lua_dump, int , lua_State *L, lua_Writer writer, void *data);
IMPORT_SYMBOL(lua_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_resume, int , lua_State *L, lua_State *from, int narg);
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_where, void , lua_State *L, int lvl);
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_fileresult, int , lua_State *L, int stat, const char *fname);
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_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_openlibs, void, lua_State* L);
}
#endif

View File

@ -27,7 +27,7 @@
<key>CFBundleShortVersionString</key>
<string>@PROJECT_VERSION@</string>
<key>NSHumanReadableCopyright</key>
<string>© 2019-2021 Francesco Abbate</string>
<string>© 2019-2022 Lite XL Team</string>
</dict>
</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,19 +1,18 @@
diff -ruN lua-5.4.3/meson.build newlua/meson.build
--- lua-5.4.3/meson.build 2022-05-29 21:04:17.850449500 +0800
+++ newlua/meson.build 2022-06-10 19:23:55.685139800 +0800
@@ -82,6 +82,7 @@
diff -ruN lua-5.4.4/meson.build lua-5.4.4-mod/meson.build
--- lua-5.4.4/meson.build 2022-11-16 10:33:38.424383300 +0800
+++ lua-5.4.4-mod/meson.build 2022-11-16 09:40:57.697918000 +0800
@@ -85,6 +85,7 @@
'src/lutf8lib.c',
'src/lvm.c',
'src/lzio.c',
+ 'src/utf8_wrappers.c',
dependencies: lua_lib_deps,
override_options: project_options,
implicit_include_directories: false,
Binary files lua-5.4.3/src/lua54.dll and newlua/src/lua54.dll differ
diff -ruN lua-5.4.3/src/luaconf.h newlua/src/luaconf.h
--- lua-5.4.3/src/luaconf.h 2021-03-15 21:32:52.000000000 +0800
+++ newlua/src/luaconf.h 2022-06-10 19:15:03.014745300 +0800
@@ -786,5 +786,15 @@
version: meson.project_version(),
soversion: lua_versions[0] + '.' + lua_versions[1],
diff -ruN lua-5.4.4/src/luaconf.h lua-5.4.4-mod/src/luaconf.h
--- lua-5.4.4/src/luaconf.h 2022-01-13 19:24:43.000000000 +0800
+++ lua-5.4.4-mod/src/luaconf.h 2022-11-16 09:40:57.703926000 +0800
@@ -782,5 +782,15 @@
@ -29,9 +28,9 @@ diff -ruN lua-5.4.3/src/luaconf.h newlua/src/luaconf.h
+
#endif
diff -ruN lua-5.4.3/src/Makefile newlua/src/Makefile
--- lua-5.4.3/src/Makefile 2021-02-10 02:47:17.000000000 +0800
+++ newlua/src/Makefile 2022-06-10 19:22:45.267931400 +0800
diff -ruN lua-5.4.4/src/Makefile lua-5.4.4-mod/src/Makefile
--- lua-5.4.4/src/Makefile 2021-07-15 22:01:52.000000000 +0800
+++ lua-5.4.4-mod/src/Makefile 2022-11-16 09:40:57.708921800 +0800
@@ -33,7 +33,7 @@
PLATS= guess aix bsd c89 freebsd generic linux linux-readline macosx mingw posix solaris
@ -41,10 +40,9 @@ diff -ruN lua-5.4.3/src/Makefile newlua/src/Makefile
LIB_O= lauxlib.o lbaselib.o lcorolib.o ldblib.o liolib.o lmathlib.o loadlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o linit.o
BASE_O= $(CORE_O) $(LIB_O) $(MYOBJS)
diff -ruN lua-5.4.3/src/utf8_wrappers.c newlua/src/utf8_wrappers.c
--- lua-5.4.3/src/utf8_wrappers.c 1970-01-01 07:30:00.000000000 +0730
+++ newlua/src/utf8_wrappers.c 2022-06-10 19:13:11.904613300 +0800
diff -ruN lua-5.4.4/src/utf8_wrappers.c lua-5.4.4-mod/src/utf8_wrappers.c
--- lua-5.4.4/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
@@ -0,0 +1,101 @@
+/**
+ * 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);
+}
+#endif
diff -ruN lua-5.4.3/src/utf8_wrappers.h newlua/src/utf8_wrappers.h
--- lua-5.4.3/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
@@ -0,0 +1,42 @@
diff -ruN lua-5.4.4/src/utf8_wrappers.h lua-5.4.4-mod/src/utf8_wrappers.h
--- lua-5.4.4/src/utf8_wrappers.h 1970-01-01 07:30:00.000000000 +0730
+++ lua-5.4.4-mod/src/utf8_wrappers.h 2022-11-16 10:29:46.044102000 +0800
@@ -0,0 +1,44 @@
+/**
+ * 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
+
+#ifdef lauxlib_c
+#include <stdio.h>
+FILE *freopen_utf8(const char *pathname, const char *mode, FILE *stream);
+#define freopen freopen_utf8
+#endif
@ -177,6 +176,7 @@ diff -ruN lua-5.4.3/src/utf8_wrappers.h newlua/src/utf8_wrappers.h
+#endif
+
+#ifdef loslib_c
+#include <stdio.h>
+int remove_utf8(const char *pathname);
+int rename_utf8(const char *oldpath, const char *newpath);
+int system_utf8(const char *command);

View File

@ -31,6 +31,7 @@ show_help() {
main() {
local platform="$(get_platform_name)"
local arch="$(get_platform_arch)"
local build_dir="$(get_default_build_dir)"
local build_type="debug"
local prefix=/
@ -106,11 +107,27 @@ main() {
portable=""
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}"
CFLAGS=$CFLAGS LDFLAGS=$LDFLAGS meson setup \
--buildtype=$build_type \
--prefix "$prefix" \
$cross_file \
$force_fallback \
$bundle \
$portable \
@ -124,7 +141,7 @@ main() {
meson compile -C "${build_dir}"
if [ ! -z ${pgo+x} ]; then
if [[ $pgo != "" ]]; then
cp -r data "${build_dir}/src"
"${build_dir}/src/lite-xl"
meson configure -Db_pgo=use "${build_dir}"

View File

@ -27,16 +27,17 @@ addons_download() {
-o "${build_dir}/lite-xl-widgets.zip"
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
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"
unzip "${build_dir}/lite-xl-plugins.zip" -d "${build_dir}"
mv "${build_dir}/lite-xl-plugins-2.1/plugins" "${build_dir}/third/data"
rm -rf "${build_dir}/lite-xl-plugins-2.1"
mv "${build_dir}/lite-xl-plugins-master/plugins" "${build_dir}/third/data"
rm -rf "${build_dir}/lite-xl-plugins-master"
}
# Addons installation: some distributions forbid external downloads
@ -45,7 +46,7 @@ addons_install() {
local build_dir="$1"
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}"
done
@ -81,6 +82,8 @@ get_platform_arch() {
else
arch=i686
fi
elif [[ $CROSS_ARCH != "" ]]; then
arch=$CROSS_ARCH
fi
echo "$arch"
}

View File

@ -125,6 +125,76 @@
"css": "right-open",
"code": 62,
"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
# symbols to ignore
IGNORE_SYM='luaL_pushmodule\|luaL_openlib'
IGNORE_SYM='luaL_pushmodule'
##### 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 "as it has an entrypoint that looks like the following, where xxxxx is the plugin name:"
echo '#include "lite_xl_plugin_api.h"'
echo "int lua_open_lite_xl_xxxxx(lua_State* L, void* XL) {"
echo "int luaopen_lite_xl_xxxxx(lua_State* L, void* XL) {"
echo " lite_xl_plugin_init(XL);"
echo " ..."
echo " return 1;"
@ -98,6 +98,8 @@ generate_header() {
decl "$LUA_PATH/lua.h"
echo
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 "#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/lauxlib.h"
echo -e "\tIMPORT_SYMBOL(luaL_openlibs, void, lua_State* L);"
echo "}"
echo "#endif"

View File

@ -8,6 +8,7 @@
#define API_TYPE_FONT "Font"
#define API_TYPE_PROCESS "Process"
#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))

View File

@ -1,8 +1,6 @@
#include "api.h"
#include <SDL.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.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 add_dirmonitor(struct dirmonitor_internal*, const char*);
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) {
@ -62,6 +61,7 @@ static int f_dirmonitor_new(lua_State* L) {
struct dirmonitor* monitor = lua_newuserdata(L, sizeof(struct dirmonitor));
luaL_setmetatable(L, API_TYPE_DIRMONITOR);
memset(monitor, 0, sizeof(struct dirmonitor));
monitor->mutex = SDL_CreateMutex();
monitor->internal = init_dirmonitor();
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[] = {
{ "new", f_dirmonitor_new },
{ "__gc", f_dirmonitor_gc },
{ "watch", f_dirmonitor_watch },
{ "unwatch", f_dirmonitor_unwatch },
{ "check", f_dirmonitor_check },
{ "mode", f_dirmonitor_mode },
{NULL, NULL}
};

View File

@ -6,3 +6,4 @@ int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, si
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; }
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* monitor = calloc(sizeof(struct dirmonitor_internal), 1);
struct dirmonitor_internal* monitor = calloc(1, sizeof(struct dirmonitor_internal));
monitor->fd = inotify_init();
pipe(monitor->sig);
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) {
inotify_rm_watch(monitor->fd, fd);
}
int get_mode_dirmonitor() { return 2; }

View File

@ -4,6 +4,7 @@
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
struct dirmonitor_internal {
int fd;
@ -11,7 +12,7 @@ struct dirmonitor_internal {
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();
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 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)
return -1;
if (nev <= 0)
@ -43,8 +46,11 @@ int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) {
int fd = open(path, O_RDONLY);
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);
kevent(monitor->fd, &change, 1, NULL, 0, NULL);
kevent(monitor->fd, &change, 1, NULL, 0, &ts);
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) {
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() {
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) {
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];
int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)info->FileName, info->FileNameLength, transform_buffer, PATH_MAX*4 - 1, NULL, NULL);
char transform_buffer[MAX_PATH*4];
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);
if (!info->NextEntryOffset)
break;
@ -60,3 +60,6 @@ int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) {
void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) {
close_monitor_handle(monitor);
}
int get_mode_dirmonitor() { return 1; }

View File

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

View File

@ -4,6 +4,126 @@
#include <string.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) {
lua_rawgeti(L, -1, 1);
@ -37,6 +157,7 @@ static int f_pcre_compile(lua_State *L) {
NULL
);
if (re) {
pcre2_jit_compile(re, PCRE2_JIT_COMPLETE);
lua_newtable(L);
lua_pushlightuserdata(L, re);
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.
static int f_pcre_match(lua_State *L) {
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);
if (lua_gettop(L) > 2)
offset = luaL_checknumber(L, 3);
offset = regex_offset_relative(luaL_checknumber(L, 3), len);
offset -= 1;
len -= offset;
if (lua_gettop(L) > 3)
opts = luaL_checknumber(L, 4);
lua_rawgeti(L, 1, 1);
pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1);
pcre2_match_data* md = pcre2_match_data_create_from_pattern(re, NULL);
int rc = pcre2_match(re, (PCRE2_SPTR)&str[offset], len, 0, opts, md, NULL);
if (rc < 0) {
if (regex_compiled) pcre2_code_free(re);
pcre2_match_data_free(md);
if (rc != PCRE2_ERROR_NOMATCH) {
PCRE2_UCHAR buffer[120];
@ -84,18 +207,155 @@ static int f_pcre_match(lua_State *L) {
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");
if (regex_compiled) pcre2_code_free(re);
pcre2_match_data_free(md);
return 0;
}
for (int i = 0; i < rc*2; i++)
lua_pushinteger(L, ovector[i]+offset+1);
if (regex_compiled) pcre2_code_free(re);
pcre2_match_data_free(md);
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[] = {
{ "compile", f_pcre_compile },
{ "cmatch", f_pcre_match },
{ "gmatch", f_pcre_gmatch },
{ "gsub", f_pcre_gsub },
{ "__gc", f_pcre_gc },
{ NULL, NULL }
};

View File

@ -1,7 +1,16 @@
#include <string.h>
#include "api.h"
<<<<<<< HEAD
#include "renderer.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(
lua_State *L,
@ -137,7 +146,22 @@ static int f_font_copy(lua_State *L) {
}
static int f_font_group(lua_State* L) {
int table_size;
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);
return 1;
}
@ -176,7 +200,10 @@ static int f_font_gc(lua_State *L) {
static int f_font_get_width(lua_State *L) {
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
lua_pushnumber(L, ren_font_group_get_width(fonts, luaL_checkstring(L, 2)));
size_t len;
const char *text = luaL_checklstring(L, 2, &len);
lua_pushnumber(L, ren_font_group_get_width(fonts, text, len));
return 1;
}
@ -199,19 +226,47 @@ static int f_font_set_size(lua_State *L) {
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) {
RenColor color;
if (lua_isnoneornil(L, idx)) {
return (RenColor) { def, def, def, 255 };
}
lua_rawgeti(L, idx, 1);
lua_rawgeti(L, idx, 2);
lua_rawgeti(L, idx, 3);
lua_rawgeti(L, idx, 4);
color.r = luaL_checknumber(L, -4);
color.g = luaL_checknumber(L, -3);
color.b = luaL_checknumber(L, -2);
color.a = luaL_optnumber(L, -1, 255);
luaL_checktype(L, idx, LUA_TTABLE);
color.r = get_color_value(L, idx, 1);
color.g = get_color_value(L, idx, 2);
color.b = get_color_value(L, idx, 3);
color.a = get_color_value_opt(L, idx, 4, 255);
lua_pop(L, 4);
return color;
}
@ -241,6 +296,9 @@ static int f_begin_frame(UNUSED lua_State *L) {
static int f_end_frame(UNUSED lua_State *L) {
rencache_end_frame();
// clear the font reference table
lua_newtable(L);
lua_rawseti(L, LUA_REGISTRYINDEX, RENDERER_FONT_REF);
return 0;
}
@ -277,11 +335,25 @@ static int f_draw_rect(lua_State *L) {
static int f_draw_text(lua_State *L) {
RenFont* fonts[FONT_FALLBACK_MAX];
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);
int y = luaL_checknumber(L, 4);
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);
return 1;
}
@ -312,6 +384,10 @@ static const luaL_Reg fontLib[] = {
};
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_newmetatable(L, API_TYPE_FONT);
luaL_setfuncs(L, fontLib, 0);

View File

@ -12,6 +12,20 @@
#include <windows.h>
#include <fileapi.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
#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) {
char buf[16];
int mx, my, wx, wy;
int mx, my, w, h;
SDL_Event e;
SDL_Event event_plus;
top:
if ( !SDL_PollEvent(&e) ) {
@ -220,12 +235,11 @@ top:
goto top;
case SDL_DROPFILE:
SDL_GetGlobalMouseState(&mx, &my);
SDL_GetWindowPosition(window, &wx, &wy);
SDL_GetMouseState(&mx, &my);
lua_pushstring(L, "filedropped");
lua_pushstring(L, e.drop.file);
lua_pushinteger(L, mx - wx);
lua_pushinteger(L, my - wy);
lua_pushinteger(L, mx);
lua_pushinteger(L, my);
SDL_free(e.drop.file);
return 4;
@ -261,6 +275,23 @@ top:
lua_pushstring(L, e.text.text);
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:
if (e.button.button == 1) { SDL_CaptureMouse(1); }
lua_pushstring(L, "mousepressed");
@ -280,7 +311,6 @@ top:
case SDL_MOUSEMOTION:
SDL_PumpEvents();
SDL_Event event_plus;
while (SDL_PeepEvents(&event_plus, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) {
e.motion.x = event_plus.motion.x;
e.motion.y = event_plus.motion.y;
@ -296,8 +326,51 @@ top:
case SDL_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);
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:
goto top;
@ -441,6 +514,36 @@ static int f_get_window_mode(lua_State *L) {
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) {
const char *title = luaL_checkstring(L, 1);
@ -448,19 +551,8 @@ static int f_show_fatal_error(lua_State *L) {
#ifdef _WIN32
MessageBox(0, msg, title, MB_OK | MB_ICONERROR);
#else
SDL_MessageBoxButtonData buttons[] = {
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Ok" },
};
SDL_MessageBoxData data = {
.title = title,
.message = msg,
.numbuttons = 1,
.buttons = buttons,
};
int buttonid;
SDL_ShowMessageBox(&data, &buttonid);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, msg, NULL);
#endif
return 0;
}
@ -517,7 +609,7 @@ static int f_list_dir(lua_State *L) {
#ifdef _WIN32
lua_settop(L, 1);
if (strchr("\\/", path[strlen(path) - 2]) != NULL)
if (path[0] == 0 || strchr("\\/", path[strlen(path) - 1]) != NULL)
lua_pushstring(L, "*");
else
lua_pushstring(L, "/*");
@ -843,7 +935,7 @@ typedef struct lua_function_node {
#define P(FUNC) { "lua_" #FUNC, (fptr)(lua_##FUNC) }
#define U(FUNC) { "luaL_" #FUNC, (fptr)(luaL_##FUNC) }
static void* api_require(const char* symbol) {
static lua_function_node nodes[] = {
static const lua_function_node nodes[] = {
P(atpanic), P(checkstack),
P(close), P(concat), P(copy), P(createtable), P(dump),
P(error), P(gc), P(getallocf), P(getfield),
@ -866,16 +958,21 @@ static void* api_require(const char* symbol) {
U(newmetatable), U(setmetatable), U(testudata), U(checkudata), U(where),
U(error), U(fileresult), U(execresult), U(ref), U(unref), U(loadstring),
U(newstate), U(setfuncs), U(buffinit), U(addlstring), U(addstring),
U(addvalue), U(pushresult), {"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
P(absindex), P(arith), P(callk), P(compare), P(getglobal),
P(len), P(pcallk), P(rawgetp), P(rawlen), P(rawsetp), P(setglobal),
P(iscfunction), P(yieldk),
U(checkversion_), U(tolstring), U(len), U(getsubtable), U(prepbuffsize),
U(pushresultsize), U(buffinitsize), U(checklstring), U(checkoption), U(gsub), U(loadbufferx),
U(loadfilex), U(optinteger), U(optlstring), U(requiref), U(traceback)
U(loadfilex), U(optinteger), U(optlstring), U(requiref), U(traceback),
#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
};
@ -886,6 +983,14 @@ static void* api_require(const char* symbol) {
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) {
char entrypoint_name[512]; entrypoint_name[sizeof(entrypoint_name) - 1] = '\0';
int result;
@ -898,9 +1003,12 @@ static int f_load_native_plugin(lua_State *L) {
lua_getglobal(L, "package");
lua_getfield(L, -1, "native_plugins");
lua_newtable(L);
lua_pushlightuserdata(L, library);
lua_setfield(L, -2, "handle");
luaL_setmetatable(L, API_TYPE_NATIVE_PLUGIN);
lua_setfield(L, -2, name);
lua_pop(L, 1);
lua_pop(L, 2);
const char *basename = strrchr(name, '.');
basename = !basename ? name : basename + 1;
@ -993,7 +1101,10 @@ static const luaL_Reg lib[] = {
{ "set_window_hit_test", f_set_window_hit_test },
{ "get_window_size", f_get_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 },
{ "raise_window", f_raise_window },
{ "show_fatal_error", f_show_fatal_error },
{ "rmdir", f_rmdir },
{ "chdir", f_chdir },
@ -1017,6 +1128,9 @@ static const luaL_Reg lib[] = {
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);
return 1;
}

View File

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

View File

@ -5,6 +5,8 @@
#include "rencache.h"
#include "renderer.h"
#include <signal.h>
#if defined(__amigaos4__) || defined(__morphos__)
#define VSTRING "Lite XL 2.1.0r1 (10.10.2022)"
#define VERSTAG "\0$VER: " VSTRING
@ -12,19 +14,20 @@
#ifdef _WIN32
#include <windows.h>
#elif __linux__ || __FreeBSD__
#elif defined(__linux__)
#include <unistd.h>
#include <signal.h>
#elif __APPLE__
#elif defined(__APPLE__)
#include <mach-o/dyld.h>
#elif __amigaos4__
#elif defined(__amigaos4__)
#include "platform/amigaos4.h"
static CONST_STRPTR stack USED = "$STACK:102400";
static CONST_STRPTR version USED = VERSTAG;
#elif __morphos__
#elif defined(__morphos__)
#include "platform/morphos.h"
unsigned long __stack = 1000000;
UBYTE VString[] = VERSTAG;
#elif defined(__FreeBSD__)
#include <sys/sysctl.h>
#endif
@ -46,7 +49,8 @@ static void get_exe_filename(char *buf, int sz) {
buf[len] = '\0';
#elif __linux__
char path[] = "/proc/self/exe";
int len = readlink(path, buf, sz - 1);
ssize_t len = readlink(path, buf, sz - 1);
if (len > 0)
buf[len] = '\0';
#elif __APPLE__
/* use realpath to resolve a symlink if the process was launched from one.
@ -58,8 +62,12 @@ static void get_exe_filename(char *buf, int sz) {
realpath(exepath, buf);
#elif defined(__amigaos4__) || defined(__morphos__)
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
strcpy(buf, "./lite");
*buf = 0;
#endif
}
@ -98,30 +106,39 @@ void set_macos_bundle_resources(lua_State *L);
#endif
#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"
#elif __aarch64__
#define ARCH_PROCESSOR "aarch64"
#elif __arm__
#define ARCH_PROCESSOR "arm"
#elif __amigaos4__ || __morphos__
#elif defined(__amigaos4__) || defined(__morphos__)
#define ARCH_PROCESSOR "ppc"
#else
#elif defined(__i386__) || defined(_M_IX86) || defined(__MINGW32__)
#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
#if _WIN32
#define ARCH_PLATFORM "windows"
#elif __linux__
#define ARCH_PLATFORM "linux"
#elif __FreeBSD__
#define ARCH_PLATFORM "freebsd"
#elif __APPLE__
#define ARCH_PLATFORM "darwin"
#elif __amigaos4__
#define ARCH_PLATFORM "amigaos4"
#elif __morphos__
#define ARCH_PLATFORM "morphos"
#else
#endif
#if !defined(ARCH_PROCESSOR) || !defined(ARCH_PLATFORM)
#error "Please define -DLITE_ARCH_TUPLE."
#endif
#define LITE_ARCH_TUPLE ARCH_PROCESSOR "-" ARCH_PLATFORM
#endif
@ -130,7 +147,7 @@ int main(int argc, char **argv) {
HINSTANCE lib = LoadLibrary("user32.dll");
int (*SetProcessDPIAware)() = (void*) GetProcAddress(lib, "SetProcessDPIAware");
SetProcessDPIAware();
#elif defined(__morphos__)
#elif defined(__amigaos4__) || defined(__morphos__)
setlocale(LC_ALL, "C");
#else
signal(SIGPIPE, SIG_IGN);
@ -150,6 +167,12 @@ int main(int argc, char **argv) {
#if SDL_VERSION_ATLEAST(2, 0, 5)
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
#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)
/* This hint tells SDL to respect borderless window as a normal window.
@ -206,7 +229,12 @@ init_lua:
char exename[2048];
get_exe_filename(exename, sizeof(exename));
if (*exename) {
lua_pushstring(L, exename);
} else {
// get_exe_filename failed
lua_pushstring(L, argv[0]);
}
lua_setglobal(L, "EXEFILE");
#ifdef __APPLE__

View File

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

View File

@ -2,9 +2,19 @@
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdalign.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 "rencache.h"
@ -16,7 +26,8 @@
#define CELLS_X 80
#define CELLS_Y 50
#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)
enum { SET_CLIP, DRAW_TEXT, DRAW_RECT };
@ -29,6 +40,7 @@ typedef struct {
RenColor color;
RenFont *fonts[FONT_FALLBACK_MAX];
float text_x;
size_t len;
char text[];
} Command;
@ -37,13 +49,15 @@ static unsigned cells_buf2[CELLS_X * CELLS_Y];
static unsigned *cells_prev = cells_buf1;
static unsigned *cells = cells_buf2;
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 RenRect screen_rect;
static bool show_debug;
static inline int min(int a, int b) { return a < b ? a : b; }
static inline int max(int a, int b) { return a > b ? a : b; }
static inline int rencache_min(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 */
@ -69,32 +83,54 @@ static inline bool rects_overlap(RenRect a, RenRect b) {
static RenRect intersect_rects(RenRect a, RenRect b) {
int x1 = max(a.x, b.x);
int y1 = max(a.y, b.y);
int x2 = min(a.x + a.width, b.x + b.width);
int y2 = min(a.y + a.height, b.y + b.height);
return (RenRect) { x1, y1, max(0, x2 - x1), max(0, y2 - y1) };
int x1 = rencache_max(a.x, b.x);
int y1 = rencache_max(a.y, b.y);
int x2 = rencache_min(a.x + a.width, b.x + b.width);
int y2 = rencache_min(a.y + a.height, b.y + b.height);
return (RenRect) { x1, y1, rencache_max(0, x2 - x1), rencache_max(0, y2 - y1) };
}
static RenRect merge_rects(RenRect a, RenRect b) {
int x1 = min(a.x, b.x);
int y1 = min(a.y, b.y);
int x2 = max(a.x + a.width, b.x + b.width);
int y2 = max(a.y + a.height, b.y + b.height);
int x1 = rencache_min(a.x, b.x);
int y1 = rencache_min(a.y, b.y);
int x2 = rencache_max(a.x + a.width, b.x + b.width);
int y2 = rencache_max(a.y + a.height, b.y + b.height);
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) {
size_t alignment = alignof(max_align_t) - 1;
size = (size + alignment) & ~alignment;
Command *cmd = (Command*) (command_buf + command_buf_idx);
int n = command_buf_idx + size;
if (n > COMMAND_BUF_SIZE) {
fprintf(stderr, "Warning: (" __FILE__ "): exhausted command buffer\n");
if (resize_issue) {
// Don't push new commands as we had problems resizing the command buffer.
// Let's wait for the next frame.
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;
memset(cmd, 0, size);
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) };
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);
if (cmd) {
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);
cmd->rect = rect;
cmd->text_x = x;
cmd->len = len;
cmd->tab_size = ren_font_group_get_tab_size(fonts);
}
}
@ -163,6 +200,7 @@ void rencache_invalidate(void) {
void rencache_begin_frame() {
/* reset all cells if the screen width/height has changed */
int w, h;
resize_issue = false;
ren_get_size(&w, &h);
if (screen_rect.width != w || h != screen_rect.height) {
screen_rect.width = w;
@ -256,7 +294,7 @@ void rencache_end_frame() {
break;
case DRAW_TEXT:
ren_font_group_set_tab_size(cmd->fonts, cmd->tab_size);
ren_draw_text(cmd->fonts, cmd->text, cmd->text_x, cmd->rect.y, cmd->color);
ren_draw_text(cmd->fonts, cmd->text, cmd->len, cmd->text_x, cmd->rect.y, cmd->color);
break;
}
}

View File

@ -8,7 +8,7 @@
void rencache_show_debug(bool enable);
void rencache_set_clip_rect(RenRect rect);
void rencache_draw_rect(RenRect rect, RenColor color);
float rencache_draw_text(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_begin_frame();
void rencache_end_frame();

View File

@ -8,6 +8,11 @@
#include <freetype/ftoutln.h>
#include FT_FREETYPE_H
#ifdef _WIN32
#include <windows.h>
#include "utfconv.h"
#endif
#include "renderer.h"
#include "renwindow.h"
@ -51,7 +56,11 @@ typedef struct RenFont {
ERenFontHinting hinting;
unsigned char style;
unsigned short underline_thickness;
char path[1];
#ifdef _WIN32
unsigned char *file;
HANDLE file_handle;
#endif
char path[];
} RenFont;
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) {
FT_Face face;
FT_Face face = NULL;
#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;
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);
if (FT_Set_Pixel_Sizes(face, 0, (int)(size*surface_scale)))
goto failure;
@ -223,16 +271,31 @@ RenFont* ren_font_load(const char* path, float size, ERenFontAntialiasing antial
font->hinting = hinting;
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))
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 (FT_Load_Char(face, ' ', font_set_load_options(font)))
if (FT_Load_Char(face, ' ', font_set_load_options(font))) {
free(font);
goto failure;
}
font->space_advance = face->glyph->advance.x / 64.0f;
font->tab_advance = font->space_advance * 2;
return font;
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;
}
@ -252,6 +315,10 @@ const char* ren_font_get_path(RenFont *font) {
void ren_font_free(RenFont* font) {
font_clear_glyph_cache(font);
FT_Done_Face(font->face);
#ifdef _WIN32
free(font->file);
CloseHandle(font->file_handle);
#endif
free(font);
}
@ -293,9 +360,9 @@ int ren_font_group_get_height(RenFont **fonts) {
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;
const char* end = text + strlen(text);
const char* end = text + len;
GlyphMetric* metric = NULL; GlyphSet* set = NULL;
while (text < end) {
unsigned int codepoint;
@ -309,7 +376,7 @@ float ren_font_group_get_width(RenFont **fonts, const char *text) {
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);
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;
y *= surface_scale;
int bytes_per_pixel = surface->format->BytesPerPixel;
const char* end = text + strlen(text);
const char* end = text + len;
uint8_t* destination_pixels = surface->pixels;
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;
bool underline = fonts[0]->style & FONT_STYLE_UNDERLINE;
bool strikethrough = fonts[0]->style & FONT_STYLE_STRIKETHROUGH;
@ -458,8 +525,13 @@ void ren_draw_rect(RenRect rect, RenColor color) {
/*************** Window Management ****************/
void ren_free_window_resources() {
extern uint8_t *command_buf;
extern size_t command_buf_size;
renwin_free(&window_renderer);
SDL_FreeSurface(draw_rect_surface);
free(command_buf);
command_buf = NULL;
command_buf_size = 0;
}
void ren_init(SDL_Window *win) {

View File

@ -11,7 +11,7 @@
#define UNUSED
#endif
#define FONT_FALLBACK_MAX 4
#define FONT_FALLBACK_MAX 10
typedef struct RenFont RenFont;
typedef enum { FONT_HINTING_NONE, FONT_HINTING_SLIGHT, FONT_HINTING_FULL } ERenFontHinting;
typedef enum { FONT_ANTIALIASING_NONE, FONT_ANTIALIASING_GRAYSCALE, FONT_ANTIALIASING_SUBPIXEL } ERenFontAntialiasing;
@ -28,8 +28,8 @@ int ren_font_group_get_height(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_tab_size(RenFont **font, int n);
float ren_font_group_get_width(RenFont **font, const char *text);
float ren_draw_text(RenFont **font, const char *text, float x, int y, RenColor color);
float ren_font_group_get_width(RenFont **font, const char *text, size_t len);
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);

View File

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

View File

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

View File

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

View File

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

View File

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