Compare commits
139 Commits
209de02c36
...
04deddbe79
Author | SHA1 | Date |
---|---|---|
George Sokianos | 04deddbe79 | |
George Sokianos | f6784d2530 | |
jgmdev | d6600b1ea3 | |
sammyette | 1504ad7f4c | |
Jan | aa503665e0 | |
Jefferson González | da524a3c46 | |
jgmdev | 7eb75b1a19 | |
Guldoman | 385ee69f94 | |
Julien Voisin | 6c6e5e9b99 | |
Guldoman | 72c0ad768e | |
Julien Voisin | b3937b0380 | |
Julien Voisin | 7133ea5419 | |
Julien Voisin | 81b8747d80 | |
Takase | b7e9ca6585 | |
Guldoman | 2638e9636b | |
Dave | 4e272c33de | |
Jefferson González | 3491eb464d | |
jgmdev | 79908baed6 | |
Guldoman | 74349f8e56 | |
Jefferson González | 870d685b59 | |
Quinten Kock | 3fda8c0a09 | |
Jefferson González | 141d00795c | |
xwii | 271a804986 | |
Simon Krauter | 8603644726 | |
Martin Ashby | d4989d98bd | |
Jefferson González | 5fa7dabd34 | |
Guldoman | c29b1c2cb9 | |
Guldoman | 18e3542be0 | |
Adam | 1446d95695 | |
Adam | 3674a9140c | |
jgmdev | fc891ef3df | |
Jan | 5c4d15c4d5 | |
jgmdev | 0ab7fe9311 | |
Guldoman | 97bfe503a0 | |
sammyette | 1810db0705 | |
Guldoman | c42f01ed1f | |
Guldoman | 24179bbb23 | |
Jan | b584f1fe35 | |
Jefferson González | 902539b97a | |
Takase | 6d0e7f3046 | |
Guldoman | 213dc7142e | |
Jefferson González | 3c64c32379 | |
Cyriaque Skrapits | 5b5b5fd3e3 | |
Jan200101 | b137d77183 | |
Adam Harrison | 5ab8dc0275 | |
Jefferson González | e152db2cce | |
Guldoman | 9d48441685 | |
sammyette | e13f265fac | |
Techie Guy | 68595e4c7c | |
sammy | dfa2f93d9c | |
Guldoman | f7ad8753eb | |
Delta-official | 5db5512663 | |
Jefferson González | ef4ca03eae | |
Takase | 2e186a746d | |
jgmdev | 7bb86e16f2 | |
jgmdev | 563fa7f29e | |
Guldoman | 9f917052ad | |
jgmdev | 0373d29f99 | |
Takase | 718791857b | |
Takase | 7fa51bb7ab | |
Jefferson González | c8e525c126 | |
Takase | 4107b0c3fe | |
Guldoman | 51f2a291d3 | |
Guldoman | 519b91c2dd | |
Guldoman | 38a8549e71 | |
Jefferson González | 469a4fed3e | |
Guldoman | c6c485feb0 | |
Guldoman | 0a1b8b6bb1 | |
Guldoman | e147a6cb9b | |
jgmdev | 80f022d8ff | |
takase1121 | 69938c619c | |
takase1121 | 4457f26502 | |
takase1121 | 5cabc68ccb | |
Guldoman | 66198eb327 | |
Alexey Dokuchaev | 1b1c13e3de | |
vkedwardli | 1590be8c8d | |
jgmdev | acebbfd88a | |
jgmdev | fb43e6f9e6 | |
Quinten Kock | 4a5851afe5 | |
Jefferson González | 9c7304f555 | |
Jan | 56e465c351 | |
Jefferson González | b8a4f729df | |
Guldoman | 03cc5ffcd1 | |
Guldoman | b029f5993e | |
Adam | 9951e785b6 | |
Adam Harrison | 3bd567f5e1 | |
Jefferson González | 69bccf6fcf | |
Guldoman | ed226c476e | |
Guldoman | 0f160e614e | |
Guldoman | b52fe1605e | |
Guldoman | c512d01a68 | |
jgmdev | a619054951 | |
Jefferson González | d89d1e6d98 | |
Guldoman | 8a9bac7de3 | |
jgmdev | 0030c69524 | |
jgmdev | 3ccd696ffc | |
Guldoman | 715411061b | |
jgmdev | 76f55aefe8 | |
Jefferson González | af6c4bc152 | |
Jefferson González | f02b3c46e6 | |
jgmdev | 3da6833249 | |
jgmdev | 261292c6aa | |
Adam | dd6eee1542 | |
Takase | 437b954595 | |
jgmdev | 3c752f86f3 | |
jgmdev | 1708462f4c | |
Adam | 1c5936e697 | |
jgmdev | 97ba91af8b | |
Guldoman | decbac4ac6 | |
Guldoman | 6ca56fee1a | |
jgmdev | 5aaa5ab273 | |
Guldoman | 6b754eb628 | |
Guldoman | 5c2c95765e | |
Takase | 7107f88f9f | |
jgmdev | 42ac231f01 | |
Jefferson González | f89088d0ec | |
Jan | fb8bc08a67 | |
Guldoman | 334a7da5c9 | |
Jefferson González | 48c800cde7 | |
Jefferson González | 0fc793d1ae | |
Jefferson González | 214c9d6287 | |
jgmdev | ec6d0532c8 | |
jgmdev | 63885cb2d6 | |
Jefferson González | a7888e96ea | |
Takase | 34c4ac3cd5 | |
Takase | 3409929a0c | |
Adam | d1c74f529a | |
Adam | dad0f79708 | |
Guldoman | e7a575b4c4 | |
Adam | 9e816154ad | |
Adam | e34a3ca78f | |
Adam | 293110feaa | |
Adam | 1580d923d3 | |
Jan | 630ab0ab92 | |
Jan | 0277aaf079 | |
Jefferson González | 3083f09fac | |
jgmdev | a65a2b293a | |
Jefferson González | ba0a454c97 | |
Jefferson González | a0164d5902 |
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
181
changelog.md
181
changelog.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
-- 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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
@ -177,6 +178,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
|
||||
|
@ -207,9 +212,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)
|
||||
|
@ -272,6 +300,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",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
---A condition to evaluate if the item should be displayed. If a string
|
||||
---is given it is treated as a require import that should return a valid object
|
||||
---which is checked against the current active view, the sames applies if a
|
||||
---table is given. A function that returns a boolean can be used instead to
|
||||
---perform a custom evaluation, setting to nil means always evaluates to true.
|
||||
---@field predicate string | table | core.statusview.item.predicate
|
||||
---@field name string
|
||||
---A unique name to identify the item on the status bar.
|
||||
---@field name string @A unique name to identify the item on the status bar.
|
||||
---@field alignment core.statusview.item.alignment
|
||||
---A function that should return a core.statusview.styledtext element,
|
||||
---returning an empty table is allowed.
|
||||
---@field get_item core.statusview.item.get_item
|
||||
---@field command? string | core.statusview.item.onclick
|
||||
---The name of a valid registered command or a callback function to execute
|
||||
---when the item is clicked.
|
||||
---@field command string | core.statusview.item.onclick | nil
|
||||
---The position in which to insert the given item on the internal table,
|
||||
---a value of -1 inserts the item at the end which is the default. A value
|
||||
---of 1 will insert the item at the beggining.
|
||||
---@field position? integer
|
||||
---@field tooltip? string
|
||||
---@field tooltip? string @Text displayed when mouse hovers the item.
|
||||
---@field visible boolean @Flag to show or hide the item
|
||||
---The type of separator rendered to the right of the item if another item
|
||||
---follows it.
|
||||
---@field separator? core.statusview.item.separator
|
||||
local StatusViewItemOptions = {
|
||||
---A condition to evaluate if the item should be displayed. If a string
|
||||
---is given it is treated as a require import that should return a valid object
|
||||
---which is checked against the current active view, the sames applies if a
|
||||
---table is given. A function that returns a boolean can be used instead to
|
||||
---perform a custom evaluation, setting to nil means always evaluates to true.
|
||||
predicate = nil,
|
||||
---A unique name to identify the item on the status bar.
|
||||
name = nil,
|
||||
alignment = nil,
|
||||
---A function that should return a core.statusview.styledtext element,
|
||||
---returning empty table is allowed.
|
||||
get_item = nil,
|
||||
---The name of a valid registered command or a callback function to execute
|
||||
---when the item is clicked.
|
||||
command = nil,
|
||||
---The position in which to insert the given item on the internal table,
|
||||
---a value of -1 inserts the item at the end which is the default. A value
|
||||
---of 1 will insert the item at the beggining.
|
||||
position = nil,
|
||||
---Displayed when mouse hovers the item
|
||||
tooltip = nil,
|
||||
separator = nil,
|
||||
}
|
||||
|
||||
StatusViewItem.options = StatusViewItemOptions
|
||||
|
||||
---Flag to tell the item should me aligned on left side of status bar.
|
||||
---@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,9 +1044,13 @@ 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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -263,13 +283,15 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
|||
-- General end of syntax check. Applies in the case where
|
||||
-- we're ending early in the middle of a delimiter, or
|
||||
-- just normally, upon finding a token.
|
||||
if subsyntax_info then
|
||||
while subsyntax_info do
|
||||
local s, e = find_text(text, subsyntax_info, i, true, true)
|
||||
if s then
|
||||
push_token(res, subsyntax_info.type, text:usub(i, e))
|
||||
-- On finding unescaped delimiter, pop it.
|
||||
pop_subsyntax()
|
||||
i = e + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -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.
|
@ -588,8 +588,11 @@ 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()
|
||||
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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+()%^",
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
---
|
||||
|
@ -193,7 +254,7 @@ function system.get_clipboard() end
|
|||
function system.set_clipboard(text) end
|
||||
|
||||
---
|
||||
---Get the process id of lite-xl it self.
|
||||
---Get the process id of lite-xl itself.
|
||||
---
|
||||
---@return integer
|
||||
function system.get_process_id() end
|
||||
|
@ -215,7 +276,9 @@ function system.sleep(seconds) end
|
|||
---Similar to os.execute() but does not return the exit status of the
|
||||
---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
|
||||
|
|
|
@ -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
|
||||
|
|
20
meson.build
20
meson.build
|
@ -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'],
|
||||
|
|
|
@ -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')
|
|
@ -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>
|
||||
|
|
|
@ -7,4 +7,4 @@ Icon=lite-xl
|
|||
Terminal=false
|
||||
StartupWMClass=lite-xl
|
||||
Categories=Development;IDE;
|
||||
MimeType=text/plain;
|
||||
MimeType=text/plain;inode/directory;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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']
|
|
@ -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);
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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}
|
||||
};
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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]);
|
||||
|
|
266
src/api/regex.c
266
src/api/regex.c
|
@ -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, ®ex_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, ®ex_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, ®ex_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 }
|
||||
};
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
#include "api.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;
|
||||
|
||||
static int font_get_options(
|
||||
lua_State *L,
|
||||
|
@ -137,7 +141,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 +195,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 +221,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 +291,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 +330,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 +379,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);
|
||||
|
|
164
src/api/system.c
164
src/api/system.c
|
@ -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>
|
||||
|
@ -158,8 +172,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) ) {
|
||||
|
@ -209,12 +224,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;
|
||||
|
||||
|
@ -250,6 +264,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");
|
||||
|
@ -269,7 +300,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;
|
||||
|
@ -285,8 +315,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;
|
||||
|
@ -426,6 +499,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);
|
||||
|
@ -433,19 +536,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;
|
||||
}
|
||||
|
@ -502,7 +594,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, "/*");
|
||||
|
@ -824,7 +916,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),
|
||||
|
@ -847,16 +939,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
|
||||
|
||||
};
|
||||
|
@ -867,6 +964,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;
|
||||
|
@ -879,9 +984,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;
|
||||
|
@ -974,7 +1082,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 },
|
||||
|
@ -998,6 +1109,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;
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
50
src/main.c
50
src/main.c
|
@ -5,13 +5,16 @@
|
|||
#include "rencache.h"
|
||||
#include "renderer.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#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 defined(__FreeBSD__)
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -33,7 +36,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.
|
||||
|
@ -43,8 +47,12 @@ static void get_exe_filename(char *buf, int sz) {
|
|||
char exepath[size];
|
||||
_NSGetExecutablePath(exepath, &size);
|
||||
realpath(exepath, buf);
|
||||
#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
|
||||
}
|
||||
|
||||
|
@ -83,24 +91,31 @@ 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"
|
||||
#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"
|
||||
#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
|
||||
|
||||
|
@ -127,6 +142,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.
|
||||
|
@ -181,7 +202,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__
|
||||
|
|
|
@ -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',
|
||||
)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
if (FT_New_Face( library, path, 0, &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:
|
||||
|
||||
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;
|
||||
|
@ -442,8 +509,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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue