Merge branch 'master' into lineguide-config
This commit is contained in:
commit
d7c309d8e2
|
@ -6,3 +6,6 @@ indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[meson.build]
|
||||||
|
indent_size = 4
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
"Category: CI":
|
||||||
|
- .github/workflows/*
|
||||||
|
|
||||||
|
"Category: Meta":
|
||||||
|
- ./*
|
||||||
|
- .github/*
|
||||||
|
- .github/ISSUE_TEMPLATE/*
|
||||||
|
- .github/PULL_REQUEST_TEMPLATE/*
|
||||||
|
- .gitignore
|
||||||
|
|
||||||
|
"Category: Build System":
|
||||||
|
- meson.build
|
||||||
|
- meson_options.txt
|
||||||
|
- subprojects/*
|
||||||
|
|
||||||
|
"Category: Documentation":
|
||||||
|
- docs/**/*
|
||||||
|
|
||||||
|
"Category: Resources":
|
||||||
|
- resources/**/*
|
||||||
|
|
||||||
|
"Category: Themes":
|
||||||
|
- data/colors/*
|
||||||
|
|
||||||
|
"Category: Lua Core":
|
||||||
|
- data/core/**/*
|
||||||
|
|
||||||
|
"Category: Fonts":
|
||||||
|
- data/fonts/*
|
||||||
|
|
||||||
|
"Category: Plugins":
|
||||||
|
- data/plugins/*
|
||||||
|
|
||||||
|
"Category: C Core":
|
||||||
|
- src/**/*
|
|
@ -0,0 +1,16 @@
|
||||||
|
name: "Pull Request Labeler"
|
||||||
|
on:
|
||||||
|
- pull_request_target
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Apply Type Label
|
||||||
|
uses: actions/labeler@v3
|
||||||
|
with:
|
||||||
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
sync-labels: "" # works around actions/labeler#104
|
|
@ -1,16 +1,44 @@
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
|
# All builds use lhelper only for releases,
|
||||||
|
# otherwise for normal builds dependencies are dynamically linked.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- '*'
|
- '*'
|
||||||
|
# tags:
|
||||||
|
# - 'v[0-9]*'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-linux:
|
archive_source_code:
|
||||||
name: Build Linux
|
name: Source Code Tarball
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
# Only on tags/releases
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Python Setup
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.6
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get install -qq ninja-build
|
||||||
|
pip3 install meson
|
||||||
|
- name: Package
|
||||||
|
shell: bash
|
||||||
|
run: bash scripts/package.sh --version ${GITHUB_REF##*/} --debug --source
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Source Code Tarball
|
||||||
|
path: "lite-xl-*-src.tar.gz"
|
||||||
|
|
||||||
|
build_linux:
|
||||||
|
name: Linux
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-18.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -21,48 +49,204 @@ jobs:
|
||||||
CC: ${{ matrix.config.cc }}
|
CC: ${{ matrix.config.cc }}
|
||||||
CXX: ${{ matrix.config.cxx }}
|
CXX: ${{ matrix.config.cxx }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- name: Set Environment Variables
|
||||||
- name: Set up Python
|
if: ${{ matrix.config.cc == 'gcc' }}
|
||||||
uses: actions/setup-python@v2
|
run: |
|
||||||
with:
|
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||||
python-version: 3.6
|
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
||||||
- name: Install dependencies
|
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-linux-$(uname -m)" >> "$GITHUB_ENV"
|
||||||
run: |
|
- uses: actions/checkout@v2
|
||||||
sudo apt-get install -qq libsdl2-dev libfreetype6 ninja-build
|
- name: Python Setup
|
||||||
pip3 install meson
|
uses: actions/setup-python@v2
|
||||||
- name: Build package
|
with:
|
||||||
run: bash build-packages.sh x86-64
|
python-version: 3.6
|
||||||
- name: upload packages
|
- name: Update Packages
|
||||||
uses: actions/upload-artifact@v2
|
run: sudo apt-get update
|
||||||
with:
|
- name: Install Dependencies
|
||||||
name: Ubuntu Package
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
path: lite-xl-linux-*.tar.gz
|
run: bash scripts/install-dependencies.sh --debug
|
||||||
|
- name: Install Release Dependencies
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
run: |
|
||||||
|
bash scripts/install-dependencies.sh --debug --lhelper
|
||||||
|
bash scripts/lhelper.sh --debug
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
bash --version
|
||||||
|
bash scripts/build.sh --debug --forcefallback
|
||||||
|
- name: Package
|
||||||
|
if: ${{ matrix.config.cc == 'gcc' }}
|
||||||
|
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --binary
|
||||||
|
- name: AppImage
|
||||||
|
if: ${{ matrix.config.cc == 'gcc' && startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
run: bash scripts/appimage.sh --nobuild --version ${INSTALL_REF}
|
||||||
|
- name: Upload Artifacts
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
if: ${{ matrix.config.cc == 'gcc' }}
|
||||||
|
with:
|
||||||
|
name: Linux Artifacts
|
||||||
|
path: |
|
||||||
|
${{ env.INSTALL_NAME }}.tar.gz
|
||||||
|
LiteXL-${{ env.INSTALL_REF }}-x86_64.AppImage
|
||||||
|
|
||||||
build-macox:
|
build_macos:
|
||||||
name: Build Mac OS X
|
name: macOS (x86_64)
|
||||||
runs-on: macos-10.15
|
runs-on: macos-10.15
|
||||||
|
env:
|
||||||
|
CC: clang
|
||||||
|
CXX: clang++
|
||||||
|
steps:
|
||||||
|
- name: System Information
|
||||||
|
run: |
|
||||||
|
system_profiler SPSoftwareDataType
|
||||||
|
bash --version
|
||||||
|
gcc -v
|
||||||
|
xcodebuild -version
|
||||||
|
- name: Set Environment Variables
|
||||||
|
run: |
|
||||||
|
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||||
|
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
||||||
|
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-macos-$(uname -m)" >> "$GITHUB_ENV"
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Python Setup
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.9
|
||||||
|
- name: Install Dependencies
|
||||||
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
run: bash scripts/install-dependencies.sh --debug
|
||||||
|
- name: Install Release Dependencies
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
run: |
|
||||||
|
bash scripts/install-dependencies.sh --debug --lhelper
|
||||||
|
bash scripts/lhelper.sh --debug
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
bash --version
|
||||||
|
bash scripts/build.sh --bundle --debug --forcefallback
|
||||||
|
- name: Error Logs
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
mkdir ${INSTALL_NAME}
|
||||||
|
cp /usr/var/lhenv/lite-xl/logs/* ${INSTALL_NAME}
|
||||||
|
tar czvf ${INSTALL_NAME}.tar.gz ${INSTALL_NAME}
|
||||||
|
# - name: Package
|
||||||
|
# if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
# run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons
|
||||||
|
- name: Create DMG Image
|
||||||
|
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --dmg
|
||||||
|
- name: Upload DMG Image
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: macOS DMG Image
|
||||||
|
path: ${{ env.INSTALL_NAME }}.dmg
|
||||||
|
- name: Upload Error Logs
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
name: Error Logs
|
||||||
|
path: ${{ env.INSTALL_NAME }}.tar.gz
|
||||||
|
|
||||||
|
build_windows_msys2:
|
||||||
|
name: Windows
|
||||||
|
runs-on: windows-2019
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
config:
|
msystem: [MINGW32, MINGW64]
|
||||||
# - { name: "GCC", cc: gcc-10, cxx: g++-10 }
|
defaults:
|
||||||
- { name: "clang", cc: clang, cxx: clang++ }
|
run:
|
||||||
env:
|
shell: msys2 {0}
|
||||||
CC: ${{ matrix.config.cc }}
|
|
||||||
CXX: ${{ matrix.config.cxx }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python
|
- uses: msys2/setup-msys2@v2
|
||||||
uses: actions/setup-python@v2
|
with:
|
||||||
with:
|
#msystem: MINGW64
|
||||||
python-version: 3.9
|
msystem: ${{ matrix.msystem }}
|
||||||
- name: Install dependencies
|
update: true
|
||||||
run: |
|
install: >-
|
||||||
pip3 install meson
|
base-devel
|
||||||
brew install ninja sdl2
|
git
|
||||||
- name: Build package
|
zip
|
||||||
run: bash build-packages.sh x86-64
|
- name: Set Environment Variables
|
||||||
- name: upload packages
|
run: |
|
||||||
uses: actions/upload-artifact@v2
|
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||||
with:
|
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-windows-$(uname -m)" >> "$GITHUB_ENV"
|
||||||
name: Mac OS X Package
|
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
||||||
path: lite-xl-macosx-*.zip
|
- name: Install Dependencies
|
||||||
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
run: bash scripts/install-dependencies.sh --debug
|
||||||
|
- name: Install Release Dependencies
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
run: bash scripts/install-dependencies.sh --debug --lhelper
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
bash --version
|
||||||
|
bash scripts/build.sh --debug --forcefallback
|
||||||
|
- name: Error Logs
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
mkdir ${INSTALL_NAME}
|
||||||
|
cp /usr/var/lhenv/lite-xl/logs/* ${INSTALL_NAME}
|
||||||
|
tar czvf ${INSTALL_NAME}.tar.gz ${INSTALL_NAME}
|
||||||
|
- name: Package
|
||||||
|
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --binary
|
||||||
|
- name: Build Installer
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
run: bash scripts/innosetup/innosetup.sh --debug
|
||||||
|
- name: Upload Artifacts
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Windows Artifacts
|
||||||
|
path: |
|
||||||
|
LiteXL*.exe
|
||||||
|
${{ env.INSTALL_NAME }}.zip
|
||||||
|
- name: Upload Error Logs
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
name: Error Logs
|
||||||
|
path: ${{ env.INSTALL_NAME }}.tar.gz
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
name: Deployment
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
# if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
if: false
|
||||||
|
needs:
|
||||||
|
- archive_source_code
|
||||||
|
- build_linux
|
||||||
|
- build_macos
|
||||||
|
- build_windows_msys2
|
||||||
|
steps:
|
||||||
|
- name: Set Environment Variables
|
||||||
|
run: echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Linux Artifacts
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: macOS DMG Image
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Source Code Tarball
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Windows Artifacts
|
||||||
|
- name: Display File Information
|
||||||
|
shell: bash
|
||||||
|
run: ls -lR
|
||||||
|
# Note: not using `actions/create-release@v1`
|
||||||
|
# because it cannot update an existing release
|
||||||
|
# see https://github.com/actions/create-release/issues/29
|
||||||
|
- uses: softprops/action-gh-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ env.INSTALL_REF }}
|
||||||
|
name: Release ${{ env.INSTALL_REF }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
files: |
|
||||||
|
lite-xl-${{ env.INSTALL_REF }}-*
|
||||||
|
LiteXL*.AppImage
|
||||||
|
LiteXL*.exe
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
build*
|
build*/
|
||||||
.build*
|
.build*/
|
||||||
.run*
|
lhelper/
|
||||||
*.zip
|
submodules/
|
||||||
*.tar.gz
|
subprojects/lua/
|
||||||
.lite-debug.log
|
subprojects/reproc/
|
||||||
subprojects/lua
|
/appimage*
|
||||||
subprojects/libagg
|
|
||||||
subprojects/reproc
|
|
||||||
lite-xl
|
|
||||||
error.txt
|
|
||||||
.ccls-cache
|
.ccls-cache
|
||||||
|
.lite-debug.log
|
||||||
|
.run*
|
||||||
|
*.diff
|
||||||
|
*.exe
|
||||||
|
*.tar.gz
|
||||||
|
*.zip
|
||||||
|
*.DS_Store
|
||||||
|
*App*
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
error.txt
|
||||||
|
lite-xl*
|
||||||
|
LiteXL*
|
||||||
|
|
93
README.md
93
README.md
|
@ -1,17 +1,18 @@
|
||||||
# Lite XL
|
# Lite XL
|
||||||
|
|
||||||
|
[![CI]](https://github.com/lite-xl/lite-xl/actions/workflows/build.yml)
|
||||||
[![Discord Badge Image]](https://discord.gg/RWzqC3nx7K)
|
[![Discord Badge Image]](https://discord.gg/RWzqC3nx7K)
|
||||||
|
|
||||||
![screenshot-dark]
|
![screenshot-dark]
|
||||||
|
|
||||||
A lightweight text editor written in Lua, adapted from [lite].
|
A lightweight text editor written in Lua, adapted from [lite].
|
||||||
|
|
||||||
* **[Get Lite XL]** — Download for Windows, Linux and Mac OS (notarized app).
|
* **[Get Lite XL]** — Download for Windows, Linux and Mac OS.
|
||||||
* **[Get plugins]** — Add additional functionality, adapted for Lite XL.
|
* **[Get plugins]** — Add additional functionality, adapted for Lite XL.
|
||||||
* **[Get color themes]** — Add additional colors themes.
|
* **[Get color themes]** — Add additional colors themes.
|
||||||
|
|
||||||
Please refer to our [website] for the user and developer documentation,
|
Please refer to our [website] for the user and developer documentation,
|
||||||
including [build] instructions.
|
including [build] instructions details. A quick build guide is described below.
|
||||||
|
|
||||||
Lite XL has support for high DPI display on Windows and Linux and,
|
Lite XL has support for high DPI display on Windows and Linux and,
|
||||||
since 1.16.7 release, it supports **retina displays** on macOS.
|
since 1.16.7 release, it supports **retina displays** on macOS.
|
||||||
|
@ -42,6 +43,91 @@ the [plugins repository] or in the [Lite XL plugins repository].
|
||||||
Additional color themes can be found in the [colors repository].
|
Additional color themes can be found in the [colors repository].
|
||||||
These color themes are bundled with all releases of Lite XL by default.
|
These color themes are bundled with all releases of Lite XL by default.
|
||||||
|
|
||||||
|
## Quick Build Guide
|
||||||
|
|
||||||
|
If you compile Lite XL yourself, it is recommended to use the script
|
||||||
|
`build-packages.sh`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash build-packages.sh -h
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will run Meson and create a tar compressed archive with the application or,
|
||||||
|
for Windows, a zip file. Lite XL can be easily installed
|
||||||
|
by unpacking the archive in any directory of your choice.
|
||||||
|
|
||||||
|
Otherwise the following is an example of basic commands if you want to customize
|
||||||
|
the build:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
meson setup --buildtype=release --prefix <prefix> build
|
||||||
|
meson compile -C build
|
||||||
|
DESTDIR="$(pwd)/lite-xl" meson install --skip-subprojects -C build
|
||||||
|
```
|
||||||
|
|
||||||
|
where `<prefix>` might be one of `/`, `/usr` or `/opt`, the default is `/`.
|
||||||
|
To build a bundle application on macOS:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
meson setup --buildtype=release --Dbundle=true --prefix / build
|
||||||
|
meson compile -C build
|
||||||
|
DESTDIR="$(pwd)/Lite XL.app" meson install --skip-subprojects -C build
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that the package is relocatable to any prefix and the option prefix
|
||||||
|
affects only the place where the application is actually installed.
|
||||||
|
|
||||||
|
## Installing Prebuilt
|
||||||
|
|
||||||
|
Head over to [releases](https://github.com/lite-xl/lite-xl/releases) and download the version for your operating system.
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
Unzip the file and `cd` into the `lite-xl` directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
tar -xzf <file>
|
||||||
|
cd lite-xl
|
||||||
|
```
|
||||||
|
|
||||||
|
To run lite-xl without installing:
|
||||||
|
```sh
|
||||||
|
cd bin
|
||||||
|
./lite-xl
|
||||||
|
```
|
||||||
|
|
||||||
|
To install lite-xl copy files over into appropriate directories:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p $HOME/.local/bin && cp bin/lite-xl $HOME/.local/bin
|
||||||
|
cp -r share $HOME/.local
|
||||||
|
```
|
||||||
|
|
||||||
|
If `$HOME/.local/bin` is not in PATH:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo -e 'export PATH=$PATH:$HOME/.local/bin' >> $HOME/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
To get the icon to show up in app launcher:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
xdg-desktop-menu forceupdate
|
||||||
|
```
|
||||||
|
|
||||||
|
You may need to logout and login again to see icon in app launcher.
|
||||||
|
|
||||||
|
To uninstall just run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rm -f $HOME/.local/bin/lite-xl
|
||||||
|
rm -rf $HOME/.local/share/icons/hicolor/scalable/apps/lite-xl.svg \
|
||||||
|
$HOME/.local/share/applications/org.lite_xl.lite_xl.desktop \
|
||||||
|
$HOME/.local/share/metainfo/org.lite_xl.lite_xl.appdata.xml \
|
||||||
|
$HOME/.local/share/lite-xl
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Any additional functionality that can be added through a plugin should be done
|
Any additional functionality that can be added through a plugin should be done
|
||||||
|
@ -60,11 +146,12 @@ the terms of the MIT license. See [LICENSE] for details.
|
||||||
See the [licenses] file for details on licenses used by the required dependencies.
|
See the [licenses] file for details on licenses used by the required dependencies.
|
||||||
|
|
||||||
|
|
||||||
|
[CI]: https://github.com/lite-xl/lite-xl/actions/workflows/build.yml/badge.svg
|
||||||
[Discord Badge Image]: https://img.shields.io/discord/847122429742809208?label=discord&logo=discord
|
[Discord Badge Image]: https://img.shields.io/discord/847122429742809208?label=discord&logo=discord
|
||||||
[screenshot-dark]: https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png
|
[screenshot-dark]: https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png
|
||||||
[lite]: https://github.com/rxi/lite
|
[lite]: https://github.com/rxi/lite
|
||||||
[website]: https://lite-xl.github.io
|
[website]: https://lite-xl.github.io
|
||||||
[build]: https://lite-xl.github.io/en/build
|
[build]: https://lite-xl.github.io/en/documentation/build/
|
||||||
[Get Lite XL]: https://github.com/franko/lite-xl/releases/latest
|
[Get Lite XL]: https://github.com/franko/lite-xl/releases/latest
|
||||||
[Get plugins]: https://github.com/franko/lite-plugins
|
[Get plugins]: https://github.com/franko/lite-plugins
|
||||||
[Get color themes]: https://github.com/rxi/lite-colors
|
[Get color themes]: https://github.com/rxi/lite-colors
|
||||||
|
|
|
@ -1,216 +1,164 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
# strip-components is normally set to 1 to strip the initial "data" from the
|
if [ ! -e "src/api/api.h" ]; then
|
||||||
# directory path.
|
echo "Please run this script from the root directory of Lite XL."; exit 1
|
||||||
copy_directory_from_repo () {
|
fi
|
||||||
local tar_options=()
|
|
||||||
if [[ $1 == --strip-components=* ]]; then
|
source scripts/common.sh
|
||||||
tar_options+=($1)
|
|
||||||
shift
|
show_help() {
|
||||||
fi
|
echo
|
||||||
local dirname="$1"
|
echo "Usage: $0 <OPTIONS>"
|
||||||
local destdir="$2"
|
echo
|
||||||
git archive "$use_branch" "$dirname" --format=tar | tar xf - -C "$destdir" "${tar_options[@]}"
|
echo "Common options:"
|
||||||
|
echo
|
||||||
|
echo "-h --help Show this help and exit."
|
||||||
|
echo "-b --builddir DIRNAME Set the name of the build directory (not path)."
|
||||||
|
echo " Default: '$(get_default_build_dir)'."
|
||||||
|
echo "-p --prefix PREFIX Install directory prefix."
|
||||||
|
echo " Default: '/'."
|
||||||
|
echo " --debug Debug this script."
|
||||||
|
echo
|
||||||
|
echo "Build options:"
|
||||||
|
echo
|
||||||
|
echo "-f --forcefallback Force to build subprojects dependencies statically."
|
||||||
|
echo "-B --bundle Create an App bundle (macOS only)"
|
||||||
|
echo "-P --portable Create a portable package."
|
||||||
|
echo "-O --pgo Use profile guided optimizations (pgo)."
|
||||||
|
echo " Requires running the application iteractively."
|
||||||
|
echo
|
||||||
|
echo "Package options:"
|
||||||
|
echo
|
||||||
|
echo "-d --destdir DIRNAME Set the name of the package directory (not path)."
|
||||||
|
echo " Default: 'lite-xl'."
|
||||||
|
echo "-v --version VERSION Sets the version on the package name."
|
||||||
|
echo "-A --appimage Create an AppImage (Linux only)."
|
||||||
|
echo "-D --dmg Create a DMG disk image (macOS only)."
|
||||||
|
echo " Requires NPM and AppDMG."
|
||||||
|
echo "-I --innosetup Create an InnoSetup installer (Windows only)."
|
||||||
|
echo "-S --source Create a source code package,"
|
||||||
|
echo " including subprojects dependencies."
|
||||||
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if build directory is ok to be used to build.
|
main() {
|
||||||
build_dir_is_usable () {
|
local build_dir
|
||||||
local build="$1"
|
local build_dir_option=()
|
||||||
if [[ $build == */* || -z "$build" ]]; then
|
local dest_dir
|
||||||
echo "invalid build directory, no path allowed: \"$build\""
|
local dest_dir_option=()
|
||||||
return 1
|
local prefix
|
||||||
fi
|
local prefix_option=()
|
||||||
git ls-files --error-unmatch "$build" &> /dev/null
|
local version
|
||||||
if [ $? == 0 ]; then
|
local version_option=()
|
||||||
echo "invalid path, \"$build\" is under revision control"
|
local debug
|
||||||
return 1
|
local force_fallback
|
||||||
fi
|
local appimage
|
||||||
}
|
local bundle
|
||||||
|
local innosetup
|
||||||
|
local portable
|
||||||
|
local pgo
|
||||||
|
|
||||||
# Ordinary release build
|
for i in "$@"; do
|
||||||
lite_build () {
|
case $i in
|
||||||
local build="$1"
|
-h|--help)
|
||||||
build_dir_is_usable "$build" || exit 1
|
show_help
|
||||||
rm -fr "$build"
|
exit 0
|
||||||
meson setup --buildtype=release "$build" || exit 1
|
;;
|
||||||
ninja -C "$build" || exit 1
|
-b|--builddir)
|
||||||
}
|
build_dir="$2"
|
||||||
|
shift
|
||||||
# Build using Profile Guided Optimizations (PGO)
|
shift
|
||||||
lite_build_pgo () {
|
;;
|
||||||
local build="$1"
|
-d|--destdir)
|
||||||
build_dir_is_usable "$build" || exit 1
|
dest_dir="$2"
|
||||||
rm -fr "$build"
|
shift
|
||||||
meson setup --buildtype=release -Db_pgo=generate "$build" || exit 1
|
shift
|
||||||
ninja -C "$build" || exit 1
|
;;
|
||||||
copy_directory_from_repo data "$build/src"
|
-f|--forcefallback)
|
||||||
"$build/src/lite-xl"
|
force_fallback="--forcefallback"
|
||||||
meson configure -Db_pgo=use "$build"
|
shift
|
||||||
ninja -C "$build" || exit 1
|
;;
|
||||||
}
|
-p|--prefix)
|
||||||
|
prefix="$2"
|
||||||
lite_build_package_windows () {
|
shift
|
||||||
local portable="-msys"
|
shift
|
||||||
if [ "$1" == "-portable" ]; then
|
;;
|
||||||
portable=""
|
-v|--version)
|
||||||
shift
|
version="$2"
|
||||||
fi
|
shift
|
||||||
local build="$1"
|
shift
|
||||||
local arch="$2"
|
;;
|
||||||
local os="win"
|
-A|--appimage)
|
||||||
local pdir=".package-build/lite-xl"
|
appimage="--appimage"
|
||||||
if [ -z "$portable" ]; then
|
shift
|
||||||
local bindir="$pdir"
|
;;
|
||||||
local datadir="$pdir/data"
|
-B|--bundle)
|
||||||
else
|
bundle="--bundle"
|
||||||
local bindir="$pdir/bin"
|
shift
|
||||||
local datadir="$pdir/share/lite-xl"
|
;;
|
||||||
fi
|
-D|--dmg)
|
||||||
mkdir -p "$bindir"
|
dmg="--dmg"
|
||||||
mkdir -p "$datadir"
|
shift
|
||||||
for module_name in core plugins colors fonts; do
|
;;
|
||||||
copy_directory_from_repo --strip-components=1 "data/$module_name" "$datadir"
|
-I|--innosetup)
|
||||||
|
innosetup="--innosetup"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-P|--portable)
|
||||||
|
portable="--portable"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-S|--source)
|
||||||
|
source="--source"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-O|--pgo)
|
||||||
|
pgo="--pgo"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--debug)
|
||||||
|
debug="--debug"
|
||||||
|
set -x
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# unknown option
|
||||||
|
;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
for module_name in plugins colors; do
|
|
||||||
cp -r "$build/third/data/$module_name" "$datadir"
|
|
||||||
done
|
|
||||||
cp "$build/src/lite-xl.exe" "$bindir"
|
|
||||||
strip --strip-all "$bindir/lite-xl.exe"
|
|
||||||
pushd ".package-build"
|
|
||||||
local package_name="lite-xl-$os-$arch$portable.zip"
|
|
||||||
zip "$package_name" -r "lite-xl"
|
|
||||||
mv "$package_name" ..
|
|
||||||
popd
|
|
||||||
rm -fr ".package-build"
|
|
||||||
echo "created package $package_name"
|
|
||||||
}
|
|
||||||
|
|
||||||
lite_build_package_macos () {
|
if [[ -n $1 ]]; then
|
||||||
local build="$1"
|
show_help
|
||||||
local arch="$2"
|
|
||||||
local os="macos"
|
|
||||||
|
|
||||||
local appdir=".package-build/lite-xl.app"
|
|
||||||
local bindir="$appdir/Contents/MacOS"
|
|
||||||
local datadir="$appdir/Contents/Resources"
|
|
||||||
mkdir -p "$bindir" "$datadir"
|
|
||||||
for module_name in core plugins colors fonts; do
|
|
||||||
copy_directory_from_repo --strip-components=1 "data/$module_name" "$datadir"
|
|
||||||
done
|
|
||||||
for module_name in plugins colors; do
|
|
||||||
cp -r "$build/third/data/$module_name" "$datadir"
|
|
||||||
done
|
|
||||||
cp resources/icons/icon.icns "$appdir/Contents/Resources/icon.icns"
|
|
||||||
cp resources/macos/Info.plist "$appdir/Contents/Info.plist"
|
|
||||||
cp "$build/src/lite-xl" "$bindir/lite-xl"
|
|
||||||
strip "$bindir/lite-xl"
|
|
||||||
pushd ".package-build"
|
|
||||||
local package_name="lite-xl-$os-$arch.zip"
|
|
||||||
zip "$package_name" -r "lite-xl.app"
|
|
||||||
mv "$package_name" ..
|
|
||||||
popd
|
|
||||||
rm -fr ".package-build"
|
|
||||||
echo "created package $package_name"
|
|
||||||
}
|
|
||||||
|
|
||||||
lite_build_package_linux () {
|
|
||||||
local portable=""
|
|
||||||
if [ "$1" == "-portable" ]; then
|
|
||||||
portable="-portable"
|
|
||||||
shift
|
|
||||||
fi
|
|
||||||
local build="$1"
|
|
||||||
local arch="$2"
|
|
||||||
local os="linux"
|
|
||||||
local pdir=".package-build/lite-xl"
|
|
||||||
if [ "$portable" == "-portable" ]; then
|
|
||||||
local bindir="$pdir"
|
|
||||||
local datadir="$pdir/data"
|
|
||||||
else
|
|
||||||
local bindir="$pdir/bin"
|
|
||||||
local datadir="$pdir/share/lite-xl"
|
|
||||||
fi
|
|
||||||
mkdir -p "$bindir"
|
|
||||||
mkdir -p "$datadir"
|
|
||||||
for module_name in core plugins colors fonts; do
|
|
||||||
copy_directory_from_repo --strip-components=1 "data/$module_name" "$datadir"
|
|
||||||
done
|
|
||||||
for module_name in plugins colors; do
|
|
||||||
cp -r "$build/third/data/$module_name" "$datadir"
|
|
||||||
done
|
|
||||||
cp "$build/src/lite-xl" "$bindir"
|
|
||||||
strip "$bindir/lite-xl"
|
|
||||||
if [ -z "$portable" ]; then
|
|
||||||
mkdir -p "$pdir/share/applications" "$pdir/share/icons/hicolor/scalable/apps"
|
|
||||||
cp "resources/linux/lite-xl.desktop" "$pdir/share/applications"
|
|
||||||
cp "resources/icons/lite-xl.svg" "$pdir/share/icons/hicolor/scalable/apps/lite-xl.svg"
|
|
||||||
fi
|
|
||||||
pushd ".package-build"
|
|
||||||
local package_name="lite-xl-$os-$arch$portable.tar.gz"
|
|
||||||
tar czf "$package_name" "lite-xl"
|
|
||||||
mv "$package_name" ..
|
|
||||||
popd
|
|
||||||
rm -fr ".package-build"
|
|
||||||
echo "created package $package_name"
|
|
||||||
}
|
|
||||||
|
|
||||||
lite_build_package () {
|
|
||||||
if [[ "$OSTYPE" == msys || "$OSTYPE" == win32 ]]; then
|
|
||||||
lite_build_package_windows "$@"
|
|
||||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
lite_build_package_macos "$@"
|
|
||||||
elif [[ "$OSTYPE" == "linux"* || "$OSTYPE" == "freebsd"* ]]; then
|
|
||||||
lite_build_package_linux "$@"
|
|
||||||
else
|
|
||||||
echo "Unknown OS type \"$OSTYPE\""
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -n $build_dir ]]; then build_dir_option=("--builddir" "${build_dir}"); fi
|
||||||
|
if [[ -n $dest_dir ]]; then dest_dir_option=("--destdir" "${dest_dir}"); fi
|
||||||
|
if [[ -n $prefix ]]; then prefix_option=("--prefix" "${prefix}"); fi
|
||||||
|
if [[ -n $version ]]; then version_option=("--version" "${version}"); fi
|
||||||
|
|
||||||
|
source scripts/build.sh \
|
||||||
|
${build_dir_option[@]} \
|
||||||
|
${prefix_option[@]} \
|
||||||
|
$debug \
|
||||||
|
$force_fallback \
|
||||||
|
$bundle \
|
||||||
|
$portable \
|
||||||
|
$pgo
|
||||||
|
|
||||||
|
source scripts/package.sh \
|
||||||
|
${build_dir_option[@]} \
|
||||||
|
${dest_dir_option[@]} \
|
||||||
|
${prefix_option[@]} \
|
||||||
|
${version_option[@]} \
|
||||||
|
--binary \
|
||||||
|
--addons \
|
||||||
|
$debug \
|
||||||
|
$appimage \
|
||||||
|
$dmg \
|
||||||
|
$innosetup \
|
||||||
|
$source
|
||||||
}
|
}
|
||||||
|
|
||||||
lite_copy_third_party_modules () {
|
main "$@"
|
||||||
local build="$1"
|
|
||||||
curl --insecure -L "https://github.com/rxi/lite-colors/archive/master.zip" -o "$build/rxi-lite-colors.zip"
|
|
||||||
mkdir -p "$build/third/data/colors" "$build/third/data/plugins"
|
|
||||||
unzip "$build/rxi-lite-colors.zip" -d "$build"
|
|
||||||
mv "$build/lite-colors-master/colors" "$build/third/data"
|
|
||||||
rm -fr "$build/lite-colors-master"
|
|
||||||
}
|
|
||||||
|
|
||||||
unset arch
|
|
||||||
while [ ! -z {$1+x} ]; do
|
|
||||||
case $1 in
|
|
||||||
-pgo)
|
|
||||||
pgo=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-branch=*)
|
|
||||||
use_branch="${1#-branch=}"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
arch="$1"
|
|
||||||
break
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z ${arch+set} ]; then
|
|
||||||
echo "usage: $0 [options] <arch>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z ${use_branch+set} ]; then
|
|
||||||
use_branch="$(git rev-parse --abbrev-ref HEAD)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
build_dir=".build-$arch"
|
|
||||||
|
|
||||||
if [ -z ${pgo+set} ]; then
|
|
||||||
lite_build "$build_dir"
|
|
||||||
else
|
|
||||||
lite_build_pgo "$build_dir"
|
|
||||||
fi
|
|
||||||
lite_copy_third_party_modules "$build_dir"
|
|
||||||
lite_build_package "$build_dir" "$arch"
|
|
||||||
if [[ ! ( "$OSTYPE" == "linux"* || "$OSTYPE" == "freebsd"* || "$OSTYPE" == "darwin"* ) ]]; then
|
|
||||||
lite_build_package -portable "$build_dir" "$arch"
|
|
||||||
fi
|
|
||||||
|
|
80
changelog.md
80
changelog.md
|
@ -1,5 +1,85 @@
|
||||||
This files document the changes done in Lite XL for each release.
|
This files document the changes done in Lite XL for each release.
|
||||||
|
|
||||||
|
### 2.0.2
|
||||||
|
|
||||||
|
Fix problem project directory when starting the application from Launcher on macOS.
|
||||||
|
|
||||||
|
Improved LogView. Entries can now be expanded and there is a context menu to copy the item's content.
|
||||||
|
|
||||||
|
Change the behavior of `ctrl+d` to add a multi-cursor selection to the next occurrence.
|
||||||
|
The old behavior to move the selection to the next occurrence is now done using the shortcut `ctrl+f3`.
|
||||||
|
|
||||||
|
Added a command to create a multi-cursor with all the occurrences of the current selection.
|
||||||
|
Activated with the shortcut `ctrl+shift+l`.
|
||||||
|
|
||||||
|
Fix problem when trying to close an unsaved new document.
|
||||||
|
|
||||||
|
No longer shows an error for the `-psn` argument passed to the application on macOS.
|
||||||
|
|
||||||
|
Fix `treeview:open-in-system` command on Windows.
|
||||||
|
|
||||||
|
Fix rename command to update name of document if opened.
|
||||||
|
|
||||||
|
Improve the find and replace dialog so that previously used expressions can be recalled
|
||||||
|
using "up" and "down" keys.
|
||||||
|
|
||||||
|
Build package script rewrite with many improvements.
|
||||||
|
|
||||||
|
Use bigger fonts by default.
|
||||||
|
|
||||||
|
Other minor improvements and fixes.
|
||||||
|
|
||||||
|
With many thanks to the contributors: @adamharrison, @takase1121, @Guldoman, @redtide, @Timofffee, @boppyt, @Jan200101.
|
||||||
|
|
||||||
|
### 2.0.1
|
||||||
|
|
||||||
|
Fix a few bugs and we mandate the mod-version 2 for plugins.
|
||||||
|
This means that users should ensure they have up-to-date plugins for Lite XL 2.0.
|
||||||
|
|
||||||
|
Here some details about the bug fixes:
|
||||||
|
|
||||||
|
- fix a bug that created a fatal error when using the command to change project folder or when closing all the active documents
|
||||||
|
- add a limit to avoid scaling fonts too much and fix a related invalid memory access for very small fonts
|
||||||
|
- fix focus problem with NagView when switching project directory
|
||||||
|
- fix error that prevented the verification of plugins versions
|
||||||
|
- fix error on X11 that caused a bug window event on exit
|
||||||
|
|
||||||
|
### 2.0
|
||||||
|
|
||||||
|
The 2.0 version of lite contains *breaking changes* to lite, in terms of how plugin settings are structured;
|
||||||
|
any custom plugins may need to be adjusted accordingly (see note below about plugin namespacing).
|
||||||
|
|
||||||
|
Contains the following new features:
|
||||||
|
|
||||||
|
Full PCRE (regex) support for find and replace, as well as in language syntax definitions. Can be accessed
|
||||||
|
programatically via the lua `regex` module.
|
||||||
|
|
||||||
|
A full, finalized subprocess API, using libreproc. Subprocess can be started and interacted with using
|
||||||
|
`Process.new`.
|
||||||
|
|
||||||
|
Support for multi-cursor editing. Cursors can be created by either ctrl+clicking on the screen, or by using
|
||||||
|
the keyboard shortcuts ctrl+shift+up/down to create an additional cursor on the previous/next line.
|
||||||
|
|
||||||
|
All build systems other than meson removed.
|
||||||
|
|
||||||
|
A more organized directory structure has been implemented; in particular a docs folder which contains C api
|
||||||
|
documentation, and a resource folder which houses all build resources.
|
||||||
|
|
||||||
|
Plugin config namespacing has been implemented. This means that instead of using `config.myplugin.a`,
|
||||||
|
to read settings, and `config.myplugin = false` to disable plugins, this has been changed to
|
||||||
|
`config.plugins.myplugin.a`, and `config.plugins.myplugin = false` repsectively. This may require changes to
|
||||||
|
your user plugin, or to any custom plugins you have.
|
||||||
|
|
||||||
|
A context menu on right click has been added.
|
||||||
|
|
||||||
|
Changes to how we deal with indentation have been implemented; in particular, hitting home no longer brings you
|
||||||
|
to the start of a line, it'll bring you to the start of indentation, which is more in line with other editors.
|
||||||
|
|
||||||
|
Lineguide, and scale plugins moved into the core, and removed from `lite-plugins`. This may also require you to
|
||||||
|
adjust your personal plugin folder to remove these if they're present.
|
||||||
|
|
||||||
|
In addition, there have been many other small fixes and improvements, too numerous to list here.
|
||||||
|
|
||||||
### 1.16.11
|
### 1.16.11
|
||||||
|
|
||||||
When opening directories with too many files lite-xl now keep diplaying files and directories in the treeview.
|
When opening directories with too many files lite-xl now keep diplaying files and directories in the treeview.
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
local b05 = 'rgba(0,0,0,0.5)' local red = '#994D4D'
|
||||||
|
local b80 = '#333333' local orange = '#B3661A'
|
||||||
|
local b60 = '#808080' local green = '#52994D'
|
||||||
|
local b40 = '#ADADAD' local teal = '#4D9999'
|
||||||
|
local b20 = '#CECECE' local blue = '#1A66B3'
|
||||||
|
local b00 = '#E6E6E6' local magenta = '#994D99'
|
||||||
|
--------------------------=--------------------------
|
||||||
|
local style = require 'core.style'
|
||||||
|
local common = require 'core.common'
|
||||||
|
--------------------------=--------------------------
|
||||||
|
style.line_highlight = { common.color(b20) }
|
||||||
|
style.background = { common.color(b00) }
|
||||||
|
style.background2 = { common.color(b20) }
|
||||||
|
style.background3 = { common.color(b20) }
|
||||||
|
style.text = { common.color(b60) }
|
||||||
|
style.caret = { common.color(b80) }
|
||||||
|
style.accent = { common.color(b80) }
|
||||||
|
style.dim = { common.color(b60) }
|
||||||
|
style.divider = { common.color(b40) }
|
||||||
|
style.selection = { common.color(b40) }
|
||||||
|
style.line_number = { common.color(b60) }
|
||||||
|
style.line_number2 = { common.color(b80) }
|
||||||
|
style.scrollbar = { common.color(b40) }
|
||||||
|
style.scrollbar2 = { common.color(b60) }
|
||||||
|
style.nagbar = { common.color(red) }
|
||||||
|
style.nagbar_text = { common.color(b00) }
|
||||||
|
style.nagbar_dim = { common.color(b05) }
|
||||||
|
--------------------------=--------------------------
|
||||||
|
style.syntax = {}
|
||||||
|
style.syntax['normal'] = { common.color(b80) }
|
||||||
|
style.syntax['symbol'] = { common.color(b80) }
|
||||||
|
style.syntax['comment'] = { common.color(b60) }
|
||||||
|
style.syntax['keyword'] = { common.color(blue) }
|
||||||
|
style.syntax['keyword2'] = { common.color(red) }
|
||||||
|
style.syntax['number'] = { common.color(teal) }
|
||||||
|
style.syntax['literal'] = { common.color(blue) }
|
||||||
|
style.syntax['string'] = { common.color(green) }
|
||||||
|
style.syntax['operator'] = { common.color(magenta) }
|
||||||
|
style.syntax['function'] = { common.color(blue) }
|
||||||
|
--------------------------=--------------------------
|
||||||
|
style.syntax.paren1 = { common.color(magenta) }
|
||||||
|
style.syntax.paren2 = { common.color(orange) }
|
||||||
|
style.syntax.paren3 = { common.color(teal) }
|
||||||
|
style.syntax.paren4 = { common.color(blue) }
|
||||||
|
style.syntax.paren5 = { common.color(red) }
|
||||||
|
--------------------------=--------------------------
|
||||||
|
style.lint = {}
|
||||||
|
style.lint.info = { common.color(blue) }
|
||||||
|
style.lint.hint = { common.color(green) }
|
||||||
|
style.lint.warning = { common.color(red) }
|
||||||
|
style.lint.error = { common.color(orange) }
|
|
@ -42,10 +42,10 @@ function command.get_all_valid()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function perform(name)
|
local function perform(name, ...)
|
||||||
local cmd = command.map[name]
|
local cmd = command.map[name]
|
||||||
if cmd and cmd.predicate() then
|
if cmd and cmd.predicate(...) then
|
||||||
cmd.perform()
|
cmd.perform(...)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -104,11 +104,20 @@ command.add(nil, {
|
||||||
end, function (text)
|
end, function (text)
|
||||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||||
end, nil, function(text)
|
end, nil, function(text)
|
||||||
local path_stat, err = system.get_file_info(common.home_expand(text))
|
local filename = common.home_expand(text)
|
||||||
|
local path_stat, err = system.get_file_info(filename)
|
||||||
if err then
|
if err then
|
||||||
core.error("Cannot open file %q: %q", text, err)
|
if err:find("No such file", 1, true) then
|
||||||
|
-- check if the containing directory exists
|
||||||
|
local dirname = common.dirname(filename)
|
||||||
|
local dir_stat = dirname and system.get_file_info(dirname)
|
||||||
|
if not dirname or (dir_stat and dir_stat.type == 'dir') then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
core.error("Cannot open file %s: %s", text, err)
|
||||||
elseif path_stat.type == 'dir' then
|
elseif path_stat.type == 'dir' then
|
||||||
core.error("Cannot open %q, is a folder", text)
|
core.error("Cannot open %s, is a folder", text)
|
||||||
else
|
else
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
@ -138,6 +147,10 @@ command.add(nil, {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:change-project-folder"] = function()
|
["core:change-project-folder"] = function()
|
||||||
|
local dirname = common.dirname(core.project_dir)
|
||||||
|
if dirname then
|
||||||
|
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
|
||||||
|
end
|
||||||
core.command_view:enter("Change Project Folder", function(text, item)
|
core.command_view:enter("Change Project Folder", function(text, item)
|
||||||
text = system.absolute_path(common.home_expand(item and item.text or text))
|
text = system.absolute_path(common.home_expand(item and item.text or text))
|
||||||
if text == core.project_dir then return end
|
if text == core.project_dir then return end
|
||||||
|
@ -146,11 +159,15 @@ command.add(nil, {
|
||||||
core.error("Cannot open folder %q", text)
|
core.error("Cannot open folder %q", text)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
core.confirm_close_all(core.open_folder_project, text)
|
core.confirm_close_docs(core.docs, core.open_folder_project, text)
|
||||||
end, suggest_directory)
|
end, suggest_directory)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:open-project-folder"] = function()
|
["core:open-project-folder"] = function()
|
||||||
|
local dirname = common.dirname(core.project_dir)
|
||||||
|
if dirname then
|
||||||
|
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
|
||||||
|
end
|
||||||
core.command_view:enter("Open Project", function(text, item)
|
core.command_view:enter("Open Project", function(text, item)
|
||||||
text = common.home_expand(item and item.text or text)
|
text = common.home_expand(item and item.text or text)
|
||||||
local path_stat = system.get_file_info(text)
|
local path_stat = system.get_file_info(text)
|
||||||
|
|
|
@ -43,7 +43,12 @@ local function append_line_if_last_line(line)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function save(filename)
|
local function save(filename)
|
||||||
doc():save(filename and core.normalize_to_project_dir(filename))
|
local abs_filename
|
||||||
|
if filename then
|
||||||
|
filename = core.normalize_to_project_dir(filename)
|
||||||
|
abs_filename = core.project_absolute_path(filename)
|
||||||
|
end
|
||||||
|
doc():save(filename, abs_filename)
|
||||||
local saved_filename = doc().filename
|
local saved_filename = doc().filename
|
||||||
core.log("Saved \"%s\"", saved_filename)
|
core.log("Saved \"%s\"", saved_filename)
|
||||||
end
|
end
|
||||||
|
@ -62,13 +67,14 @@ local function cut_or_copy(delete)
|
||||||
doc().cursor_clipboard[idx] = ""
|
doc().cursor_clipboard[idx] = ""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
doc().cursor_clipboard["full"] = full_text
|
||||||
system.set_clipboard(full_text)
|
system.set_clipboard(full_text)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function split_cursor(direction)
|
local function split_cursor(direction)
|
||||||
local new_cursors = {}
|
local new_cursors = {}
|
||||||
for _, line1, col1 in doc():get_selections() do
|
for _, line1, col1 in doc():get_selections() do
|
||||||
if line1 > 1 and line1 < #doc().lines then
|
if line1 + direction >= 1 and line1 + direction <= #doc().lines then
|
||||||
table.insert(new_cursors, { line1 + direction, col1 })
|
table.insert(new_cursors, { line1 + direction, col1 })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -76,6 +82,16 @@ local function split_cursor(direction)
|
||||||
core.blink_reset()
|
core.blink_reset()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function set_cursor(x, y, type)
|
||||||
|
local line, col = dv():resolve_screen_position(x, y)
|
||||||
|
doc():set_selection(line, col, line, col)
|
||||||
|
if type == "word" or type == "lines" then
|
||||||
|
command.perform("doc:select-" .. type)
|
||||||
|
end
|
||||||
|
dv().mouse_selecting = { line, col }
|
||||||
|
core.blink_reset()
|
||||||
|
end
|
||||||
|
|
||||||
local commands = {
|
local commands = {
|
||||||
["doc:undo"] = function()
|
["doc:undo"] = function()
|
||||||
doc():undo()
|
doc():undo()
|
||||||
|
@ -94,8 +110,13 @@ local commands = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:paste"] = function()
|
["doc:paste"] = function()
|
||||||
|
local clipboard = system.get_clipboard()
|
||||||
|
-- If the clipboard has changed since our last look, use that instead
|
||||||
|
if doc().cursor_clipboard["full"] ~= clipboard then
|
||||||
|
doc().cursor_clipboard = {}
|
||||||
|
end
|
||||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||||
local value = doc().cursor_clipboard[idx] or system.get_clipboard()
|
local value = doc().cursor_clipboard[idx] or clipboard
|
||||||
doc():text_input(value:gsub("\r", ""), idx)
|
doc():text_input(value:gsub("\r", ""), idx)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
@ -157,16 +178,6 @@ local commands = {
|
||||||
doc():set_selection(line, col)
|
doc():set_selection(line, col)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
|
||||||
["doc:indent"] = function()
|
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
|
||||||
local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2)
|
|
||||||
if l1 then
|
|
||||||
doc():set_selections(idx, l1, c1, l2, c2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:select-lines"] = function()
|
["doc:select-lines"] = function()
|
||||||
for idx, line1, _, line2 in doc():get_selections(true) do
|
for idx, line1, _, line2 in doc():get_selections(true) do
|
||||||
append_line_if_last_line(line2)
|
append_line_if_last_line(line2)
|
||||||
|
@ -363,12 +374,14 @@ local commands = {
|
||||||
end
|
end
|
||||||
core.command_view:set_text(old_filename)
|
core.command_view:set_text(old_filename)
|
||||||
core.command_view:enter("Rename", function(filename)
|
core.command_view:enter("Rename", function(filename)
|
||||||
doc():save(filename)
|
save(common.home_expand(filename))
|
||||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||||
if filename ~= old_filename then
|
if filename ~= old_filename then
|
||||||
os.remove(old_filename)
|
os.remove(old_filename)
|
||||||
end
|
end
|
||||||
end, common.path_suggest)
|
end, function (text)
|
||||||
|
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||||
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
|
||||||
|
@ -385,6 +398,30 @@ local commands = {
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
core.log("Removed \"%s\"", filename)
|
core.log("Removed \"%s\"", filename)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
["doc:select-to-cursor"] = function(x, y, clicks)
|
||||||
|
local line1, col1 = select(3, doc():get_selection())
|
||||||
|
local line2, col2 = dv():resolve_screen_position(x, y)
|
||||||
|
dv().mouse_selecting = { line1, col1 }
|
||||||
|
doc():set_selection(line2, col2, line1, col1)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:set-cursor"] = function(x, y)
|
||||||
|
set_cursor(x, y, "set")
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:set-cursor-word"] = function(x, y)
|
||||||
|
set_cursor(x, y, "word")
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:set-cursor-line"] = function(x, y, clicks)
|
||||||
|
set_cursor(x, y, "lines")
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:split-cursor"] = function(x, y, clicks)
|
||||||
|
local line, col = dv():resolve_screen_position(x, y)
|
||||||
|
doc():add_selection(line, col, line, col)
|
||||||
|
end,
|
||||||
|
|
||||||
["doc:create-cursor-previous-line"] = function()
|
["doc:create-cursor-previous-line"] = function()
|
||||||
split_cursor(-1)
|
split_cursor(-1)
|
||||||
|
@ -411,6 +448,7 @@ local translations = {
|
||||||
["start-of-line"] = translate.start_of_line,
|
["start-of-line"] = translate.start_of_line,
|
||||||
["end-of-line"] = translate.end_of_line,
|
["end-of-line"] = translate.end_of_line,
|
||||||
["start-of-word"] = translate.start_of_word,
|
["start-of-word"] = translate.start_of_word,
|
||||||
|
["start-of-indentation"] = translate.start_of_indentation,
|
||||||
["end-of-word"] = translate.end_of_word,
|
["end-of-word"] = translate.end_of_word,
|
||||||
["previous-line"] = DocView.translate.previous_line,
|
["previous-line"] = DocView.translate.previous_line,
|
||||||
["next-line"] = DocView.translate.next_line,
|
["next-line"] = DocView.translate.next_line,
|
||||||
|
|
|
@ -12,6 +12,7 @@ local last_finds, last_view, last_fn, last_text, last_sel
|
||||||
|
|
||||||
local case_sensitive = config.find_case_sensitive or false
|
local case_sensitive = config.find_case_sensitive or false
|
||||||
local find_regex = config.find_regex or false
|
local find_regex = config.find_regex or false
|
||||||
|
local found_expression
|
||||||
|
|
||||||
local function doc()
|
local function doc()
|
||||||
return core.active_view:is(DocView) and core.active_view.doc or last_view.doc
|
return core.active_view:is(DocView) and core.active_view.doc or last_view.doc
|
||||||
|
@ -29,43 +30,57 @@ local function get_find_tooltip()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function update_preview(sel, search_fn, text)
|
local function update_preview(sel, search_fn, text)
|
||||||
local ok, line1, col1, line2, col2 =
|
local ok, line1, col1, line2, col2 = pcall(search_fn, last_view.doc,
|
||||||
pcall(search_fn, last_view.doc, sel[1], sel[2], text, case_sensitive, find_regex)
|
sel[1], sel[2], text, case_sensitive, find_regex)
|
||||||
if ok and line1 and text ~= "" then
|
if ok and line1 and text ~= "" then
|
||||||
last_view.doc:set_selection(line2, col2, line1, col1)
|
last_view.doc:set_selection(line2, col2, line1, col1)
|
||||||
last_view:scroll_to_line(line2, true)
|
last_view:scroll_to_line(line2, true)
|
||||||
return true
|
found_expression = true
|
||||||
else
|
else
|
||||||
last_view.doc:set_selection(unpack(sel))
|
last_view.doc:set_selection(table.unpack(sel))
|
||||||
return false
|
found_expression = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function insert_unique(t, v)
|
||||||
|
local n = #t
|
||||||
|
for i = 1, n do
|
||||||
|
if t[i] == v then return end
|
||||||
|
end
|
||||||
|
t[n + 1] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
local function find(label, search_fn)
|
local function find(label, search_fn)
|
||||||
last_view, last_sel, last_finds = core.active_view,
|
last_view, last_sel, last_finds = core.active_view,
|
||||||
{ core.active_view.doc:get_selection() }, {}
|
{ core.active_view.doc:get_selection() }, {}
|
||||||
local text, found = last_view.doc:get_text(unpack(last_sel)), false
|
local text = last_view.doc:get_text(table.unpack(last_sel))
|
||||||
|
found_expression = false
|
||||||
|
|
||||||
core.command_view:set_text(text, true)
|
core.command_view:set_text(text, true)
|
||||||
core.status_view:show_tooltip(get_find_tooltip())
|
core.status_view:show_tooltip(get_find_tooltip())
|
||||||
|
|
||||||
core.command_view:enter(label, function(text)
|
core.command_view:set_hidden_suggestions()
|
||||||
|
core.command_view:enter(label, function(text, item)
|
||||||
|
insert_unique(core.previous_find, text)
|
||||||
core.status_view:remove_tooltip()
|
core.status_view:remove_tooltip()
|
||||||
if found then
|
if found_expression then
|
||||||
last_fn, last_text = search_fn, text
|
last_fn, last_text = search_fn, text
|
||||||
else
|
else
|
||||||
core.error("Couldn't find %q", text)
|
core.error("Couldn't find %q", text)
|
||||||
last_view.doc:set_selection(unpack(last_sel))
|
last_view.doc:set_selection(table.unpack(last_sel))
|
||||||
last_view:scroll_to_make_visible(unpack(last_sel))
|
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
||||||
end
|
end
|
||||||
end, function(text)
|
end, function(text)
|
||||||
found = update_preview(last_sel, search_fn, text)
|
update_preview(last_sel, search_fn, text)
|
||||||
last_fn, last_text = search_fn, text
|
last_fn, last_text = search_fn, text
|
||||||
|
return core.previous_find
|
||||||
end, function(explicit)
|
end, function(explicit)
|
||||||
core.status_view:remove_tooltip()
|
core.status_view:remove_tooltip()
|
||||||
if explicit then
|
if explicit then
|
||||||
last_view.doc:set_selection(unpack(last_sel))
|
last_view.doc:set_selection(table.unpack(last_sel))
|
||||||
last_view:scroll_to_make_visible(unpack(last_sel))
|
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -75,18 +90,25 @@ local function replace(kind, default, fn)
|
||||||
core.command_view:set_text(default, true)
|
core.command_view:set_text(default, true)
|
||||||
|
|
||||||
core.status_view:show_tooltip(get_find_tooltip())
|
core.status_view:show_tooltip(get_find_tooltip())
|
||||||
|
core.command_view:set_hidden_suggestions()
|
||||||
core.command_view:enter("Find To Replace " .. kind, function(old)
|
core.command_view:enter("Find To Replace " .. kind, function(old)
|
||||||
|
insert_unique(core.previous_find, old)
|
||||||
core.command_view:set_text(old, true)
|
core.command_view:set_text(old, true)
|
||||||
|
|
||||||
local s = string.format("Replace %s %q With", kind, old)
|
local s = string.format("Replace %s %q With", kind, old)
|
||||||
|
core.command_view:set_hidden_suggestions()
|
||||||
core.command_view:enter(s, function(new)
|
core.command_view:enter(s, function(new)
|
||||||
|
core.status_view:remove_tooltip()
|
||||||
|
insert_unique(core.previous_replace, new)
|
||||||
local n = doc():replace(function(text)
|
local n = doc():replace(function(text)
|
||||||
return fn(text, old, new)
|
return fn(text, old, new)
|
||||||
end)
|
end)
|
||||||
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
|
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
|
||||||
end, function() end, function()
|
end, function() return core.previous_replace end, function()
|
||||||
core.status_view:remove_tooltip()
|
core.status_view:remove_tooltip()
|
||||||
end)
|
end)
|
||||||
|
end, function() return core.previous_find end, function()
|
||||||
|
core.status_view:remove_tooltip()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -94,13 +116,60 @@ local function has_selection()
|
||||||
return core.active_view:is(DocView) and core.active_view.doc:has_selection()
|
return core.active_view:is(DocView) and core.active_view.doc:has_selection()
|
||||||
end
|
end
|
||||||
|
|
||||||
command.add(has_selection, {
|
local function has_unique_selection()
|
||||||
|
if not core.active_view:is(DocView) then return false end
|
||||||
|
local text = nil
|
||||||
|
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do
|
||||||
|
if line1 == line2 and col1 == col2 then return false end
|
||||||
|
local selection = doc():get_text(line1, col1, line2, col2)
|
||||||
|
if text ~= nil and text ~= selection then return false end
|
||||||
|
text = selection
|
||||||
|
end
|
||||||
|
return text ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_in_selection(line, col, l1, c1, l2, c2)
|
||||||
|
if line < l1 or line > l2 then return false end
|
||||||
|
if line == l1 and col <= c1 then return false end
|
||||||
|
if line == l2 and col > c2 then return false end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_in_any_selection(line, col)
|
||||||
|
for idx, l1, c1, l2, c2 in doc():get_selections(true, false) do
|
||||||
|
if is_in_selection(line, col, l1, c1, l2, c2) then return true end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function select_next(all)
|
||||||
|
local il1, ic1 = doc():get_selection(true)
|
||||||
|
for idx, l1, c1, l2, c2 in doc():get_selections(true, true) do
|
||||||
|
local text = doc():get_text(l1, c1, l2, c2)
|
||||||
|
repeat
|
||||||
|
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||||
|
if l1 == il1 and c1 == ic1 then break end
|
||||||
|
if l2 and (all or not is_in_any_selection(l2, c2)) then
|
||||||
|
doc():add_selection(l2, c2, l1, c1)
|
||||||
|
if not all then
|
||||||
|
core.active_view:scroll_to_make_visible(l2, c2)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
until not all or not l2
|
||||||
|
if all then break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
command.add(has_unique_selection, {
|
||||||
["find-replace:select-next"] = function()
|
["find-replace:select-next"] = function()
|
||||||
local l1, c1, l2, c2 = doc():get_selection(true)
|
local l1, c1, l2, c2 = doc():get_selection(true)
|
||||||
local text = doc():get_text(l1, c1, l2, c2)
|
local text = doc():get_text(l1, c1, l2, c2)
|
||||||
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||||
if l2 then doc():set_selection(l2, c2, l1, c1) end
|
if l2 then doc():set_selection(l2, c2, l1, c1) end
|
||||||
end
|
end,
|
||||||
|
["find-replace:select-add-next"] = function() select_next(false) end,
|
||||||
|
["find-replace:select-add-all"] = function() select_next(true) end
|
||||||
})
|
})
|
||||||
|
|
||||||
command.add("core.docview", {
|
command.add("core.docview", {
|
||||||
|
@ -112,11 +181,13 @@ command.add("core.docview", {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["find-replace:replace"] = function()
|
["find-replace:replace"] = function()
|
||||||
replace("Text", doc():get_text(doc():get_selection(true)), function(text, old, new)
|
local l1, c1, l2, c2 = doc():get_selection()
|
||||||
|
local selected_text = doc():get_text(l1, c1, l2, c2)
|
||||||
|
replace("Text", l1 == l2 and selected_text or "", function(text, old, new)
|
||||||
if not find_regex then
|
if not find_regex then
|
||||||
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
||||||
end
|
end
|
||||||
local result, matches = regex.gsub(regex.compile(old), text, new)
|
local result, matches = regex.gsub(regex.compile(old, "m"), text, new)
|
||||||
return result, #matches
|
return result, #matches
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
@ -180,12 +251,12 @@ command.add("core.commandview", {
|
||||||
["find-replace:toggle-sensitivity"] = function()
|
["find-replace:toggle-sensitivity"] = function()
|
||||||
case_sensitive = not case_sensitive
|
case_sensitive = not case_sensitive
|
||||||
core.status_view:show_tooltip(get_find_tooltip())
|
core.status_view:show_tooltip(get_find_tooltip())
|
||||||
update_preview(last_sel, last_fn, last_text)
|
if last_sel then update_preview(last_sel, last_fn, last_text) end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["find-replace:toggle-regex"] = function()
|
["find-replace:toggle-regex"] = function()
|
||||||
find_regex = not find_regex
|
find_regex = not find_regex
|
||||||
core.status_view:show_tooltip(get_find_tooltip())
|
core.status_view:show_tooltip(get_find_tooltip())
|
||||||
update_preview(last_sel, last_fn, last_text)
|
if last_sel then update_preview(last_sel, last_fn, last_text) end
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,6 +3,7 @@ local style = require "core.style"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
|
local config = require "core.config"
|
||||||
|
|
||||||
|
|
||||||
local t = {
|
local t = {
|
||||||
|
@ -21,9 +22,15 @@ local t = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["root:close-all"] = function()
|
["root:close-all"] = function()
|
||||||
core.confirm_close_all(core.root_view.close_all_docviews, core.root_view)
|
core.confirm_close_docs(core.docs, core.root_view.close_all_docviews, core.root_view)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
["root:close-all-others"] = function()
|
||||||
|
local active_doc, docs = core.active_view and core.active_view.doc, {}
|
||||||
|
for i, v in ipairs(core.docs) do if v ~= active_doc then table.insert(docs, v) end end
|
||||||
|
core.confirm_close_docs(docs, core.root_view.close_all_docviews, core.root_view, true)
|
||||||
|
end,
|
||||||
|
|
||||||
["root:switch-to-previous-tab"] = function()
|
["root:switch-to-previous-tab"] = function()
|
||||||
local node = core.root_view:get_active_node()
|
local node = core.root_view:get_active_node()
|
||||||
local idx = node:get_view_idx(core.active_view)
|
local idx = node:get_view_idx(core.active_view)
|
||||||
|
@ -57,7 +64,7 @@ local t = {
|
||||||
table.insert(node.views, idx + 1, core.active_view)
|
table.insert(node.views, idx + 1, core.active_view)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["root:shrink"] = function()
|
["root:shrink"] = function()
|
||||||
local node = core.root_view:get_active_node()
|
local node = core.root_view:get_active_node()
|
||||||
local parent = node:get_parent_node(core.root_view.root_node)
|
local parent = node:get_parent_node(core.root_view.root_node)
|
||||||
|
@ -70,7 +77,7 @@ local t = {
|
||||||
local parent = node:get_parent_node(core.root_view.root_node)
|
local parent = node:get_parent_node(core.root_view.root_node)
|
||||||
local n = (parent.a == node) and 0.1 or -0.1
|
local n = (parent.a == node) and 0.1 or -0.1
|
||||||
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
|
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
|
||||||
end,
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,3 +123,14 @@ command.add(function()
|
||||||
local node = core.root_view:get_active_node()
|
local node = core.root_view:get_active_node()
|
||||||
return not node:get_locked_size()
|
return not node:get_locked_size()
|
||||||
end, t)
|
end, t)
|
||||||
|
|
||||||
|
command.add(nil, {
|
||||||
|
["root:scroll"] = function(delta)
|
||||||
|
local view = (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) or core.active_view
|
||||||
|
if view and view.scrollable then
|
||||||
|
view.scroll.to.y = view.scroll.to.y + delta * -config.mouse_wheel_scroll
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
|
@ -15,6 +15,8 @@ end
|
||||||
|
|
||||||
local CommandView = DocView:extend()
|
local CommandView = DocView:extend()
|
||||||
|
|
||||||
|
CommandView.context = "application"
|
||||||
|
|
||||||
local max_suggestions = 10
|
local max_suggestions = 10
|
||||||
|
|
||||||
local noop = function() end
|
local noop = function() end
|
||||||
|
@ -32,6 +34,7 @@ function CommandView:new()
|
||||||
self.suggestion_idx = 1
|
self.suggestion_idx = 1
|
||||||
self.suggestions = {}
|
self.suggestions = {}
|
||||||
self.suggestions_height = 0
|
self.suggestions_height = 0
|
||||||
|
self.show_suggestions = true
|
||||||
self.last_change_id = 0
|
self.last_change_id = 0
|
||||||
self.gutter_width = 0
|
self.gutter_width = 0
|
||||||
self.gutter_text_brightness = 0
|
self.gutter_text_brightness = 0
|
||||||
|
@ -43,6 +46,11 @@ function CommandView:new()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function CommandView:set_hidden_suggestions()
|
||||||
|
self.show_suggestions = false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function CommandView:get_name()
|
function CommandView:get_name()
|
||||||
return View.get_name(self)
|
return View.get_name(self)
|
||||||
end
|
end
|
||||||
|
@ -81,10 +89,29 @@ end
|
||||||
|
|
||||||
|
|
||||||
function CommandView:move_suggestion_idx(dir)
|
function CommandView:move_suggestion_idx(dir)
|
||||||
local n = self.suggestion_idx + dir
|
if self.show_suggestions then
|
||||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
local n = self.suggestion_idx + dir
|
||||||
self:complete()
|
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||||
self.last_change_id = self.doc:get_change_id()
|
self:complete()
|
||||||
|
self.last_change_id = self.doc:get_change_id()
|
||||||
|
else
|
||||||
|
local current_suggestion = #self.suggestions > 0 and self.suggestions[self.suggestion_idx].text
|
||||||
|
local text = self:get_text()
|
||||||
|
if text == current_suggestion then
|
||||||
|
local n = self.suggestion_idx + dir
|
||||||
|
if n == 0 and self.save_suggestion then
|
||||||
|
self:set_text(self.save_suggestion)
|
||||||
|
else
|
||||||
|
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||||
|
self:complete()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.save_suggestion = text
|
||||||
|
self:complete()
|
||||||
|
end
|
||||||
|
self.last_change_id = self.doc:get_change_id()
|
||||||
|
self.state.suggest(self:get_text())
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -132,6 +159,8 @@ function CommandView:exit(submitted, inexplicit)
|
||||||
self.doc:reset()
|
self.doc:reset()
|
||||||
self.suggestions = {}
|
self.suggestions = {}
|
||||||
if not submitted then cancel(not inexplicit) end
|
if not submitted then cancel(not inexplicit) end
|
||||||
|
self.show_suggestions = true
|
||||||
|
self.save_suggestion = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -185,7 +214,7 @@ function CommandView:update()
|
||||||
|
|
||||||
-- update suggestions box height
|
-- update suggestions box height
|
||||||
local lh = self:get_suggestion_line_height()
|
local lh = self:get_suggestion_line_height()
|
||||||
local dest = math.min(#self.suggestions, max_suggestions) * lh
|
local dest = self.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0
|
||||||
self:move_towards("suggestions_height", dest)
|
self:move_towards("suggestions_height", dest)
|
||||||
|
|
||||||
-- update suggestion cursor offset
|
-- update suggestion cursor offset
|
||||||
|
@ -254,7 +283,9 @@ end
|
||||||
|
|
||||||
function CommandView:draw()
|
function CommandView:draw()
|
||||||
CommandView.super.draw(self)
|
CommandView.super.draw(self)
|
||||||
core.root_view:defer_draw(draw_suggestions_box, self)
|
if self.show_suggestions then
|
||||||
|
core.root_view:defer_draw(draw_suggestions_box, self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,11 @@ function common.lerp(a, b, t)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function common.distance(x1, y1, x2, y2)
|
||||||
|
return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.color(str)
|
function common.color(str)
|
||||||
local r, g, b, a = str:match("#(%x%x)(%x%x)(%x%x)")
|
local r, g, b, a = str:match("#(%x%x)(%x%x)(%x%x)")
|
||||||
if r then
|
if r then
|
||||||
|
@ -230,6 +235,12 @@ function common.basename(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- can return nil if there is no directory part in the path
|
||||||
|
function common.dirname(path)
|
||||||
|
return path:match("(.+)[\\/][^\\/]+$")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.home_encode(text)
|
function common.home_encode(text)
|
||||||
if HOME and string.find(text, HOME, 1, true) == 1 then
|
if HOME and string.find(text, HOME, 1, true) == 1 then
|
||||||
local dir_pos = #HOME + 1
|
local dir_pos = #HOME + 1
|
||||||
|
@ -257,16 +268,6 @@ function common.home_expand(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.normalize_path(filename)
|
|
||||||
if PATHSEP == '\\' then
|
|
||||||
filename = filename:gsub('[/\\]', '\\')
|
|
||||||
local drive, rem = filename:match('^([a-zA-Z])(:.*)')
|
|
||||||
return drive and drive:upper() .. rem or filename
|
|
||||||
end
|
|
||||||
return filename
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function split_on_slash(s, sep_pattern)
|
local function split_on_slash(s, sep_pattern)
|
||||||
local t = {}
|
local t = {}
|
||||||
if s:match("^[/\\]") then
|
if s:match("^[/\\]") then
|
||||||
|
@ -279,8 +280,29 @@ local function split_on_slash(s, sep_pattern)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function common.normalize_path(filename)
|
||||||
|
if not filename then return end
|
||||||
|
if PATHSEP == '\\' then
|
||||||
|
filename = filename:gsub('[/\\]', '\\')
|
||||||
|
local drive, rem = filename:match('^([a-zA-Z])(:.*)')
|
||||||
|
filename = drive and drive:upper() .. rem or filename
|
||||||
|
end
|
||||||
|
local parts = split_on_slash(filename, PATHSEP)
|
||||||
|
local accu = {}
|
||||||
|
for _, part in ipairs(parts) do
|
||||||
|
if part == '..' and #accu > 0 and accu[#accu] ~= ".." then
|
||||||
|
table.remove(accu)
|
||||||
|
elseif part ~= '.' then
|
||||||
|
table.insert(accu, part)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local npath = table.concat(accu, PATHSEP)
|
||||||
|
return npath == "" and PATHSEP or npath
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.path_belongs_to(filename, path)
|
function common.path_belongs_to(filename, path)
|
||||||
return filename and string.find(filename, path .. PATHSEP, 1, true) == 1
|
return string.find(filename, path .. PATHSEP, 1, true) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ config.fps = 60
|
||||||
config.max_log_items = 80
|
config.max_log_items = 80
|
||||||
config.message_timeout = 5
|
config.message_timeout = 5
|
||||||
config.mouse_wheel_scroll = 50 * SCALE
|
config.mouse_wheel_scroll = 50 * SCALE
|
||||||
|
config.scroll_past_end = true
|
||||||
config.file_size_limit = 10
|
config.file_size_limit = 10
|
||||||
config.ignore_files = "^%."
|
config.ignore_files = "^%."
|
||||||
config.symbol_pattern = "[%a_][%w_]*"
|
config.symbol_pattern = "[%a_][%w_]*"
|
||||||
|
@ -12,6 +13,7 @@ config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||||
config.undo_merge_timeout = 0.3
|
config.undo_merge_timeout = 0.3
|
||||||
config.max_undos = 10000
|
config.max_undos = 10000
|
||||||
config.max_tabs = 10
|
config.max_tabs = 10
|
||||||
|
config.always_show_tabs = false
|
||||||
config.highlight_current_line = true
|
config.highlight_current_line = true
|
||||||
config.line_height = 1.2
|
config.line_height = 1.2
|
||||||
config.indent_size = 2
|
config.indent_size = 2
|
||||||
|
@ -22,9 +24,11 @@ config.max_project_files = 2000
|
||||||
config.transitions = true
|
config.transitions = true
|
||||||
config.animation_rate = 1.0
|
config.animation_rate = 1.0
|
||||||
config.blink_period = 0.8
|
config.blink_period = 0.8
|
||||||
|
config.disable_blink = false
|
||||||
config.draw_whitespace = false
|
config.draw_whitespace = false
|
||||||
config.borderless = false
|
config.borderless = false
|
||||||
config.tab_close_button = true
|
config.tab_close_button = true
|
||||||
|
config.max_clicks = 3
|
||||||
|
|
||||||
-- Disable plugin loading setting to false the config entry
|
-- Disable plugin loading setting to false the config entry
|
||||||
-- of the same name.
|
-- of the same name.
|
||||||
|
@ -32,5 +36,6 @@ config.plugins = {}
|
||||||
|
|
||||||
config.plugins.trimwhitespace = false
|
config.plugins.trimwhitespace = false
|
||||||
config.plugins.lineguide = false
|
config.plugins.lineguide = false
|
||||||
|
config.plugins.drawwhitespace = false
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
|
@ -49,7 +49,7 @@ function ContextMenu:register(predicate, items)
|
||||||
local width, height = 0, 0 --precalculate the size of context menu
|
local width, height = 0, 0 --precalculate the size of context menu
|
||||||
for i, item in ipairs(items) do
|
for i, item in ipairs(items) do
|
||||||
if item ~= DIVIDER then
|
if item ~= DIVIDER then
|
||||||
item.info = keymap.reverse_map[item.command]
|
item.info = keymap.get_binding(item.command)
|
||||||
end
|
end
|
||||||
local lw, lh = get_item_size(item)
|
local lw, lh = get_item_size(item)
|
||||||
width = math.max(width, lw)
|
width = math.max(width, lw)
|
||||||
|
|
|
@ -24,7 +24,7 @@ function Highlighter:new(doc)
|
||||||
for i = self.first_invalid_line, max do
|
for i = self.first_invalid_line, max do
|
||||||
local state = (i > 1) and self.lines[i - 1].state
|
local state = (i > 1) and self.lines[i - 1].state
|
||||||
local line = self.lines[i]
|
local line = self.lines[i]
|
||||||
if not (line and line.init_state == state) then
|
if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
|
||||||
self.lines[i] = self:tokenize_line(i, state)
|
self.lines[i] = self:tokenize_line(i, state)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -44,12 +44,25 @@ function Highlighter:reset()
|
||||||
self.max_wanted_line = 0
|
self.max_wanted_line = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Highlighter:invalidate(idx)
|
function Highlighter:invalidate(idx)
|
||||||
self.first_invalid_line = math.min(self.first_invalid_line, idx)
|
self.first_invalid_line = math.min(self.first_invalid_line, idx)
|
||||||
self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines)
|
self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Highlighter:insert_notify(line, n)
|
||||||
|
self:invalidate(line)
|
||||||
|
for i = 1, n do
|
||||||
|
table.insert(self.lines, line, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Highlighter:remove_notify(line, n)
|
||||||
|
self:invalidate(line)
|
||||||
|
for i = 1, n do
|
||||||
|
table.remove(self.lines, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Highlighter:tokenize_line(idx, state)
|
function Highlighter:tokenize_line(idx, state)
|
||||||
local res = {}
|
local res = {}
|
||||||
|
|
|
@ -17,10 +17,15 @@ local function split_lines(text)
|
||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
function Doc:new(filename)
|
|
||||||
|
function Doc:new(filename, abs_filename, new_file)
|
||||||
|
self.new_file = new_file
|
||||||
self:reset()
|
self:reset()
|
||||||
if filename then
|
if filename then
|
||||||
self:load(filename)
|
self:set_filename(filename, abs_filename)
|
||||||
|
if not new_file then
|
||||||
|
self:load(filename)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -47,16 +52,15 @@ function Doc:reset_syntax()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:set_filename(filename)
|
function Doc:set_filename(filename, abs_filename)
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.abs_filename = system.absolute_path(filename)
|
self.abs_filename = abs_filename
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:load(filename)
|
function Doc:load(filename)
|
||||||
local fp = assert( io.open(filename, "rb") )
|
local fp = assert( io.open(filename, "rb") )
|
||||||
self:reset()
|
self:reset()
|
||||||
self:set_filename(filename)
|
|
||||||
self.lines = {}
|
self.lines = {}
|
||||||
for line in fp:lines() do
|
for line in fp:lines() do
|
||||||
if line:byte(-1) == 13 then
|
if line:byte(-1) == 13 then
|
||||||
|
@ -73,17 +77,20 @@ function Doc:load(filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:save(filename)
|
function Doc:save(filename, abs_filename)
|
||||||
filename = filename or assert(self.filename, "no filename set to default to")
|
if not filename then
|
||||||
|
assert(self.filename, "no filename set to default to")
|
||||||
|
filename = self.filename
|
||||||
|
abs_filename = self.abs_filename
|
||||||
|
end
|
||||||
local fp = assert( io.open(filename, "wb") )
|
local fp = assert( io.open(filename, "wb") )
|
||||||
for _, line in ipairs(self.lines) do
|
for _, line in ipairs(self.lines) do
|
||||||
if self.crlf then line = line:gsub("\n", "\r\n") end
|
if self.crlf then line = line:gsub("\n", "\r\n") end
|
||||||
fp:write(line)
|
fp:write(line)
|
||||||
end
|
end
|
||||||
fp:close()
|
fp:close()
|
||||||
if filename then
|
self:set_filename(filename, abs_filename)
|
||||||
self:set_filename(filename)
|
self.new_file = false
|
||||||
end
|
|
||||||
self:reset_syntax()
|
self:reset_syntax()
|
||||||
self:clean()
|
self:clean()
|
||||||
end
|
end
|
||||||
|
@ -95,7 +102,11 @@ end
|
||||||
|
|
||||||
|
|
||||||
function Doc:is_dirty()
|
function Doc:is_dirty()
|
||||||
return self.clean_change_id ~= self:get_change_id()
|
if self.new_file then
|
||||||
|
return #self.lines > 1 or #self.lines[1] > 1
|
||||||
|
else
|
||||||
|
return self.clean_change_id ~= self:get_change_id()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,6 +128,19 @@ function Doc:get_selection(sort)
|
||||||
return line1, col1, line2, col2, sort
|
return line1, col1, line2, col2, sort
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Doc:get_selection_text(limit)
|
||||||
|
limit = limit or math.huge
|
||||||
|
local result = {}
|
||||||
|
for idx, line1, col1, line2, col2 in self:get_selections() do
|
||||||
|
if idx > limit then break end
|
||||||
|
if line1 ~= line2 or col1 ~= col2 then
|
||||||
|
local text = self:get_text(line1, col1, line2, col2)
|
||||||
|
if text ~= "" then result[#result + 1] = text end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(result, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
function Doc:has_selection()
|
function Doc:has_selection()
|
||||||
local line1, col1, line2, col2 = self:get_selection(false)
|
local line1, col1, line2, col2 = self:get_selection(false)
|
||||||
return line1 ~= line2 or col1 ~= col2
|
return line1 ~= line2 or col1 ~= col2
|
||||||
|
@ -166,19 +190,21 @@ function Doc:merge_cursors(idx)
|
||||||
if self.selections[i] == self.selections[j] and
|
if self.selections[i] == self.selections[j] and
|
||||||
self.selections[i+1] == self.selections[j+1] then
|
self.selections[i+1] == self.selections[j+1] then
|
||||||
common.splice(self.selections, i, 4)
|
common.splice(self.selections, i, 4)
|
||||||
|
common.splice(self.cursor_clipboard, i, 1)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if #self.selections <= 4 then self.cursor_clipboard = {} end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function selection_iterator(invariant, idx)
|
local function selection_iterator(invariant, idx)
|
||||||
local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1)
|
local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1)
|
||||||
if target > #invariant[1] or target <= 0 or (type(invariant[3]) == "number" and invariant[3] ~= idx - 1) then return end
|
if target > #invariant[1] or target <= 0 or (type(invariant[3]) == "number" and invariant[3] ~= idx - 1) then return end
|
||||||
if invariant[2] then
|
if invariant[2] then
|
||||||
return idx+(invariant[3] and -1 or 1), sort_positions(unpack(invariant[1], target, target+4))
|
return idx+(invariant[3] and -1 or 1), sort_positions(table.unpack(invariant[1], target, target+4))
|
||||||
else
|
else
|
||||||
return idx+(invariant[3] and -1 or 1), unpack(invariant[1], target, target+4)
|
return idx+(invariant[3] and -1 or 1), table.unpack(invariant[1], target, target+4)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -279,7 +305,7 @@ local function pop_undo(self, undo_stack, redo_stack, modified)
|
||||||
local line1, col1, line2, col2 = table.unpack(cmd)
|
local line1, col1, line2, col2 = table.unpack(cmd)
|
||||||
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
|
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
|
||||||
elseif cmd.type == "selection" then
|
elseif cmd.type == "selection" then
|
||||||
self.selections = { unpack(cmd) }
|
self.selections = { table.unpack(cmd) }
|
||||||
end
|
end
|
||||||
|
|
||||||
modified = modified or (cmd.type ~= "selection")
|
modified = modified or (cmd.type ~= "selection")
|
||||||
|
@ -300,6 +326,7 @@ end
|
||||||
function Doc:raw_insert(line, col, text, undo_stack, time)
|
function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||||
-- split text into lines and merge with line at insertion point
|
-- split text into lines and merge with line at insertion point
|
||||||
local lines = split_lines(text)
|
local lines = split_lines(text)
|
||||||
|
local len = #lines[#lines]
|
||||||
local before = self.lines[line]:sub(1, col - 1)
|
local before = self.lines[line]:sub(1, col - 1)
|
||||||
local after = self.lines[line]:sub(col)
|
local after = self.lines[line]:sub(col)
|
||||||
for i = 1, #lines - 1 do
|
for i = 1, #lines - 1 do
|
||||||
|
@ -310,14 +337,22 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||||
|
|
||||||
-- splice lines into line array
|
-- splice lines into line array
|
||||||
common.splice(self.lines, line, 1, lines)
|
common.splice(self.lines, line, 1, lines)
|
||||||
|
|
||||||
|
-- keep cursors where they should be
|
||||||
|
for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
|
||||||
|
if cline1 < line then break end
|
||||||
|
local line_addition = (line < cline1 or col < ccol1) and #lines - 1 or 0
|
||||||
|
local column_addition = line == cline1 and ccol1 > col and len or 0
|
||||||
|
self:set_selections(idx, cline1 + line_addition, ccol1 + column_addition, cline2 + line_addition, ccol2 + column_addition)
|
||||||
|
end
|
||||||
|
|
||||||
-- push undo
|
-- push undo
|
||||||
local line2, col2 = self:position_offset(line, col, #text)
|
local line2, col2 = self:position_offset(line, col, #text)
|
||||||
push_undo(undo_stack, time, "selection", unpack(self.selections))
|
push_undo(undo_stack, time, "selection", table.unpack(self.selections))
|
||||||
push_undo(undo_stack, time, "remove", line, col, line2, col2)
|
push_undo(undo_stack, time, "remove", line, col, line2, col2)
|
||||||
|
|
||||||
-- update highlighter and assure selection is in bounds
|
-- update highlighter and assure selection is in bounds
|
||||||
self.highlighter:invalidate(line)
|
self.highlighter:insert_notify(line, #lines - 1)
|
||||||
self:sanitize_selection()
|
self:sanitize_selection()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -325,7 +360,7 @@ end
|
||||||
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||||
-- push undo
|
-- push undo
|
||||||
local text = self:get_text(line1, col1, line2, col2)
|
local text = self:get_text(line1, col1, line2, col2)
|
||||||
push_undo(undo_stack, time, "selection", unpack(self.selections))
|
push_undo(undo_stack, time, "selection", table.unpack(self.selections))
|
||||||
push_undo(undo_stack, time, "insert", line1, col1, text)
|
push_undo(undo_stack, time, "insert", line1, col1, text)
|
||||||
|
|
||||||
-- get line content before/after removed text
|
-- get line content before/after removed text
|
||||||
|
@ -334,9 +369,17 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||||
|
|
||||||
-- splice line into line array
|
-- splice line into line array
|
||||||
common.splice(self.lines, line1, line2 - line1 + 1, { before .. after })
|
common.splice(self.lines, line1, line2 - line1 + 1, { before .. after })
|
||||||
|
|
||||||
|
-- move all cursors back if they share a line with the removed text
|
||||||
|
for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
|
||||||
|
if cline1 < line2 then break end
|
||||||
|
local line_removal = line2 - line1
|
||||||
|
local column_removal = line2 == cline2 and col2 < ccol1 and (line2 == line1 and col2 - col1 or col2) or 0
|
||||||
|
self:set_selections(idx, cline1 - line_removal, ccol1 - column_removal, cline2 - line_removal, ccol2 - column_removal)
|
||||||
|
end
|
||||||
|
|
||||||
-- update highlighter and assure selection is in bounds
|
-- update highlighter and assure selection is in bounds
|
||||||
self.highlighter:invalidate(line1)
|
self.highlighter:remove_notify(line1, line2 - line1)
|
||||||
self:sanitize_selection()
|
self:sanitize_selection()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -370,7 +413,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
function Doc:text_input(text, idx)
|
function Doc:text_input(text, idx)
|
||||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
|
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
if line1 ~= line2 or col1 ~= col2 then
|
||||||
self:delete_to_cursor(sidx)
|
self:delete_to_cursor(sidx)
|
||||||
end
|
end
|
||||||
|
@ -379,12 +422,7 @@ function Doc:text_input(text, idx)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
|
||||||
function Doc:replace(fn)
|
|
||||||
local line1, col1, line2, col2 = self:get_selection(true)
|
|
||||||
if line1 == line2 and col1 == col2 then
|
|
||||||
line1, col1, line2, col2 = 1, 1, #self.lines, #self.lines[#self.lines]
|
|
||||||
end
|
|
||||||
local old_text = self:get_text(line1, col1, line2, col2)
|
local old_text = self:get_text(line1, col1, line2, col2)
|
||||||
local new_text, n = fn(old_text)
|
local new_text, n = fn(old_text)
|
||||||
if old_text ~= new_text then
|
if old_text ~= new_text then
|
||||||
|
@ -392,12 +430,27 @@ function Doc:replace(fn)
|
||||||
self:remove(line1, col1, line2, col2)
|
self:remove(line1, col1, line2, col2)
|
||||||
if line1 == line2 and col1 == col2 then
|
if line1 == line2 and col1 == col2 then
|
||||||
line2, col2 = self:position_offset(line1, col1, #new_text)
|
line2, col2 = self:position_offset(line1, col1, #new_text)
|
||||||
self:set_selection(line1, col1, line2, col2)
|
self:set_selections(idx, line1, col1, line2, col2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return n
|
return n
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Doc:replace(fn)
|
||||||
|
local has_selection, n = false, 0
|
||||||
|
for idx, line1, col1, line2, col2 in self:get_selections(true) do
|
||||||
|
if line1 ~= line2 or col1 ~= col2 then
|
||||||
|
n = n + self:replace_cursor(idx, line1, col1, line2, col2, fn)
|
||||||
|
has_selection = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not has_selection then
|
||||||
|
self:set_selection(table.unpack(self.selections))
|
||||||
|
n = n + self:replace_cursor(1, 1, 1, #self.lines, #self.lines[#self.lines], fn)
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:delete_to_cursor(idx, ...)
|
function Doc:delete_to_cursor(idx, ...)
|
||||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
|
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
|
||||||
|
|
|
@ -117,6 +117,10 @@ function translate.start_of_line(doc, line, col)
|
||||||
return line, 1
|
return line, 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function translate.start_of_indentation(doc, line, col)
|
||||||
|
local s, e = doc.lines[line]:find("^%s*")
|
||||||
|
return line, col > e + 1 and e + 1 or 1
|
||||||
|
end
|
||||||
|
|
||||||
function translate.end_of_line(doc, line, col)
|
function translate.end_of_line(doc, line, col)
|
||||||
return line, math.huge
|
return line, math.huge
|
||||||
|
|
|
@ -9,6 +9,7 @@ local View = require "core.view"
|
||||||
|
|
||||||
local DocView = View:extend()
|
local DocView = View:extend()
|
||||||
|
|
||||||
|
DocView.context = "session"
|
||||||
|
|
||||||
local function move_to_line_offset(dv, line, col, offset)
|
local function move_to_line_offset(dv, line, col, offset)
|
||||||
local xo = dv.last_x_offset
|
local xo = dv.last_x_offset
|
||||||
|
@ -97,6 +98,9 @@ end
|
||||||
|
|
||||||
|
|
||||||
function DocView:get_scrollable_size()
|
function DocView:get_scrollable_size()
|
||||||
|
if not config.scroll_past_end then
|
||||||
|
return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2
|
||||||
|
end
|
||||||
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
|
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -149,14 +153,14 @@ function DocView:get_col_x_offset(line, col)
|
||||||
local font = style.syntax_fonts[type] or default_font
|
local font = style.syntax_fonts[type] or default_font
|
||||||
for char in common.utf8_chars(text) do
|
for char in common.utf8_chars(text) do
|
||||||
if column == col then
|
if column == col then
|
||||||
return xoffset / font:subpixel_scale()
|
return xoffset
|
||||||
end
|
end
|
||||||
xoffset = xoffset + font:get_width_subpixel(char)
|
xoffset = xoffset + font:get_width(char)
|
||||||
column = column + #char
|
column = column + #char
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return xoffset / default_font:subpixel_scale()
|
return xoffset
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,14 +169,12 @@ function DocView:get_x_offset_col(line, x)
|
||||||
|
|
||||||
local xoffset, last_i, i = 0, 1, 1
|
local xoffset, last_i, i = 0, 1, 1
|
||||||
local default_font = self:get_font()
|
local default_font = self:get_font()
|
||||||
local subpixel_scale = default_font:subpixel_scale()
|
|
||||||
local x_subpixel = subpixel_scale * x + subpixel_scale / 2
|
|
||||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||||
local font = style.syntax_fonts[type] or default_font
|
local font = style.syntax_fonts[type] or default_font
|
||||||
for char in common.utf8_chars(text) do
|
for char in common.utf8_chars(text) do
|
||||||
local w = font:get_width_subpixel(char)
|
local w = font:get_width(char)
|
||||||
if xoffset >= subpixel_scale * x then
|
if xoffset >= x then
|
||||||
return (xoffset - x_subpixel > w / 2) and last_i or i
|
return (xoffset - x > w / 2) and last_i or i
|
||||||
end
|
end
|
||||||
xoffset = xoffset + w
|
xoffset = xoffset + w
|
||||||
last_i = i
|
last_i = i
|
||||||
|
@ -222,52 +224,6 @@ function DocView:scroll_to_make_visible(line, col)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function mouse_selection(doc, clicks, line1, col1, line2, col2)
|
|
||||||
local swap = line2 < line1 or line2 == line1 and col2 <= col1
|
|
||||||
if swap then
|
|
||||||
line1, col1, line2, col2 = line2, col2, line1, col1
|
|
||||||
end
|
|
||||||
if clicks % 4 == 2 then
|
|
||||||
line1, col1 = translate.start_of_word(doc, line1, col1)
|
|
||||||
line2, col2 = translate.end_of_word(doc, line2, col2)
|
|
||||||
elseif clicks % 4 == 3 then
|
|
||||||
if line2 == #doc.lines and doc.lines[#doc.lines] ~= "\n" then
|
|
||||||
doc:insert(math.huge, math.huge, "\n")
|
|
||||||
end
|
|
||||||
line1, col1, line2, col2 = line1, 1, line2 + 1, 1
|
|
||||||
end
|
|
||||||
if swap then
|
|
||||||
return line2, col2, line1, col1
|
|
||||||
end
|
|
||||||
return line1, col1, line2, col2
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function DocView:on_mouse_pressed(button, x, y, clicks)
|
|
||||||
local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks)
|
|
||||||
if caught then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if keymap.modkeys["shift"] then
|
|
||||||
if clicks % 2 == 1 then
|
|
||||||
local line1, col1 = select(3, self.doc:get_selection())
|
|
||||||
local line2, col2 = self:resolve_screen_position(x, y)
|
|
||||||
self.doc:set_selection(line2, col2, line1, col1)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local line, col = self:resolve_screen_position(x, y)
|
|
||||||
if keymap.modkeys["ctrl"] then
|
|
||||||
self.doc:add_selection(mouse_selection(self.doc, clicks, line, col, line, col))
|
|
||||||
else
|
|
||||||
self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col))
|
|
||||||
end
|
|
||||||
self.mouse_selecting = { line, col, clicks = clicks }
|
|
||||||
end
|
|
||||||
core.blink_reset()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function DocView:on_mouse_moved(x, y, ...)
|
function DocView:on_mouse_moved(x, y, ...)
|
||||||
DocView.super.on_mouse_moved(self, x, y, ...)
|
DocView.super.on_mouse_moved(self, x, y, ...)
|
||||||
|
|
||||||
|
@ -280,7 +236,6 @@ function DocView:on_mouse_moved(x, y, ...)
|
||||||
if self.mouse_selecting then
|
if self.mouse_selecting then
|
||||||
local l1, c1 = self:resolve_screen_position(x, y)
|
local l1, c1 = self:resolve_screen_position(x, y)
|
||||||
local l2, c2 = table.unpack(self.mouse_selecting)
|
local l2, c2 = table.unpack(self.mouse_selecting)
|
||||||
local clicks = self.mouse_selecting.clicks
|
|
||||||
if keymap.modkeys["ctrl"] then
|
if keymap.modkeys["ctrl"] then
|
||||||
if l1 > l2 then l1, l2 = l2, l1 end
|
if l1 > l2 then l1, l2 = l2, l1 end
|
||||||
self.doc.selections = { }
|
self.doc.selections = { }
|
||||||
|
@ -288,7 +243,7 @@ function DocView:on_mouse_moved(x, y, ...)
|
||||||
self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c2, #self.doc.lines[i]))
|
self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c2, #self.doc.lines[i]))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2))
|
self.doc:set_selection(l1, c1, l2, c2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -338,16 +293,11 @@ end
|
||||||
|
|
||||||
function DocView:draw_line_text(idx, x, y)
|
function DocView:draw_line_text(idx, x, y)
|
||||||
local default_font = self:get_font()
|
local default_font = self:get_font()
|
||||||
local subpixel_scale = default_font:subpixel_scale()
|
local tx, ty = x, y + self:get_line_text_y_offset()
|
||||||
local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset()
|
|
||||||
for _, type, text in self.doc.highlighter:each_token(idx) do
|
for _, type, text in self.doc.highlighter:each_token(idx) do
|
||||||
local color = style.syntax[type]
|
local color = style.syntax[type]
|
||||||
local font = style.syntax_fonts[type] or default_font
|
local font = style.syntax_fonts[type] or default_font
|
||||||
if config.draw_whitespace then
|
tx = renderer.draw_text(font, text, tx, ty, color)
|
||||||
tx = renderer.draw_text_subpixel(font, text, tx, ty, color, core.replacements, style.syntax.comment)
|
|
||||||
else
|
|
||||||
tx = renderer.draw_text_subpixel(font, text, tx, ty, color)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -357,6 +307,18 @@ function DocView:draw_caret(x, y)
|
||||||
end
|
end
|
||||||
|
|
||||||
function DocView:draw_line_body(idx, x, y)
|
function DocView:draw_line_body(idx, x, y)
|
||||||
|
-- draw highlight if any selection ends on this line
|
||||||
|
local draw_highlight = false
|
||||||
|
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
|
||||||
|
if line1 == idx then
|
||||||
|
draw_highlight = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if draw_highlight and config.highlight_current_line and core.active_view == self then
|
||||||
|
self:draw_line_highlight(x + self.scroll.x, y)
|
||||||
|
end
|
||||||
|
|
||||||
-- draw selection if it overlaps this line
|
-- draw selection if it overlaps this line
|
||||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
||||||
if idx >= line1 and idx <= line2 then
|
if idx >= line1 and idx <= line2 then
|
||||||
|
@ -366,14 +328,9 @@ function DocView:draw_line_body(idx, x, y)
|
||||||
local x1 = x + self:get_col_x_offset(idx, col1)
|
local x1 = x + self:get_col_x_offset(idx, col1)
|
||||||
local x2 = x + self:get_col_x_offset(idx, col2)
|
local x2 = x + self:get_col_x_offset(idx, col2)
|
||||||
local lh = self:get_line_height()
|
local lh = self:get_line_height()
|
||||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
if x1 ~= x2 then
|
||||||
end
|
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||||
end
|
end
|
||||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
|
||||||
-- draw line highlight if caret is on this line
|
|
||||||
if config.highlight_current_line and (line1 == line2 and col1 == col2)
|
|
||||||
and line1 == idx and core.active_view == self then
|
|
||||||
self:draw_line_highlight(x + self.scroll.x, y)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -403,10 +360,12 @@ function DocView:draw_overlay()
|
||||||
local T = config.blink_period
|
local T = config.blink_period
|
||||||
for _, line, col in self.doc:get_selections() do
|
for _, line, col in self.doc:get_selections() do
|
||||||
if line >= minline and line <= maxline
|
if line >= minline and line <= maxline
|
||||||
and (core.blink_timer - core.blink_start) % T < T / 2
|
|
||||||
and system.window_has_focus() then
|
and system.window_has_focus() then
|
||||||
local x, y = self:get_line_screen_position(line)
|
if config.disable_blink
|
||||||
self:draw_caret(x + self:get_col_x_offset(line, col), y)
|
or (core.blink_timer - core.blink_start) % T < T / 2 then
|
||||||
|
local x, y = self:get_line_screen_position(line)
|
||||||
|
self:draw_caret(x + self:get_col_x_offset(line, col), y)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,10 +17,7 @@ local core = {}
|
||||||
|
|
||||||
local function load_session()
|
local function load_session()
|
||||||
local ok, t = pcall(dofile, USERDIR .. "/session.lua")
|
local ok, t = pcall(dofile, USERDIR .. "/session.lua")
|
||||||
if ok then
|
return ok and t or {}
|
||||||
return t.recents, t.window, t.window_mode
|
|
||||||
end
|
|
||||||
return {}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +27,8 @@ local function save_session()
|
||||||
fp:write("return {recents=", common.serialize(core.recent_projects),
|
fp:write("return {recents=", common.serialize(core.recent_projects),
|
||||||
", window=", common.serialize(table.pack(system.get_window_size())),
|
", window=", common.serialize(table.pack(system.get_window_size())),
|
||||||
", window_mode=", common.serialize(system.get_window_mode()),
|
", window_mode=", common.serialize(system.get_window_mode()),
|
||||||
|
", previous_find=", common.serialize(core.previous_find),
|
||||||
|
", previous_replace=", common.serialize(core.previous_replace),
|
||||||
"}\n")
|
"}\n")
|
||||||
fp:close()
|
fp:close()
|
||||||
end
|
end
|
||||||
|
@ -80,6 +79,9 @@ function core.open_folder_project(dir_path_abs)
|
||||||
if core.set_project_dir(dir_path_abs, core.on_quit_project) then
|
if core.set_project_dir(dir_path_abs, core.on_quit_project) then
|
||||||
core.root_view:close_all_docviews()
|
core.root_view:close_all_docviews()
|
||||||
update_recents_project("add", dir_path_abs)
|
update_recents_project("add", dir_path_abs)
|
||||||
|
if not core.load_project_module() then
|
||||||
|
command.perform("core:open-log")
|
||||||
|
end
|
||||||
core.on_enter_project(dir_path_abs)
|
core.on_enter_project(dir_path_abs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -320,8 +322,8 @@ local style = require "core.style"
|
||||||
------------------------------- Fonts ----------------------------------------
|
------------------------------- Fonts ----------------------------------------
|
||||||
|
|
||||||
-- customize fonts:
|
-- customize fonts:
|
||||||
-- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
|
-- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 14 * SCALE)
|
||||||
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
|
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 14 * SCALE)
|
||||||
--
|
--
|
||||||
-- font names used by lite:
|
-- font names used by lite:
|
||||||
-- style.font : user interface
|
-- style.font : user interface
|
||||||
|
@ -342,11 +344,11 @@ local style = require "core.style"
|
||||||
|
|
||||||
-- enable or disable plugin loading setting config entries:
|
-- enable or disable plugin loading setting config entries:
|
||||||
|
|
||||||
-- enable trimwhitespace, otherwise it is disable by default:
|
-- enable plugins.trimwhitespace, otherwise it is disable by default:
|
||||||
-- config.trimwhitespace = true
|
-- config.plugins.trimwhitespace = true
|
||||||
--
|
--
|
||||||
-- disable detectindent, otherwise it is enabled by default
|
-- disable detectindent, otherwise it is enabled by default
|
||||||
-- config.detectindent = false
|
-- config.plugins.detectindent = false
|
||||||
]])
|
]])
|
||||||
init_file:close()
|
init_file:close()
|
||||||
end
|
end
|
||||||
|
@ -394,15 +396,6 @@ function core.remove_project_directory(path)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function whitespace_replacements()
|
|
||||||
local r = renderer.replacements.new()
|
|
||||||
r:add(" ", "·")
|
|
||||||
r:add("\t", "»")
|
|
||||||
return r
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function reload_on_user_module_save()
|
local function reload_on_user_module_save()
|
||||||
-- auto-realod style when user's module is saved by overriding Doc:Save()
|
-- auto-realod style when user's module is saved by overriding Doc:Save()
|
||||||
local doc_save = Doc.save
|
local doc_save = Doc.save
|
||||||
|
@ -435,13 +428,15 @@ function core.init()
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do
|
||||||
local recent_projects, window_position, window_mode = load_session()
|
local session = load_session()
|
||||||
if window_mode == "normal" then
|
if session.window_mode == "normal" then
|
||||||
system.set_window_size(table.unpack(window_position))
|
system.set_window_size(table.unpack(session.window))
|
||||||
elseif window_mode == "maximized" then
|
elseif session.window_mode == "maximized" then
|
||||||
system.set_window_mode("maximized")
|
system.set_window_mode("maximized")
|
||||||
end
|
end
|
||||||
core.recent_projects = recent_projects
|
core.recent_projects = session.recents or {}
|
||||||
|
core.previous_find = session.previous_find or {}
|
||||||
|
core.previous_replace = session.previous_replace or {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local project_dir = core.recent_projects[1] or "."
|
local project_dir = core.recent_projects[1] or "."
|
||||||
|
@ -461,7 +456,10 @@ function core.init()
|
||||||
project_dir = arg_filename
|
project_dir = arg_filename
|
||||||
project_dir_explicit = true
|
project_dir_explicit = true
|
||||||
else
|
else
|
||||||
delayed_error = string.format("error: invalid file or directory %q", ARGS[i])
|
-- on macOS we can get an argument like "-psn_0_52353" that we just ignore.
|
||||||
|
if not ARGS[i]:match("^-psn") then
|
||||||
|
delayed_error = string.format("error: invalid file or directory %q", ARGS[i])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -494,7 +492,7 @@ function core.init()
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
core.visited_files = {}
|
core.visited_files = {}
|
||||||
core.restart_request = false
|
core.restart_request = false
|
||||||
core.replacements = whitespace_replacements()
|
core.quit_request = false
|
||||||
|
|
||||||
core.root_view = RootView()
|
core.root_view = RootView()
|
||||||
core.command_view = CommandView()
|
core.command_view = CommandView()
|
||||||
|
@ -564,10 +562,10 @@ function core.init()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function core.confirm_close_all(close_fn, ...)
|
function core.confirm_close_docs(docs, close_fn, ...)
|
||||||
local dirty_count = 0
|
local dirty_count = 0
|
||||||
local dirty_name
|
local dirty_name
|
||||||
for _, doc in ipairs(core.docs) do
|
for _, doc in ipairs(docs or core.docs) do
|
||||||
if doc:is_dirty() then
|
if doc:is_dirty() then
|
||||||
dirty_count = dirty_count + 1
|
dirty_count = dirty_count + 1
|
||||||
dirty_name = doc:get_name()
|
dirty_name = doc:get_name()
|
||||||
|
@ -620,24 +618,6 @@ do
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- DEPRECATED function
|
|
||||||
core.doc_save_hooks = {}
|
|
||||||
function core.add_save_hook(fn)
|
|
||||||
core.error("The function core.add_save_hook is deprecated." ..
|
|
||||||
" Modules should now directly override the Doc:save function.")
|
|
||||||
core.doc_save_hooks[#core.doc_save_hooks + 1] = fn
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- DEPRECATED function
|
|
||||||
function core.on_doc_save(filename)
|
|
||||||
-- for backward compatibility in modules. Hooks are deprecated, the function Doc:save
|
|
||||||
-- should be directly overidded.
|
|
||||||
for _, hook in ipairs(core.doc_save_hooks) do
|
|
||||||
hook(filename)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function quit_with_function(quit_fn, force)
|
local function quit_with_function(quit_fn, force)
|
||||||
if force then
|
if force then
|
||||||
delete_temp_files()
|
delete_temp_files()
|
||||||
|
@ -645,12 +625,12 @@ local function quit_with_function(quit_fn, force)
|
||||||
save_session()
|
save_session()
|
||||||
quit_fn()
|
quit_fn()
|
||||||
else
|
else
|
||||||
core.confirm_close_all(quit_with_function, quit_fn, true)
|
core.confirm_close_docs(core.docs, quit_with_function, quit_fn, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function core.quit(force)
|
function core.quit(force)
|
||||||
quit_with_function(os.exit, force)
|
quit_with_function(function() core.quit_request = true end, force)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -679,8 +659,8 @@ local function check_plugin_version(filename)
|
||||||
-- Future versions will look only at the mod-version tag.
|
-- Future versions will look only at the mod-version tag.
|
||||||
local version = line:match('%-%-%s*lite%-xl%s*(%d+%.%d+)$')
|
local version = line:match('%-%-%s*lite%-xl%s*(%d+%.%d+)$')
|
||||||
if version then
|
if version then
|
||||||
-- we consider the version tag 1.16 equivalent to mod-version:1
|
-- we consider the version tag 2.0 equivalent to mod-version:2
|
||||||
version_match = (version == '1.16' and MOD_VERSION == "1")
|
version_match = (version == '2.0' and MOD_VERSION == "2")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -695,24 +675,30 @@ function core.load_plugins()
|
||||||
userdir = {dir = USERDIR, plugins = {}},
|
userdir = {dir = USERDIR, plugins = {}},
|
||||||
datadir = {dir = DATADIR, plugins = {}},
|
datadir = {dir = DATADIR, plugins = {}},
|
||||||
}
|
}
|
||||||
for _, root_dir in ipairs {USERDIR, DATADIR} do
|
local files, ordered = {}, {}
|
||||||
|
for _, root_dir in ipairs {DATADIR, USERDIR} do
|
||||||
local plugin_dir = root_dir .. "/plugins"
|
local plugin_dir = root_dir .. "/plugins"
|
||||||
local files = system.list_dir(plugin_dir)
|
for _, filename in ipairs(system.list_dir(plugin_dir) or {}) do
|
||||||
for _, filename in ipairs(files or {}) do
|
if not files[filename] then table.insert(ordered, filename) end
|
||||||
local basename = filename:match("(.-)%.lua$") or filename
|
files[filename] = plugin_dir -- user plugins will always replace system plugins
|
||||||
local is_lua_file, version_match = check_plugin_version(plugin_dir .. '/' .. filename)
|
end
|
||||||
if is_lua_file then
|
end
|
||||||
if not version_match then
|
table.sort(ordered)
|
||||||
core.log_quiet("Version mismatch for plugin %q from %s", basename, plugin_dir)
|
|
||||||
local ls = refused_list[root_dir == USERDIR and 'userdir' or 'datadir'].plugins
|
for _, filename in ipairs(ordered) do
|
||||||
ls[#ls + 1] = filename
|
local plugin_dir, basename = files[filename], filename:match("(.-)%.lua$") or filename
|
||||||
elseif config.plugins[basename] ~= false then
|
local is_lua_file, version_match = check_plugin_version(plugin_dir .. '/' .. filename)
|
||||||
local modname = "plugins." .. basename
|
if is_lua_file then
|
||||||
local ok = core.try(require, modname)
|
if not version_match then
|
||||||
if ok then core.log_quiet("Loaded plugin %q from %s", basename, plugin_dir) end
|
core.log_quiet("Version mismatch for plugin %q from %s", basename, plugin_dir)
|
||||||
if not ok then
|
local list = refused_list[plugin_dir:find(USERDIR, 1, true) == 1 and 'userdir' or 'datadir'].plugins
|
||||||
no_errors = false
|
table.insert(list, filename)
|
||||||
end
|
end
|
||||||
|
if version_match and config.plugins[basename] ~= false then
|
||||||
|
local ok = core.try(require, "plugins." .. basename)
|
||||||
|
if ok then core.log_quiet("Loaded plugin %q from %s", basename, plugin_dir) end
|
||||||
|
if not ok then
|
||||||
|
no_errors = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -759,8 +745,12 @@ end
|
||||||
|
|
||||||
function core.set_active_view(view)
|
function core.set_active_view(view)
|
||||||
assert(view, "Tried to set active view to nil")
|
assert(view, "Tried to set active view to nil")
|
||||||
if core.active_view and core.active_view.force_focus then return end
|
|
||||||
if view ~= core.active_view then
|
if view ~= core.active_view then
|
||||||
|
if core.active_view and core.active_view.force_focus then
|
||||||
|
core.next_active_view = view
|
||||||
|
return
|
||||||
|
end
|
||||||
|
core.next_active_view = nil
|
||||||
if view.doc and view.doc.filename then
|
if view.doc and view.doc.filename then
|
||||||
core.set_visited(view.doc.filename)
|
core.set_visited(view.doc.filename)
|
||||||
end
|
end
|
||||||
|
@ -810,10 +800,30 @@ function core.normalize_to_project_dir(filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- The function below works like system.absolute_path except it
|
||||||
|
-- doesn't fail if the file does not exist. We consider that the
|
||||||
|
-- current dir is core.project_dir so relative filename are considered
|
||||||
|
-- to be in core.project_dir.
|
||||||
|
-- Please note that .. or . in the filename are not taken into account.
|
||||||
|
-- This function should get only filenames normalized using
|
||||||
|
-- common.normalize_path function.
|
||||||
|
function core.project_absolute_path(filename)
|
||||||
|
if filename:match('^%a:\\') or filename:find('/', 1, true) == 1 then
|
||||||
|
return filename
|
||||||
|
else
|
||||||
|
return core.project_dir .. PATHSEP .. filename
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function core.open_doc(filename)
|
function core.open_doc(filename)
|
||||||
|
local new_file = not filename or not system.get_file_info(filename)
|
||||||
|
local abs_filename
|
||||||
if filename then
|
if filename then
|
||||||
|
-- normalize filename and set absolute filename then
|
||||||
-- try to find existing doc for filename
|
-- try to find existing doc for filename
|
||||||
local abs_filename = system.absolute_path(filename)
|
filename = core.normalize_to_project_dir(filename)
|
||||||
|
abs_filename = core.project_absolute_path(filename)
|
||||||
for _, doc in ipairs(core.docs) do
|
for _, doc in ipairs(core.docs) do
|
||||||
if doc.abs_filename and abs_filename == doc.abs_filename then
|
if doc.abs_filename and abs_filename == doc.abs_filename then
|
||||||
return doc
|
return doc
|
||||||
|
@ -821,8 +831,7 @@ function core.open_doc(filename)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- no existing doc for filename; create new
|
-- no existing doc for filename; create new
|
||||||
filename = filename and core.normalize_to_project_dir(filename)
|
local doc = Doc(filename, abs_filename, new_file)
|
||||||
local doc = Doc(filename)
|
|
||||||
table.insert(core.docs, doc)
|
table.insert(core.docs, doc)
|
||||||
core.log_quiet(filename and "Opened doc \"%s\"" or "Opened new doc", filename)
|
core.log_quiet(filename and "Opened doc \"%s\"" or "Opened new doc", filename)
|
||||||
return doc
|
return doc
|
||||||
|
@ -871,6 +880,23 @@ function core.error(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function core.get_log(i)
|
||||||
|
if i == nil then
|
||||||
|
local r = {}
|
||||||
|
for _, item in ipairs(core.log_items) do
|
||||||
|
table.insert(r, core.get_log(item))
|
||||||
|
end
|
||||||
|
return table.concat(r, "\n")
|
||||||
|
end
|
||||||
|
local item = type(i) == "number" and core.log_items[i] or i
|
||||||
|
local text = string.format("[%s] %s at %s", os.date(nil, item.time), item.text, item.at)
|
||||||
|
if item.info then
|
||||||
|
text = string.format("%s\n%s\n", text, item.info)
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function core.try(fn, ...)
|
function core.try(fn, ...)
|
||||||
local err
|
local err
|
||||||
local ok, res = xpcall(fn, function(msg)
|
local ok, res = xpcall(fn, function(msg)
|
||||||
|
@ -896,11 +922,15 @@ function core.on_event(type, ...)
|
||||||
elseif type == "mousemoved" then
|
elseif type == "mousemoved" then
|
||||||
core.root_view:on_mouse_moved(...)
|
core.root_view:on_mouse_moved(...)
|
||||||
elseif type == "mousepressed" then
|
elseif type == "mousepressed" then
|
||||||
core.root_view:on_mouse_pressed(...)
|
if not core.root_view:on_mouse_pressed(...) then
|
||||||
|
did_keymap = keymap.on_mouse_pressed(...)
|
||||||
|
end
|
||||||
elseif type == "mousereleased" then
|
elseif type == "mousereleased" then
|
||||||
core.root_view:on_mouse_released(...)
|
core.root_view:on_mouse_released(...)
|
||||||
elseif type == "mousewheel" then
|
elseif type == "mousewheel" then
|
||||||
core.root_view:on_mouse_wheel(...)
|
if not core.root_view:on_mouse_wheel(...) then
|
||||||
|
did_keymap = keymap.on_mouse_wheel(...)
|
||||||
|
end
|
||||||
elseif type == "resized" then
|
elseif type == "resized" then
|
||||||
core.window_mode = system.get_window_mode()
|
core.window_mode = system.get_window_mode()
|
||||||
elseif type == "minimized" or type == "maximized" or type == "restored" then
|
elseif type == "minimized" or type == "maximized" or type == "restored" then
|
||||||
|
@ -1027,7 +1057,7 @@ function core.run()
|
||||||
core.frame_start = system.get_time()
|
core.frame_start = system.get_time()
|
||||||
local did_redraw = core.step()
|
local did_redraw = core.step()
|
||||||
local need_more_work = run_threads()
|
local need_more_work = run_threads()
|
||||||
if core.restart_request then break end
|
if core.restart_request or core.quit_request then break end
|
||||||
if not did_redraw and not need_more_work then
|
if not did_redraw and not need_more_work then
|
||||||
idle_iterations = idle_iterations + 1
|
idle_iterations = idle_iterations + 1
|
||||||
-- do not wait of events at idle_iterations = 1 to give a chance at core.step to run
|
-- do not wait of events at idle_iterations = 1 to give a chance at core.step to run
|
||||||
|
|
|
@ -32,6 +32,8 @@ local function keymap_macos(keymap)
|
||||||
["cmd+7"] = "root:switch-to-tab-7",
|
["cmd+7"] = "root:switch-to-tab-7",
|
||||||
["cmd+8"] = "root:switch-to-tab-8",
|
["cmd+8"] = "root:switch-to-tab-8",
|
||||||
["cmd+9"] = "root:switch-to-tab-9",
|
["cmd+9"] = "root:switch-to-tab-9",
|
||||||
|
["wheel"] = "root:scroll",
|
||||||
|
|
||||||
["cmd+f"] = "find-replace:find",
|
["cmd+f"] = "find-replace:find",
|
||||||
["cmd+r"] = "find-replace:replace",
|
["cmd+r"] = "find-replace:replace",
|
||||||
["f3"] = "find-replace:repeat-find",
|
["f3"] = "find-replace:repeat-find",
|
||||||
|
@ -52,23 +54,27 @@ local function keymap_macos(keymap)
|
||||||
["shift+tab"] = "doc:unindent",
|
["shift+tab"] = "doc:unindent",
|
||||||
["backspace"] = "doc:backspace",
|
["backspace"] = "doc:backspace",
|
||||||
["shift+backspace"] = "doc:backspace",
|
["shift+backspace"] = "doc:backspace",
|
||||||
["cmd+backspace"] = "doc:delete-to-previous-word-start",
|
["option+backspace"] = "doc:delete-to-previous-word-start",
|
||||||
["cmd+shift+backspace"] = "doc:delete-to-previous-word-start",
|
["cmd+shift+backspace"] = "doc:delete-to-previous-word-start",
|
||||||
|
["cmd+backspace"] = "doc:delete-to-start-of-indentation",
|
||||||
["delete"] = "doc:delete",
|
["delete"] = "doc:delete",
|
||||||
["shift+delete"] = "doc:delete",
|
["shift+delete"] = "doc:delete",
|
||||||
["cmd+delete"] = "doc:delete-to-next-word-end",
|
["option+delete"] = "doc:delete-to-next-word-end",
|
||||||
["cmd+shift+delete"] = "doc:delete-to-next-word-end",
|
["cmd+shift+delete"] = "doc:delete-to-next-word-end",
|
||||||
|
["cmd+delete"] = "doc:delete-to-end-of-line",
|
||||||
["return"] = { "command:submit", "doc:newline", "dialog:select" },
|
["return"] = { "command:submit", "doc:newline", "dialog:select" },
|
||||||
["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" },
|
["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" },
|
||||||
["cmd+return"] = "doc:newline-below",
|
["cmd+return"] = "doc:newline-below",
|
||||||
["cmd+shift+return"] = "doc:newline-above",
|
["cmd+shift+return"] = "doc:newline-above",
|
||||||
["cmd+j"] = "doc:join-lines",
|
["cmd+j"] = "doc:join-lines",
|
||||||
["cmd+a"] = "doc:select-all",
|
["cmd+a"] = "doc:select-all",
|
||||||
["cmd+d"] = { "find-replace:select-next", "doc:select-word" },
|
["cmd+d"] = { "find-replace:select-add-next", "doc:select-word" },
|
||||||
|
["cmd+f3"] = "find-replace:select-next",
|
||||||
["cmd+l"] = "doc:select-lines",
|
["cmd+l"] = "doc:select-lines",
|
||||||
|
["cmd+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||||
["cmd+/"] = "doc:toggle-line-comments",
|
["cmd+/"] = "doc:toggle-line-comments",
|
||||||
["cmd+up"] = "doc:move-lines-up",
|
["option+up"] = "doc:move-lines-up",
|
||||||
["cmd+down"] = "doc:move-lines-down",
|
["option+down"] = "doc:move-lines-down",
|
||||||
["cmd+shift+d"] = "doc:duplicate-lines",
|
["cmd+shift+d"] = "doc:duplicate-lines",
|
||||||
["cmd+shift+k"] = "doc:delete-lines",
|
["cmd+shift+k"] = "doc:delete-lines",
|
||||||
|
|
||||||
|
@ -76,33 +82,42 @@ local function keymap_macos(keymap)
|
||||||
["right"] = { "doc:move-to-next-char", "dialog:next-entry"},
|
["right"] = { "doc:move-to-next-char", "dialog:next-entry"},
|
||||||
["up"] = { "command:select-previous", "doc:move-to-previous-line" },
|
["up"] = { "command:select-previous", "doc:move-to-previous-line" },
|
||||||
["down"] = { "command:select-next", "doc:move-to-next-line" },
|
["down"] = { "command:select-next", "doc:move-to-next-line" },
|
||||||
["cmd+left"] = "doc:move-to-previous-word-start",
|
["option+left"] = "doc:move-to-previous-word-start",
|
||||||
["cmd+right"] = "doc:move-to-next-word-end",
|
["option+right"] = "doc:move-to-next-word-end",
|
||||||
|
["cmd+left"] = "doc:move-to-start-of-indentation",
|
||||||
|
["cmd+right"] = "doc:move-to-end-of-line",
|
||||||
["cmd+["] = "doc:move-to-previous-block-start",
|
["cmd+["] = "doc:move-to-previous-block-start",
|
||||||
["cmd+]"] = "doc:move-to-next-block-end",
|
["cmd+]"] = "doc:move-to-next-block-end",
|
||||||
["home"] = "doc:move-to-start-of-line",
|
["home"] = "doc:move-to-start-of-indentation",
|
||||||
["end"] = "doc:move-to-end-of-line",
|
["end"] = "doc:move-to-end-of-line",
|
||||||
["cmd+home"] = "doc:move-to-start-of-doc",
|
["cmd+up"] = "doc:move-to-start-of-doc",
|
||||||
["cmd+end"] = "doc:move-to-end-of-doc",
|
["cmd+down"] = "doc:move-to-end-of-doc",
|
||||||
["pageup"] = "doc:move-to-previous-page",
|
["pageup"] = "doc:move-to-previous-page",
|
||||||
["pagedown"] = "doc:move-to-next-page",
|
["pagedown"] = "doc:move-to-next-page",
|
||||||
|
|
||||||
|
["shift+1lclick"] = "doc:select-to-cursor",
|
||||||
|
["ctrl+1lclick"] = "doc:split-cursor",
|
||||||
|
["1lclick"] = "doc:set-cursor",
|
||||||
|
["2lclick"] = "doc:set-cursor-word",
|
||||||
|
["3lclick"] = "doc:set-cursor-line",
|
||||||
["shift+left"] = "doc:select-to-previous-char",
|
["shift+left"] = "doc:select-to-previous-char",
|
||||||
["shift+right"] = "doc:select-to-next-char",
|
["shift+right"] = "doc:select-to-next-char",
|
||||||
["shift+up"] = "doc:select-to-previous-line",
|
["shift+up"] = "doc:select-to-previous-line",
|
||||||
["shift+down"] = "doc:select-to-next-line",
|
["shift+down"] = "doc:select-to-next-line",
|
||||||
["cmd+shift+left"] = "doc:select-to-previous-word-start",
|
["option+shift+left"] = "doc:select-to-previous-word-start",
|
||||||
["cmd+shift+right"] = "doc:select-to-next-word-end",
|
["option+shift+right"] = "doc:select-to-next-word-end",
|
||||||
|
["cmd+shift+left"] = "doc:select-to-start-of-indentation",
|
||||||
|
["cmd+shift+right"] = "doc:select-to-end-of-line",
|
||||||
["cmd+shift+["] = "doc:select-to-previous-block-start",
|
["cmd+shift+["] = "doc:select-to-previous-block-start",
|
||||||
["cmd+shift+]"] = "doc:select-to-next-block-end",
|
["cmd+shift+]"] = "doc:select-to-next-block-end",
|
||||||
["shift+home"] = "doc:select-to-start-of-line",
|
["shift+home"] = "doc:select-to-start-of-indentation",
|
||||||
["shift+end"] = "doc:select-to-end-of-line",
|
["shift+end"] = "doc:select-to-end-of-line",
|
||||||
["cmd+shift+home"] = "doc:select-to-start-of-doc",
|
["cmd+shift+up"] = "doc:select-to-start-of-doc",
|
||||||
["cmd+shift+end"] = "doc:select-to-end-of-doc",
|
["cmd+shift+down"] = "doc:select-to-end-of-doc",
|
||||||
["shift+pageup"] = "doc:select-to-previous-page",
|
["shift+pageup"] = "doc:select-to-previous-page",
|
||||||
["shift+pagedown"] = "doc:select-to-next-page",
|
["shift+pagedown"] = "doc:select-to-next-page",
|
||||||
["cmd+shift+up"] = "doc:create-cursor-previous-line",
|
["cmd+option+up"] = "doc:create-cursor-previous-line",
|
||||||
["cmd+shift+down"] = "doc:create-cursor-next-line"
|
["cmd+option+down"] = "doc:create-cursor-next-line"
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
local config = require "core.config"
|
||||||
local keymap = {}
|
local keymap = {}
|
||||||
|
|
||||||
keymap.modkeys = {}
|
keymap.modkeys = {}
|
||||||
keymap.map = {}
|
keymap.map = {}
|
||||||
keymap.reverse_map = {}
|
keymap.reverse_map = {}
|
||||||
|
|
||||||
local macos = rawget(_G, "MACOS_RESOURCES")
|
local macos = PLATFORM == "Mac OS X"
|
||||||
|
|
||||||
-- Thanks to mathewmariani, taken from his lite-macos github repository.
|
-- Thanks to mathewmariani, taken from his lite-macos github repository.
|
||||||
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or "generic"))
|
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or "generic"))
|
||||||
|
@ -30,7 +31,8 @@ function keymap.add_direct(map)
|
||||||
end
|
end
|
||||||
keymap.map[stroke] = commands
|
keymap.map[stroke] = commands
|
||||||
for _, cmd in ipairs(commands) do
|
for _, cmd in ipairs(commands) do
|
||||||
keymap.reverse_map[cmd] = stroke
|
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
|
||||||
|
table.insert(keymap.reverse_map[cmd], stroke)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -52,18 +54,43 @@ function keymap.add(map, overwrite)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for _, cmd in ipairs(commands) do
|
for _, cmd in ipairs(commands) do
|
||||||
keymap.reverse_map[cmd] = stroke
|
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
|
||||||
|
table.insert(keymap.reverse_map[cmd], stroke)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function keymap.get_binding(cmd)
|
local function remove_only(tbl, k, v)
|
||||||
return keymap.reverse_map[cmd]
|
for key, values in pairs(tbl) do
|
||||||
|
if key == k then
|
||||||
|
if v then
|
||||||
|
for i, value in ipairs(values) do
|
||||||
|
if value == v then
|
||||||
|
table.remove(values, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
tbl[key] = nil
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function keymap.on_key_pressed(k)
|
function keymap.unbind(key, cmd)
|
||||||
|
remove_only(keymap.map, key, cmd)
|
||||||
|
remove_only(keymap.reverse_map, cmd, key)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function keymap.get_binding(cmd)
|
||||||
|
return table.unpack(keymap.reverse_map[cmd] or {})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function keymap.on_key_pressed(k, ...)
|
||||||
local mk = modkey_map[k]
|
local mk = modkey_map[k]
|
||||||
if mk then
|
if mk then
|
||||||
keymap.modkeys[mk] = true
|
keymap.modkeys[mk] = true
|
||||||
|
@ -73,18 +100,30 @@ function keymap.on_key_pressed(k)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local stroke = key_to_stroke(k)
|
local stroke = key_to_stroke(k)
|
||||||
local commands = keymap.map[stroke]
|
local commands, performed = keymap.map[stroke]
|
||||||
if commands then
|
if commands then
|
||||||
for _, cmd in ipairs(commands) do
|
for _, cmd in ipairs(commands) do
|
||||||
local performed = command.perform(cmd)
|
performed = command.perform(cmd, ...)
|
||||||
if performed then break end
|
if performed then break end
|
||||||
end
|
end
|
||||||
return true
|
return performed
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function keymap.on_mouse_wheel(delta, ...)
|
||||||
|
return not (keymap.on_key_pressed("wheel" .. (delta > 0 and "up" or "down"), delta, ...)
|
||||||
|
or keymap.on_key_pressed("wheel", delta, ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
function keymap.on_mouse_pressed(button, x, y, clicks)
|
||||||
|
local click_number = (((clicks - 1) % config.max_clicks) + 1)
|
||||||
|
return not (keymap.on_key_pressed(click_number .. button:sub(1,1) .. "click", x, y, clicks) or
|
||||||
|
keymap.on_key_pressed(button:sub(1,1) .. "click", x, y, clicks) or
|
||||||
|
keymap.on_key_pressed(click_number .. "click", x, y, clicks) or
|
||||||
|
keymap.on_key_pressed("click", x, y, clicks))
|
||||||
|
end
|
||||||
|
|
||||||
function keymap.on_key_released(k)
|
function keymap.on_key_released(k)
|
||||||
local mk = modkey_map[k]
|
local mk = modkey_map[k]
|
||||||
|
@ -108,6 +147,7 @@ keymap.add_direct {
|
||||||
["ctrl+shift+c"] = "core:change-project-folder",
|
["ctrl+shift+c"] = "core:change-project-folder",
|
||||||
["ctrl+shift+o"] = "core:open-project-folder",
|
["ctrl+shift+o"] = "core:open-project-folder",
|
||||||
["alt+return"] = "core:toggle-fullscreen",
|
["alt+return"] = "core:toggle-fullscreen",
|
||||||
|
["f11"] = "core:toggle-fullscreen",
|
||||||
|
|
||||||
["alt+shift+j"] = "root:split-left",
|
["alt+shift+j"] = "root:split-left",
|
||||||
["alt+shift+l"] = "root:split-right",
|
["alt+shift+l"] = "root:split-right",
|
||||||
|
@ -132,6 +172,7 @@ keymap.add_direct {
|
||||||
["alt+7"] = "root:switch-to-tab-7",
|
["alt+7"] = "root:switch-to-tab-7",
|
||||||
["alt+8"] = "root:switch-to-tab-8",
|
["alt+8"] = "root:switch-to-tab-8",
|
||||||
["alt+9"] = "root:switch-to-tab-9",
|
["alt+9"] = "root:switch-to-tab-9",
|
||||||
|
["wheel"] = "root:scroll",
|
||||||
|
|
||||||
["ctrl+f"] = "find-replace:find",
|
["ctrl+f"] = "find-replace:find",
|
||||||
["ctrl+r"] = "find-replace:replace",
|
["ctrl+r"] = "find-replace:replace",
|
||||||
|
@ -167,8 +208,10 @@ keymap.add_direct {
|
||||||
["ctrl+shift+return"] = "doc:newline-above",
|
["ctrl+shift+return"] = "doc:newline-above",
|
||||||
["ctrl+j"] = "doc:join-lines",
|
["ctrl+j"] = "doc:join-lines",
|
||||||
["ctrl+a"] = "doc:select-all",
|
["ctrl+a"] = "doc:select-all",
|
||||||
["ctrl+d"] = { "find-replace:select-next", "doc:select-word" },
|
["ctrl+d"] = { "find-replace:select-add-next", "doc:select-word" },
|
||||||
|
["ctrl+f3"] = "find-replace:select-next",
|
||||||
["ctrl+l"] = "doc:select-lines",
|
["ctrl+l"] = "doc:select-lines",
|
||||||
|
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||||
["ctrl+/"] = "doc:toggle-line-comments",
|
["ctrl+/"] = "doc:toggle-line-comments",
|
||||||
["ctrl+up"] = "doc:move-lines-up",
|
["ctrl+up"] = "doc:move-lines-up",
|
||||||
["ctrl+down"] = "doc:move-lines-down",
|
["ctrl+down"] = "doc:move-lines-down",
|
||||||
|
@ -183,13 +226,18 @@ keymap.add_direct {
|
||||||
["ctrl+right"] = "doc:move-to-next-word-end",
|
["ctrl+right"] = "doc:move-to-next-word-end",
|
||||||
["ctrl+["] = "doc:move-to-previous-block-start",
|
["ctrl+["] = "doc:move-to-previous-block-start",
|
||||||
["ctrl+]"] = "doc:move-to-next-block-end",
|
["ctrl+]"] = "doc:move-to-next-block-end",
|
||||||
["home"] = "doc:move-to-start-of-line",
|
["home"] = "doc:move-to-start-of-indentation",
|
||||||
["end"] = "doc:move-to-end-of-line",
|
["end"] = "doc:move-to-end-of-line",
|
||||||
["ctrl+home"] = "doc:move-to-start-of-doc",
|
["ctrl+home"] = "doc:move-to-start-of-doc",
|
||||||
["ctrl+end"] = "doc:move-to-end-of-doc",
|
["ctrl+end"] = "doc:move-to-end-of-doc",
|
||||||
["pageup"] = "doc:move-to-previous-page",
|
["pageup"] = "doc:move-to-previous-page",
|
||||||
["pagedown"] = "doc:move-to-next-page",
|
["pagedown"] = "doc:move-to-next-page",
|
||||||
|
|
||||||
|
["shift+1lclick"] = "doc:select-to-cursor",
|
||||||
|
["ctrl+1lclick"] = "doc:split-cursor",
|
||||||
|
["1lclick"] = "doc:set-cursor",
|
||||||
|
["2lclick"] = "doc:set-cursor-word",
|
||||||
|
["3lclick"] = "doc:set-cursor-line",
|
||||||
["shift+left"] = "doc:select-to-previous-char",
|
["shift+left"] = "doc:select-to-previous-char",
|
||||||
["shift+right"] = "doc:select-to-next-char",
|
["shift+right"] = "doc:select-to-next-char",
|
||||||
["shift+up"] = "doc:select-to-previous-line",
|
["shift+up"] = "doc:select-to-previous-line",
|
||||||
|
@ -198,7 +246,7 @@ keymap.add_direct {
|
||||||
["ctrl+shift+right"] = "doc:select-to-next-word-end",
|
["ctrl+shift+right"] = "doc:select-to-next-word-end",
|
||||||
["ctrl+shift+["] = "doc:select-to-previous-block-start",
|
["ctrl+shift+["] = "doc:select-to-previous-block-start",
|
||||||
["ctrl+shift+]"] = "doc:select-to-next-block-end",
|
["ctrl+shift+]"] = "doc:select-to-next-block-end",
|
||||||
["shift+home"] = "doc:select-to-start-of-line",
|
["shift+home"] = "doc:select-to-start-of-indentation",
|
||||||
["shift+end"] = "doc:select-to-end-of-line",
|
["shift+end"] = "doc:select-to-end-of-line",
|
||||||
["ctrl+shift+home"] = "doc:select-to-start-of-doc",
|
["ctrl+shift+home"] = "doc:select-to-start-of-doc",
|
||||||
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
|
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
|
||||||
|
|
|
@ -1,14 +1,45 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
|
local common = require "core.common"
|
||||||
local style = require "core.style"
|
local style = require "core.style"
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
|
|
||||||
|
|
||||||
|
local function lines(text)
|
||||||
|
if text == "" then return 0 end
|
||||||
|
local l = 1
|
||||||
|
for _ in string.gmatch(text, "\n") do
|
||||||
|
l = l + 1
|
||||||
|
end
|
||||||
|
return l
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local item_height_result = {}
|
||||||
|
|
||||||
|
|
||||||
|
local function get_item_height(item)
|
||||||
|
local h = item_height_result[item]
|
||||||
|
if not h then
|
||||||
|
h = {}
|
||||||
|
local l = 1 + lines(item.text) + lines(item.info or "")
|
||||||
|
h.normal = style.font:get_height() + style.padding.y
|
||||||
|
h.expanded = l * style.font:get_height() + style.padding.y
|
||||||
|
h.current = h.normal
|
||||||
|
h.target = h.current
|
||||||
|
item_height_result[item] = h
|
||||||
|
end
|
||||||
|
return h
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
local LogView = View:extend()
|
local LogView = View:extend()
|
||||||
|
|
||||||
|
LogView.context = "session"
|
||||||
|
|
||||||
function LogView:new()
|
function LogView:new()
|
||||||
LogView.super.new(self)
|
LogView.super.new(self)
|
||||||
self.last_item = core.log_items[#core.log_items]
|
self.last_item = core.log_items[#core.log_items]
|
||||||
|
self.expanding = {}
|
||||||
self.scrollable = true
|
self.scrollable = true
|
||||||
self.yoffset = 0
|
self.yoffset = 0
|
||||||
end
|
end
|
||||||
|
@ -19,6 +50,55 @@ function LogView:get_name()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function is_expanded(item)
|
||||||
|
local item_height = get_item_height(item)
|
||||||
|
return item_height.target == item_height.expanded
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function LogView:expand_item(item)
|
||||||
|
item = get_item_height(item)
|
||||||
|
item.target = item.target == item.expanded and item.normal or item.expanded
|
||||||
|
table.insert(self.expanding, item)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function LogView:each_item()
|
||||||
|
local x, y = self:get_content_offset()
|
||||||
|
y = y + style.padding.y + self.yoffset
|
||||||
|
return coroutine.wrap(function()
|
||||||
|
for i = #core.log_items, 1, -1 do
|
||||||
|
local item = core.log_items[i]
|
||||||
|
local h = get_item_height(item).current
|
||||||
|
coroutine.yield(i, item, x, y, self.size.x, h)
|
||||||
|
y = y + h
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function LogView:on_mouse_moved(px, py, ...)
|
||||||
|
LogView.super.on_mouse_moved(self, px, py, ...)
|
||||||
|
local hovered = false
|
||||||
|
for _, item, x, y, w, h in self:each_item() do
|
||||||
|
if px >= x and py >= y and px < x + w and py < y + h then
|
||||||
|
hovered = true
|
||||||
|
self.hovered_item = item
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not hovered then self.hovered_item = nil end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function LogView:on_mouse_pressed(button, mx, my, clicks)
|
||||||
|
if LogView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
|
||||||
|
if self.hovered_item then
|
||||||
|
self:expand_item(self.hovered_item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function LogView:update()
|
function LogView:update()
|
||||||
local item = core.log_items[#core.log_items]
|
local item = core.log_items[#core.log_items]
|
||||||
if self.last_item ~= item then
|
if self.last_item ~= item then
|
||||||
|
@ -27,6 +107,14 @@ function LogView:update()
|
||||||
self.yoffset = -(style.font:get_height() + style.padding.y)
|
self.yoffset = -(style.font:get_height() + style.padding.y)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local expanding = self.expanding[1]
|
||||||
|
if expanding then
|
||||||
|
self:move_towards(expanding, "current", expanding.target)
|
||||||
|
if expanding.current == expanding.target then
|
||||||
|
table.remove(self.expanding, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
self:move_towards("yoffset", 0)
|
self:move_towards("yoffset", 0)
|
||||||
|
|
||||||
LogView.super.update(self)
|
LogView.super.update(self)
|
||||||
|
@ -35,38 +123,48 @@ end
|
||||||
|
|
||||||
local function draw_text_multiline(font, text, x, y, color)
|
local function draw_text_multiline(font, text, x, y, color)
|
||||||
local th = font:get_height()
|
local th = font:get_height()
|
||||||
local resx, resy = x, y
|
local resx = x
|
||||||
for line in text:gmatch("[^\n]+") do
|
for line in text:gmatch("[^\n]+") do
|
||||||
resy = y
|
|
||||||
resx = renderer.draw_text(style.font, line, x, y, color)
|
resx = renderer.draw_text(style.font, line, x, y, color)
|
||||||
y = y + th
|
y = y + th
|
||||||
end
|
end
|
||||||
return resx, resy
|
return resx, y
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function LogView:draw()
|
function LogView:draw()
|
||||||
self:draw_background(style.background)
|
self:draw_background(style.background)
|
||||||
|
|
||||||
local ox, oy = self:get_content_offset()
|
|
||||||
local th = style.font:get_height()
|
local th = style.font:get_height()
|
||||||
local y = oy + style.padding.y + self.yoffset
|
local lh = th + style.padding.y -- for one line
|
||||||
|
for _, item, x, y, w in self:each_item() do
|
||||||
for i = #core.log_items, 1, -1 do
|
|
||||||
local x = ox + style.padding.x
|
|
||||||
local item = core.log_items[i]
|
|
||||||
local time = os.date(nil, item.time)
|
|
||||||
x = renderer.draw_text(style.font, time, x, y, style.dim)
|
|
||||||
x = x + style.padding.x
|
x = x + style.padding.x
|
||||||
local subx = x
|
|
||||||
x, y = draw_text_multiline(style.font, item.text, x, y, style.text)
|
local time = os.date(nil, item.time)
|
||||||
renderer.draw_text(style.font, " at " .. item.at, x, y, style.dim)
|
x = common.draw_text(style.font, style.dim, time, "left", x, y, w, lh)
|
||||||
y = y + th
|
x = x + style.padding.x
|
||||||
if item.info then
|
|
||||||
subx, y = draw_text_multiline(style.font, item.info, subx, y, style.dim)
|
x = common.draw_text(style.code_font, style.dim, is_expanded(item) and "-" or "+", "left", x, y, w, lh)
|
||||||
y = y + th
|
x = x + style.padding.x
|
||||||
|
w = w - (x - self:get_content_offset())
|
||||||
|
|
||||||
|
if is_expanded(item) then
|
||||||
|
y = y + common.round(style.padding.y / 2)
|
||||||
|
_, y = draw_text_multiline(style.font, item.text, x, y, style.text)
|
||||||
|
|
||||||
|
local at = "at " .. common.home_encode(item.at)
|
||||||
|
_, y = common.draw_text(style.font, style.dim, at, "left", x, y, w, lh)
|
||||||
|
|
||||||
|
if item.info then
|
||||||
|
_, y = draw_text_multiline(style.font, item.info, x, y, style.dim)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local line, has_newline = string.match(item.text, "([^\n]+)(\n?)")
|
||||||
|
if has_newline ~= "" then
|
||||||
|
line = line .. " ..."
|
||||||
|
end
|
||||||
|
_, y = common.draw_text(style.font, style.text, line, "left", x, y, w, lh)
|
||||||
end
|
end
|
||||||
y = y + style.padding.y
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -193,7 +193,8 @@ function NagView:next()
|
||||||
self:change_hovered(common.find_index(self.options, "default_yes"))
|
self:change_hovered(common.find_index(self.options, "default_yes"))
|
||||||
end
|
end
|
||||||
self.force_focus = self.message ~= nil
|
self.force_focus = self.message ~= nil
|
||||||
core.set_active_view(self.message ~= nil and self or core.last_active_view)
|
core.set_active_view(self.message ~= nil and self or
|
||||||
|
core.next_active_view or core.last_active_view)
|
||||||
end
|
end
|
||||||
|
|
||||||
function NagView:show(title, message, options, on_select)
|
function NagView:show(title, message, options, on_select)
|
||||||
|
|
|
@ -20,17 +20,6 @@ function Object:extend()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Object:implement(...)
|
|
||||||
for _, cls in pairs({...}) do
|
|
||||||
for k, v in pairs(cls) do
|
|
||||||
if self[k] == nil and type(v) == "function" then
|
|
||||||
self[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Object:is(T)
|
function Object:is(T)
|
||||||
local mt = getmetatable(self)
|
local mt = getmetatable(self)
|
||||||
while mt do
|
while mt do
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
-- So that in addition to regex.gsub(pattern, string), we can also do
|
-- So that in addition to regex.gsub(pattern, string), we can also do
|
||||||
-- pattern:gsub(string).
|
-- pattern:gsub(string).
|
||||||
regex.__index = function(table, key) return regex[key]; end
|
regex.__index = function(table, key) return regex[key]; end
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ regex.match = function(pattern_string, string, offset, options)
|
||||||
return regex.cmatch(pattern, string, offset or 1, options or 0)
|
return regex.cmatch(pattern, string, offset or 1, options or 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Will iterate back through any UTF-8 bytes so that we don't replace bits
|
-- Will iterate back through any UTF-8 bytes so that we don't replace bits
|
||||||
-- mid character.
|
-- mid character.
|
||||||
local function previous_character(str, index)
|
local function previous_character(str, index)
|
||||||
local byte
|
local byte
|
||||||
|
@ -23,7 +23,7 @@ end
|
||||||
-- Moves to the end of the identified character.
|
-- Moves to the end of the identified character.
|
||||||
local function end_character(str, index)
|
local function end_character(str, index)
|
||||||
local byte = string.byte(str, index + 1)
|
local byte = string.byte(str, index + 1)
|
||||||
while byte >= 128 and byte < 192 do
|
while byte and byte >= 128 and byte < 192 do
|
||||||
index = index + 1
|
index = index + 1
|
||||||
byte = string.byte(str, index + 1)
|
byte = string.byte(str, index + 1)
|
||||||
end
|
end
|
||||||
|
@ -32,7 +32,7 @@ end
|
||||||
|
|
||||||
-- Build off matching. For now, only support basic replacements, but capture
|
-- Build off matching. For now, only support basic replacements, but capture
|
||||||
-- groupings should be doable. We can even have custom group replacements and
|
-- groupings should be doable. We can even have custom group replacements and
|
||||||
-- transformations and stuff in lua. Currently, this takes group replacements
|
-- transformations and stuff in lua. Currently, this takes group replacements
|
||||||
-- as \1 - \9.
|
-- as \1 - \9.
|
||||||
-- Should work on UTF-8 text.
|
-- Should work on UTF-8 text.
|
||||||
regex.gsub = function(pattern_string, str, replacement)
|
regex.gsub = function(pattern_string, str, replacement)
|
||||||
|
@ -48,8 +48,8 @@ regex.gsub = function(pattern_string, str, replacement)
|
||||||
if #indices > 2 then
|
if #indices > 2 then
|
||||||
for i = 1, (#indices/2 - 1) do
|
for i = 1, (#indices/2 - 1) do
|
||||||
currentReplacement = string.gsub(
|
currentReplacement = string.gsub(
|
||||||
currentReplacement,
|
currentReplacement,
|
||||||
"\\" .. i,
|
"\\" .. i,
|
||||||
str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))
|
str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -57,10 +57,10 @@ regex.gsub = function(pattern_string, str, replacement)
|
||||||
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
|
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
|
||||||
table.insert(replacements, { indices[1], #currentReplacement+indices[1] })
|
table.insert(replacements, { indices[1], #currentReplacement+indices[1] })
|
||||||
if indices[1] > 1 then
|
if indices[1] > 1 then
|
||||||
result = result ..
|
result = result ..
|
||||||
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
|
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
|
||||||
else
|
else
|
||||||
result = result .. currentReplacement
|
result = result .. currentReplacement
|
||||||
end
|
end
|
||||||
str = str:sub(indices[2])
|
str = str:sub(indices[2])
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,6 @@ local style = require "core.style"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
local Object = require "core.object"
|
local Object = require "core.object"
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
local CommandView = require "core.commandview"
|
|
||||||
local NagView = require "core.nagview"
|
local NagView = require "core.nagview"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
|
|
||||||
|
@ -143,7 +142,14 @@ function Node:remove_view(root, view)
|
||||||
local parent = self:get_parent_node(root)
|
local parent = self:get_parent_node(root)
|
||||||
local is_a = (parent.a == self)
|
local is_a = (parent.a == self)
|
||||||
local other = parent[is_a and "b" or "a"]
|
local other = parent[is_a and "b" or "a"]
|
||||||
if other:get_locked_size() then
|
local locked_size_x, locked_size_y = other:get_locked_size()
|
||||||
|
local locked_size
|
||||||
|
if parent.type == "hsplit" then
|
||||||
|
locked_size = locked_size_x
|
||||||
|
else
|
||||||
|
locked_size = locked_size_y
|
||||||
|
end
|
||||||
|
if self.is_primary_node or locked_size then
|
||||||
self.views = {}
|
self.views = {}
|
||||||
self:add_view(EmptyView())
|
self:add_view(EmptyView())
|
||||||
else
|
else
|
||||||
|
@ -240,12 +246,15 @@ end
|
||||||
|
|
||||||
function Node:get_divider_overlapping_point(px, py)
|
function Node:get_divider_overlapping_point(px, py)
|
||||||
if self.type ~= "leaf" then
|
if self.type ~= "leaf" then
|
||||||
local p = 6
|
local axis = self.type == "hsplit" and "x" or "y"
|
||||||
local x, y, w, h = self:get_divider_rect()
|
if self.a:is_resizable(axis) and self.b:is_resizable(axis) then
|
||||||
x, y = x - p, y - p
|
local p = 6
|
||||||
w, h = w + p * 2, h + p * 2
|
local x, y, w, h = self:get_divider_rect()
|
||||||
if px > x and py > y and px < x + w and py < y + h then
|
x, y = x - p, y - p
|
||||||
return self
|
w, h = w + p * 2, h + p * 2
|
||||||
|
if px > x and py > y and px < x + w and py < y + h then
|
||||||
|
return self
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return self.a:get_divider_overlapping_point(px, py)
|
return self.a:get_divider_overlapping_point(px, py)
|
||||||
or self.b:get_divider_overlapping_point(px, py)
|
or self.b:get_divider_overlapping_point(px, py)
|
||||||
|
@ -259,7 +268,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
function Node:get_tab_overlapping_point(px, py)
|
function Node:get_tab_overlapping_point(px, py)
|
||||||
if #self.views == 1 then return nil end
|
if not self:should_show_tabs() then return nil end
|
||||||
local tabs_number = self:get_visible_tabs_number()
|
local tabs_number = self:get_visible_tabs_number()
|
||||||
local x1, y1, w, h = self:get_tab_rect(self.tab_offset)
|
local x1, y1, w, h = self:get_tab_rect(self.tab_offset)
|
||||||
local x2, y2 = self:get_tab_rect(self.tab_offset + tabs_number)
|
local x2, y2 = self:get_tab_rect(self.tab_offset + tabs_number)
|
||||||
|
@ -269,6 +278,19 @@ function Node:get_tab_overlapping_point(px, py)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:should_show_tabs()
|
||||||
|
if self.locked then return false end
|
||||||
|
local dn = core.root_view.dragged_node
|
||||||
|
if #self.views > 1
|
||||||
|
or (dn and dn.dragging) then -- show tabs while dragging
|
||||||
|
return true
|
||||||
|
elseif config.always_show_tabs then
|
||||||
|
return not self.views[1]:is(EmptyView)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
local function close_button_location(x, w)
|
local function close_button_location(x, w)
|
||||||
local cw = style.icon_font:get_width("C")
|
local cw = style.icon_font:get_width("C")
|
||||||
local pad = style.padding.y
|
local pad = style.padding.y
|
||||||
|
@ -412,7 +434,7 @@ end
|
||||||
function Node:update_layout()
|
function Node:update_layout()
|
||||||
if self.type == "leaf" then
|
if self.type == "leaf" then
|
||||||
local av = self.active_view
|
local av = self.active_view
|
||||||
if #self.views > 1 then
|
if self:should_show_tabs() then
|
||||||
local _, _, _, th = self:get_tab_rect(1)
|
local _, _, _, th = self:get_tab_rect(1)
|
||||||
av.position.x, av.position.y = self.position.x, self.position.y + th
|
av.position.x, av.position.y = self.position.x, self.position.y + th
|
||||||
av.size.x, av.size.y = self.size.x, self.size.y - th
|
av.size.x, av.size.y = self.size.x, self.size.y - th
|
||||||
|
@ -494,6 +516,53 @@ function Node:update()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
||||||
|
local ds = style.divider_size
|
||||||
|
local dots_width = style.font:get_width("…")
|
||||||
|
local color = style.dim
|
||||||
|
local padding_y = style.padding.y
|
||||||
|
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
||||||
|
if standalone then
|
||||||
|
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
|
||||||
|
end
|
||||||
|
if is_active then
|
||||||
|
color = style.text
|
||||||
|
renderer.draw_rect(x, y, w, h, style.background)
|
||||||
|
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
||||||
|
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
||||||
|
end
|
||||||
|
local cx, cw, cspace = close_button_location(x, w)
|
||||||
|
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
|
||||||
|
if show_close_button then
|
||||||
|
local close_style = is_close_hovered and style.text or style.dim
|
||||||
|
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
||||||
|
end
|
||||||
|
if is_hovered then
|
||||||
|
color = style.text
|
||||||
|
end
|
||||||
|
local padx = style.padding.x
|
||||||
|
-- Normally we should substract "cspace" from text_avail_width and from the
|
||||||
|
-- clipping width. It is the padding space we give to the left and right of the
|
||||||
|
-- close button. However, since we are using dots to terminate filenames, we
|
||||||
|
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
||||||
|
-- close button.
|
||||||
|
local text_avail_width = cx - x - padx
|
||||||
|
core.push_clip_rect(x, y, cx - x, h)
|
||||||
|
x, w = x + padx, w - padx * 2
|
||||||
|
local align = "center"
|
||||||
|
if style.font:get_width(text) > text_avail_width then
|
||||||
|
align = "left"
|
||||||
|
for i = 1, #text do
|
||||||
|
local reduced_text = text:sub(1, #text - i)
|
||||||
|
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
||||||
|
text = reduced_text .. "…"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
common.draw_text(style.font, color, text, align, x, y, w, h)
|
||||||
|
core.pop_clip_rect()
|
||||||
|
end
|
||||||
|
|
||||||
function Node:draw_tabs()
|
function Node:draw_tabs()
|
||||||
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
||||||
|
@ -518,47 +587,9 @@ function Node:draw_tabs()
|
||||||
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
||||||
local view = self.views[i]
|
local view = self.views[i]
|
||||||
local x, y, w, h = self:get_tab_rect(i)
|
local x, y, w, h = self:get_tab_rect(i)
|
||||||
local text = view:get_name()
|
self:draw_tab(view:get_name(), view == self.active_view,
|
||||||
local color = style.dim
|
i == self.hovered_tab, i == self.hovered_close,
|
||||||
local padding_y = style.padding.y
|
x, y, w, h)
|
||||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
|
||||||
if view == self.active_view then
|
|
||||||
color = style.text
|
|
||||||
renderer.draw_rect(x, y, w, h, style.background)
|
|
||||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
|
||||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
|
||||||
end
|
|
||||||
local cx, cw, cspace = close_button_location(x, w)
|
|
||||||
local show_close_button = ((view == self.active_view or i == self.hovered_tab) and config.tab_close_button)
|
|
||||||
if show_close_button then
|
|
||||||
local close_style = self.hovered_close == i and style.text or style.dim
|
|
||||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
|
||||||
end
|
|
||||||
if i == self.hovered_tab then
|
|
||||||
color = style.text
|
|
||||||
end
|
|
||||||
local padx = style.padding.x
|
|
||||||
-- Normally we should substract "cspace" from text_avail_width and from the
|
|
||||||
-- clipping width. It is the padding space we give to the left and right of the
|
|
||||||
-- close button. However, since we are using dots to terminate filenames, we
|
|
||||||
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
|
||||||
-- close button.
|
|
||||||
local text_avail_width = cx - x - padx
|
|
||||||
core.push_clip_rect(x, y, cx - x, h)
|
|
||||||
x, w = x + padx, w - padx * 2
|
|
||||||
local align = "center"
|
|
||||||
if style.font:get_width(text) > text_avail_width then
|
|
||||||
align = "left"
|
|
||||||
for i = 1, #text do
|
|
||||||
local reduced_text = text:sub(1, #text - i)
|
|
||||||
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
|
||||||
text = reduced_text .. "…"
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
common.draw_text(style.font, color, text, align, x, y, w, h)
|
|
||||||
core.pop_clip_rect()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
core.pop_clip_rect()
|
core.pop_clip_rect()
|
||||||
|
@ -567,7 +598,7 @@ end
|
||||||
|
|
||||||
function Node:draw()
|
function Node:draw()
|
||||||
if self.type == "leaf" then
|
if self.type == "leaf" then
|
||||||
if #self.views > 1 then
|
if self:should_show_tabs() then
|
||||||
self:draw_tabs()
|
self:draw_tabs()
|
||||||
end
|
end
|
||||||
local pos, size = self.active_view.position, self.active_view.size
|
local pos, size = self.active_view.position, self.active_view.size
|
||||||
|
@ -591,23 +622,37 @@ function Node:is_empty()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Node:close_all_docviews()
|
function Node:close_all_docviews(keep_active)
|
||||||
|
local node_active_view = self.active_view
|
||||||
|
local lost_active_view = false
|
||||||
if self.type == "leaf" then
|
if self.type == "leaf" then
|
||||||
local i = 1
|
local i = 1
|
||||||
while i <= #self.views do
|
while i <= #self.views do
|
||||||
local view = self.views[i]
|
local view = self.views[i]
|
||||||
if view:is(DocView) and not view:is(CommandView) then
|
if view.context == "session" and (not keep_active or view ~= self.active_view) then
|
||||||
table.remove(self.views, i)
|
table.remove(self.views, i)
|
||||||
|
if view == node_active_view then
|
||||||
|
lost_active_view = true
|
||||||
|
end
|
||||||
else
|
else
|
||||||
i = i + 1
|
i = i + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
self.tab_offset = 1
|
||||||
if #self.views == 0 and self.is_primary_node then
|
if #self.views == 0 and self.is_primary_node then
|
||||||
|
-- if we are not the primary view and we had the active view it doesn't
|
||||||
|
-- matter to reattribute the active view because, within the close_all_docviews
|
||||||
|
-- top call, the primary node will take the active view anyway.
|
||||||
|
-- Set the empty view and takes the active view.
|
||||||
self:add_view(EmptyView())
|
self:add_view(EmptyView())
|
||||||
|
elseif #self.views > 0 and lost_active_view then
|
||||||
|
-- In practice we never get there but if a view remain we need
|
||||||
|
-- to reset the Node's active view.
|
||||||
|
self:set_active_view(self.views[1])
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
self.a:close_all_docviews()
|
self.a:close_all_docviews(keep_active)
|
||||||
self.b:close_all_docviews()
|
self.b:close_all_docviews(keep_active)
|
||||||
if self.a:is_empty() and not self.a.is_primary_node then
|
if self.a:is_empty() and not self.a.is_primary_node then
|
||||||
self:consume(self.b)
|
self:consume(self.b)
|
||||||
elseif self.b:is_empty() and not self.b.is_primary_node then
|
elseif self.b:is_empty() and not self.b.is_primary_node then
|
||||||
|
@ -670,6 +715,62 @@ function Node:resize(axis, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_split_type(mouse_x, mouse_y)
|
||||||
|
local x, y = self.position.x, self.position.y
|
||||||
|
local w, h = self.size.x, self.size.y
|
||||||
|
local _, _, _, tab_h = self:get_scroll_button_rect(1)
|
||||||
|
y = y + tab_h
|
||||||
|
h = h - tab_h
|
||||||
|
|
||||||
|
local local_mouse_x = mouse_x - x
|
||||||
|
local local_mouse_y = mouse_y - y
|
||||||
|
|
||||||
|
if local_mouse_y < 0 then
|
||||||
|
return "tab"
|
||||||
|
else
|
||||||
|
local left_pct = local_mouse_x * 100 / w
|
||||||
|
local top_pct = local_mouse_y * 100 / h
|
||||||
|
if left_pct <= 30 then
|
||||||
|
return "left"
|
||||||
|
elseif left_pct >= 70 then
|
||||||
|
return "right"
|
||||||
|
elseif top_pct <= 30 then
|
||||||
|
return "up"
|
||||||
|
elseif top_pct >= 70 then
|
||||||
|
return "down"
|
||||||
|
end
|
||||||
|
return "middle"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_drag_overlay_tab_position(x, y, dragged_node, dragged_index)
|
||||||
|
local tab_index = self:get_tab_overlapping_point(x, y)
|
||||||
|
if not tab_index then
|
||||||
|
local first_tab_x = self:get_tab_rect(1)
|
||||||
|
if x < first_tab_x then
|
||||||
|
-- mouse before first visible tab
|
||||||
|
tab_index = self.tab_offset or 1
|
||||||
|
else
|
||||||
|
-- mouse after last visible tab
|
||||||
|
tab_index = self:get_visible_tabs_number() + (self.tab_offset - 1 or 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local tab_x, tab_y, tab_w, tab_h = self:get_tab_rect(tab_index)
|
||||||
|
if x > tab_x + tab_w / 2 and tab_index <= #self.views then
|
||||||
|
-- use next tab
|
||||||
|
tab_x = tab_x + tab_w
|
||||||
|
tab_index = tab_index + 1
|
||||||
|
end
|
||||||
|
if self == dragged_node and dragged_index and tab_index > dragged_index then
|
||||||
|
-- the tab we are moving is counted in tab_index
|
||||||
|
tab_index = tab_index - 1
|
||||||
|
tab_x = tab_x - tab_w
|
||||||
|
end
|
||||||
|
return tab_index, tab_x, tab_y, tab_w, tab_h
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
local RootView = View:extend()
|
local RootView = View:extend()
|
||||||
|
|
||||||
function RootView:new()
|
function RootView:new()
|
||||||
|
@ -677,6 +778,14 @@ function RootView:new()
|
||||||
self.root_node = Node()
|
self.root_node = Node()
|
||||||
self.deferred_draws = {}
|
self.deferred_draws = {}
|
||||||
self.mouse = { x = 0, y = 0 }
|
self.mouse = { x = 0, y = 0 }
|
||||||
|
self.drag_overlay = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0,
|
||||||
|
base_color = style.drag_overlay,
|
||||||
|
color = { table.unpack(style.drag_overlay) } }
|
||||||
|
self.drag_overlay.to = { x = 0, y = 0, w = 0, h = 0 }
|
||||||
|
self.drag_overlay_tab = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0,
|
||||||
|
base_color = style.drag_overlay_tab,
|
||||||
|
color = { table.unpack(style.drag_overlay_tab) } }
|
||||||
|
self.drag_overlay_tab.to = { x = 0, y = 0, w = 0, h = 0 }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -733,8 +842,8 @@ function RootView:open_doc(doc)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function RootView:close_all_docviews()
|
function RootView:close_all_docviews(keep_active)
|
||||||
self.root_node:close_all_docviews()
|
self.root_node:close_all_docviews(keep_active)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -748,38 +857,101 @@ function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||||
if div then
|
if div then
|
||||||
self.dragged_divider = div
|
self.dragged_divider = div
|
||||||
return
|
return true
|
||||||
end
|
end
|
||||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||||
if node.hovered_scroll_button > 0 then
|
if node.hovered_scroll_button > 0 then
|
||||||
node:scroll_tabs(node.hovered_scroll_button)
|
node:scroll_tabs(node.hovered_scroll_button)
|
||||||
return
|
return true
|
||||||
end
|
end
|
||||||
local idx = node:get_tab_overlapping_point(x, y)
|
local idx = node:get_tab_overlapping_point(x, y)
|
||||||
if idx then
|
if idx then
|
||||||
if button == "middle" or node.hovered_close == idx then
|
if button == "middle" or node.hovered_close == idx then
|
||||||
node:close_view(self.root_node, node.views[idx])
|
node:close_view(self.root_node, node.views[idx])
|
||||||
|
return true
|
||||||
else
|
else
|
||||||
self.dragged_node = { node, idx }
|
if button == "left" then
|
||||||
|
self.dragged_node = { node = node, idx = idx, dragging = false, drag_start_x = x, drag_start_y = y}
|
||||||
|
end
|
||||||
node:set_active_view(node.views[idx])
|
node:set_active_view(node.views[idx])
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
else
|
elseif not self.dragged_node then -- avoid sending on_mouse_pressed events when dragging tabs
|
||||||
core.set_active_view(node.active_view)
|
core.set_active_view(node.active_view)
|
||||||
if not self.on_view_mouse_pressed(button, x, y, clicks) then
|
if not self.on_view_mouse_pressed(button, x, y, clicks) then
|
||||||
node.active_view:on_mouse_pressed(button, x, y, clicks)
|
return node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function RootView:on_mouse_released(...)
|
function RootView:get_overlay_base_color(overlay)
|
||||||
|
if overlay == self.drag_overlay then
|
||||||
|
return style.drag_overlay
|
||||||
|
else
|
||||||
|
return style.drag_overlay_tab
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function RootView:set_show_overlay(overlay, status)
|
||||||
|
overlay.visible = status
|
||||||
|
if status then -- reset colors
|
||||||
|
-- reload base_color
|
||||||
|
overlay.base_color = self:get_overlay_base_color(overlay)
|
||||||
|
overlay.color[1] = overlay.base_color[1]
|
||||||
|
overlay.color[2] = overlay.base_color[2]
|
||||||
|
overlay.color[3] = overlay.base_color[3]
|
||||||
|
overlay.color[4] = overlay.base_color[4]
|
||||||
|
overlay.opacity = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function RootView:on_mouse_released(button, x, y, ...)
|
||||||
if self.dragged_divider then
|
if self.dragged_divider then
|
||||||
self.dragged_divider = nil
|
self.dragged_divider = nil
|
||||||
end
|
end
|
||||||
if self.dragged_node then
|
if self.dragged_node then
|
||||||
self.dragged_node = nil
|
if button == "left" then
|
||||||
|
if self.dragged_node.dragging then
|
||||||
|
local node = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y)
|
||||||
|
local dragged_node = self.dragged_node.node
|
||||||
|
|
||||||
|
if node and not node.locked
|
||||||
|
-- don't do anything if dragging onto own node, with only one view
|
||||||
|
and (node ~= dragged_node or #node.views > 1) then
|
||||||
|
local split_type = node:get_split_type(self.mouse.x, self.mouse.y)
|
||||||
|
local view = dragged_node.views[self.dragged_node.idx]
|
||||||
|
|
||||||
|
if split_type ~= "middle" and split_type ~= "tab" then -- needs splitting
|
||||||
|
local new_node = node:split(split_type)
|
||||||
|
self.root_node:get_node_for_view(view):remove_view(self.root_node, view)
|
||||||
|
new_node:add_view(view)
|
||||||
|
elseif split_type == "middle" and node ~= dragged_node then -- move to other node
|
||||||
|
dragged_node:remove_view(self.root_node, view)
|
||||||
|
node:add_view(view)
|
||||||
|
self.root_node:get_node_for_view(view):set_active_view(view)
|
||||||
|
elseif split_type == "tab" then -- move besides other tabs
|
||||||
|
local tab_index = node:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y, dragged_node, self.dragged_node.idx)
|
||||||
|
dragged_node:remove_view(self.root_node, view)
|
||||||
|
node:add_view(view, tab_index)
|
||||||
|
self.root_node:get_node_for_view(view):set_active_view(view)
|
||||||
|
end
|
||||||
|
self.root_node:update_layout()
|
||||||
|
core.redraw = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:set_show_overlay(self.drag_overlay, false)
|
||||||
|
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||||
|
if self.dragged_node and self.dragged_node.dragging then
|
||||||
|
core.request_cursor("arrow")
|
||||||
|
end
|
||||||
|
self.dragged_node = nil
|
||||||
|
end
|
||||||
|
else -- avoid sending on_mouse_released events when dragging tabs
|
||||||
|
self.root_node:on_mouse_released(button, x, y, ...)
|
||||||
end
|
end
|
||||||
self.root_node:on_mouse_released(...)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -815,40 +987,33 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.mouse.x, self.mouse.y = x, y
|
self.mouse.x, self.mouse.y = x, y
|
||||||
|
|
||||||
|
local dn = self.dragged_node
|
||||||
|
if dn and not dn.dragging then
|
||||||
|
-- start dragging only after enough movement
|
||||||
|
dn.dragging = common.distance(x, y, dn.drag_start_x, dn.drag_start_y) > style.tab_width * .05
|
||||||
|
if dn.dragging then
|
||||||
|
core.request_cursor("hand")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- avoid sending on_mouse_moved events when dragging tabs
|
||||||
|
if dn then return end
|
||||||
|
|
||||||
self.root_node:on_mouse_moved(x, y, dx, dy)
|
self.root_node:on_mouse_moved(x, y, dx, dy)
|
||||||
|
|
||||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
self.overlapping_node = self.root_node:get_child_overlapping_point(x, y)
|
||||||
|
|
||||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||||
local tab_index = node and node:get_tab_overlapping_point(x, y)
|
local tab_index = self.overlapping_node and self.overlapping_node:get_tab_overlapping_point(x, y)
|
||||||
if node and node:get_scroll_button_index(x, y) then
|
if self.overlapping_node and self.overlapping_node:get_scroll_button_index(x, y) then
|
||||||
core.request_cursor("arrow")
|
core.request_cursor("arrow")
|
||||||
elseif div then
|
elseif div then
|
||||||
local axis = (div.type == "hsplit" and "x" or "y")
|
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||||
if div.a:is_resizable(axis) and div.b:is_resizable(axis) then
|
|
||||||
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
|
||||||
end
|
|
||||||
elseif tab_index then
|
elseif tab_index then
|
||||||
core.request_cursor("arrow")
|
core.request_cursor("arrow")
|
||||||
elseif node then
|
elseif self.overlapping_node then
|
||||||
core.request_cursor(node.active_view.cursor)
|
core.request_cursor(self.overlapping_node.active_view.cursor)
|
||||||
end
|
|
||||||
if node and self.dragged_node and (self.dragged_node[1] ~= node or (tab_index and self.dragged_node[2] ~= tab_index))
|
|
||||||
and node.type == "leaf" and #node.views > 0 and node.views[1]:is(DocView) then
|
|
||||||
local tab = self.dragged_node[1].views[self.dragged_node[2]]
|
|
||||||
if self.dragged_node[1] ~= node then
|
|
||||||
for i, v in ipairs(node.views) do if v.doc == tab.doc then tab = nil break end end
|
|
||||||
if tab then
|
|
||||||
self.dragged_node[1]:remove_view(self.root_node, tab)
|
|
||||||
node:add_view(tab, tab_index)
|
|
||||||
self.root_node:update_layout()
|
|
||||||
self.dragged_node = { node, tab_index or #node.views }
|
|
||||||
core.redraw = true
|
|
||||||
end
|
|
||||||
else
|
|
||||||
table.remove(self.dragged_node[1].views, self.dragged_node[2])
|
|
||||||
table.insert(node.views, tab_index, tab)
|
|
||||||
self.dragged_node = { node, tab_index }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -856,7 +1021,7 @@ end
|
||||||
function RootView:on_mouse_wheel(...)
|
function RootView:on_mouse_wheel(...)
|
||||||
local x, y = self.mouse.x, self.mouse.y
|
local x, y = self.mouse.x, self.mouse.y
|
||||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||||
node.active_view:on_mouse_wheel(...)
|
return node.active_view:on_mouse_wheel(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -870,10 +1035,110 @@ function RootView:on_focus_lost(...)
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function RootView:interpolate_drag_overlay(overlay)
|
||||||
|
self:move_towards(overlay, "x", overlay.to.x)
|
||||||
|
self:move_towards(overlay, "y", overlay.to.y)
|
||||||
|
self:move_towards(overlay, "w", overlay.to.w)
|
||||||
|
self:move_towards(overlay, "h", overlay.to.h)
|
||||||
|
|
||||||
|
self:move_towards(overlay, "opacity", overlay.visible and 100 or 0)
|
||||||
|
overlay.color[4] = overlay.base_color[4] * overlay.opacity / 100
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function RootView:update()
|
function RootView:update()
|
||||||
copy_position_and_size(self.root_node, self)
|
copy_position_and_size(self.root_node, self)
|
||||||
self.root_node:update()
|
self.root_node:update()
|
||||||
self.root_node:update_layout()
|
self.root_node:update_layout()
|
||||||
|
|
||||||
|
self:update_drag_overlay()
|
||||||
|
self:interpolate_drag_overlay(self.drag_overlay)
|
||||||
|
self:interpolate_drag_overlay(self.drag_overlay_tab)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function RootView:set_drag_overlay(overlay, x, y, w, h, immediate)
|
||||||
|
overlay.to.x = x
|
||||||
|
overlay.to.y = y
|
||||||
|
overlay.to.w = w
|
||||||
|
overlay.to.h = h
|
||||||
|
if immediate then
|
||||||
|
overlay.x = x
|
||||||
|
overlay.y = y
|
||||||
|
overlay.w = w
|
||||||
|
overlay.h = h
|
||||||
|
end
|
||||||
|
if not overlay.visible then
|
||||||
|
self:set_show_overlay(overlay, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_split_sizes(split_type, x, y, w, h)
|
||||||
|
if split_type == "left" then
|
||||||
|
w = w * .5
|
||||||
|
elseif split_type == "right" then
|
||||||
|
x = x + w * .5
|
||||||
|
w = w * .5
|
||||||
|
elseif split_type == "up" then
|
||||||
|
h = h * .5
|
||||||
|
elseif split_type == "down" then
|
||||||
|
y = y + h * .5
|
||||||
|
h = h * .5
|
||||||
|
end
|
||||||
|
return x, y, w, h
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function RootView:update_drag_overlay()
|
||||||
|
if not (self.dragged_node and self.dragged_node.dragging) then return end
|
||||||
|
local over = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y)
|
||||||
|
if over and not over.locked then
|
||||||
|
local _, _, _, tab_h = over:get_scroll_button_rect(1)
|
||||||
|
local x, y = over.position.x, over.position.y
|
||||||
|
local w, h = over.size.x, over.size.y
|
||||||
|
local split_type = over:get_split_type(self.mouse.x, self.mouse.y)
|
||||||
|
|
||||||
|
if split_type == "tab" and (over ~= self.dragged_node.node or #over.views > 1) then
|
||||||
|
local tab_index, tab_x, tab_y, tab_w, tab_h = over:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y)
|
||||||
|
self:set_drag_overlay(self.drag_overlay_tab,
|
||||||
|
tab_x + (tab_index and 0 or tab_w), tab_y,
|
||||||
|
style.caret_width, tab_h,
|
||||||
|
-- avoid showing tab overlay moving between nodes
|
||||||
|
over ~= self.drag_overlay_tab.last_over)
|
||||||
|
self:set_show_overlay(self.drag_overlay, false)
|
||||||
|
self.drag_overlay_tab.last_over = over
|
||||||
|
else
|
||||||
|
if (over ~= self.dragged_node.node or #over.views > 1) then
|
||||||
|
y = y + tab_h
|
||||||
|
h = h - tab_h
|
||||||
|
x, y, w, h = get_split_sizes(split_type, x, y, w, h)
|
||||||
|
end
|
||||||
|
self:set_drag_overlay(self.drag_overlay, x, y, w, h)
|
||||||
|
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self:set_show_overlay(self.drag_overlay, false)
|
||||||
|
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function RootView:draw_grabbed_tab()
|
||||||
|
local dn = self.dragged_node
|
||||||
|
local _,_, w, h = dn.node:get_tab_rect(dn.idx)
|
||||||
|
local x = self.mouse.x - w / 2
|
||||||
|
local y = self.mouse.y - h / 2
|
||||||
|
local text = dn.node.views[dn.idx] and dn.node.views[dn.idx]:get_name() or ""
|
||||||
|
self.root_node:draw_tab(text, true, true, false, x, y, w, h, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function RootView:draw_drag_overlay(ov)
|
||||||
|
if ov.opacity > 0 then
|
||||||
|
renderer.draw_rect(ov.x, ov.y, ov.w, ov.h, ov.color)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -883,6 +1148,12 @@ function RootView:draw()
|
||||||
local t = table.remove(self.deferred_draws)
|
local t = table.remove(self.deferred_draws)
|
||||||
t.fn(table.unpack(t))
|
t.fn(table.unpack(t))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self:draw_drag_overlay(self.drag_overlay)
|
||||||
|
self:draw_drag_overlay(self.drag_overlay_tab)
|
||||||
|
if self.dragged_node and self.dragged_node.dragging then
|
||||||
|
self:draw_grabbed_tab()
|
||||||
|
end
|
||||||
if core.cursor_change_req then
|
if core.cursor_change_req then
|
||||||
system.set_cursor(core.cursor_change_req)
|
system.set_cursor(core.cursor_change_req)
|
||||||
core.cursor_change_req = nil
|
core.cursor_change_req = nil
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
-- this file is used by lite-xl to setup the Lua environment when starting
|
-- this file is used by lite-xl to setup the Lua environment when starting
|
||||||
VERSION = "2.0-beta1"
|
VERSION = "@PROJECT_VERSION@"
|
||||||
MOD_VERSION = "1"
|
MOD_VERSION = "2"
|
||||||
|
|
||||||
SCALE = tonumber(os.getenv("LITE_SCALE")) or SCALE
|
SCALE = tonumber(os.getenv("LITE_SCALE")) or SCALE
|
||||||
PATHSEP = package.config:sub(1, 1)
|
PATHSEP = package.config:sub(1, 1)
|
||||||
|
@ -20,3 +20,14 @@ package.path = DATADIR .. '/?/init.lua;' .. package.path
|
||||||
package.path = USERDIR .. '/?.lua;' .. package.path
|
package.path = USERDIR .. '/?.lua;' .. package.path
|
||||||
package.path = USERDIR .. '/?/init.lua;' .. package.path
|
package.path = USERDIR .. '/?/init.lua;' .. package.path
|
||||||
|
|
||||||
|
local dynamic_suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so')
|
||||||
|
package.cpath = DATADIR .. '/?.' .. dynamic_suffix .. ";" .. USERDIR .. '/?.' .. dynamic_suffix
|
||||||
|
package.native_plugins = {}
|
||||||
|
package.searchers = { package.searchers[1], package.searchers[2], function(modname)
|
||||||
|
local path = package.searchpath(modname, package.cpath)
|
||||||
|
if not path then return nil end
|
||||||
|
return system.load_native_plugin, path
|
||||||
|
end }
|
||||||
|
|
||||||
|
table.pack = table.pack or pack or function(...) return {...} end
|
||||||
|
table.unpack = table.unpack or unpack
|
||||||
|
|
|
@ -6,6 +6,7 @@ local style = require "core.style"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
local LogView = require "core.logview"
|
local LogView = require "core.logview"
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
|
local Object = require "core.object"
|
||||||
|
|
||||||
|
|
||||||
local StatusView = View:extend()
|
local StatusView = View:extend()
|
||||||
|
@ -70,7 +71,7 @@ local function draw_items(self, items, x, y, draw_fn)
|
||||||
local color = style.text
|
local color = style.text
|
||||||
|
|
||||||
for _, item in ipairs(items) do
|
for _, item in ipairs(items) do
|
||||||
if type(item) == "userdata" then
|
if Object.is(item, renderer.font) then
|
||||||
font = item
|
font = item
|
||||||
elseif type(item) == "table" then
|
elseif type(item) == "table" then
|
||||||
color = item
|
color = item
|
||||||
|
|
|
@ -21,11 +21,11 @@ style.tab_width = common.round(170 * SCALE)
|
||||||
--
|
--
|
||||||
-- On High DPI monitor or non RGB monitor you may consider using antialiasing grayscale instead.
|
-- On High DPI monitor or non RGB monitor you may consider using antialiasing grayscale instead.
|
||||||
-- The antialiasing grayscale with full hinting is interesting for crisp font rendering.
|
-- The antialiasing grayscale with full hinting is interesting for crisp font rendering.
|
||||||
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
|
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 15 * SCALE)
|
||||||
style.big_font = style.font:copy(40 * SCALE)
|
style.big_font = style.font:copy(46 * SCALE)
|
||||||
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 14 * SCALE, {antialiasing="grayscale", hinting="full"})
|
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 16 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||||
style.icon_big_font = style.icon_font:copy(20 * SCALE)
|
style.icon_big_font = style.icon_font:copy(23 * SCALE)
|
||||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
|
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 15 * SCALE)
|
||||||
|
|
||||||
style.background = { common.color "#2e2e32" }
|
style.background = { common.color "#2e2e32" }
|
||||||
style.background2 = { common.color "#252529" }
|
style.background2 = { common.color "#252529" }
|
||||||
|
@ -44,6 +44,8 @@ style.scrollbar2 = { common.color "#4b4b52" }
|
||||||
style.nagbar = { common.color "#FF0000" }
|
style.nagbar = { common.color "#FF0000" }
|
||||||
style.nagbar_text = { common.color "#FFFFFF" }
|
style.nagbar_text = { common.color "#FFFFFF" }
|
||||||
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
|
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
|
||||||
|
style.drag_overlay = { common.color "rgba(255,255,255,0.1)" }
|
||||||
|
style.drag_overlay_tab = { common.color "#93DDFA" }
|
||||||
|
|
||||||
style.syntax = {}
|
style.syntax = {}
|
||||||
style.syntax["normal"] = { common.color "#e1e1e6" }
|
style.syntax["normal"] = { common.color "#e1e1e6" }
|
||||||
|
|
|
@ -155,7 +155,7 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
if count % 2 == 0 then break end
|
if count % 2 == 0 then break end
|
||||||
end
|
end
|
||||||
until not res[1] or not close or not target[3]
|
until not res[1] or not close or not target[3]
|
||||||
return unpack(res)
|
return table.unpack(res)
|
||||||
end
|
end
|
||||||
|
|
||||||
while i <= #text do
|
while i <= #text do
|
||||||
|
|
|
@ -7,6 +7,10 @@ local Object = require "core.object"
|
||||||
|
|
||||||
local View = Object:extend()
|
local View = Object:extend()
|
||||||
|
|
||||||
|
-- context can be "application" or "session". The instance of objects
|
||||||
|
-- with context "session" will be closed when a project session is
|
||||||
|
-- terminated. The context "application" is for functional UI elements.
|
||||||
|
View.context = "application"
|
||||||
|
|
||||||
function View:new()
|
function View:new()
|
||||||
self.position = { x = 0, y = 0 }
|
self.position = { x = 0, y = 0 }
|
||||||
|
@ -98,13 +102,9 @@ function View:on_text_input(text)
|
||||||
-- no-op
|
-- no-op
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function View:on_mouse_wheel(y)
|
function View:on_mouse_wheel(y)
|
||||||
if self.scrollable then
|
|
||||||
self.scroll.to.y = self.scroll.to.y + y * -config.mouse_wheel_scroll
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
function View:get_content_bounds()
|
function View:get_content_bounds()
|
||||||
local x = self.scroll.x
|
local x = self.scroll.x
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
|
@ -8,25 +8,65 @@ local keymap = require "core.keymap"
|
||||||
local translate = require "core.doc.translate"
|
local translate = require "core.doc.translate"
|
||||||
local RootView = require "core.rootview"
|
local RootView = require "core.rootview"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
|
local Doc = require "core.doc"
|
||||||
|
|
||||||
config.plugins.autocomplete = { max_suggestions = 6 }
|
config.plugins.autocomplete = {
|
||||||
|
-- Amount of characters that need to be written for autocomplete
|
||||||
|
min_len = 3,
|
||||||
|
-- The max amount of visible items
|
||||||
|
max_height = 6,
|
||||||
|
-- The max amount of scrollable items
|
||||||
|
max_suggestions = 100,
|
||||||
|
}
|
||||||
|
|
||||||
local autocomplete = {}
|
local autocomplete = {}
|
||||||
autocomplete.map = {}
|
|
||||||
|
|
||||||
|
autocomplete.map = {}
|
||||||
|
autocomplete.map_manually = {}
|
||||||
|
autocomplete.on_close = nil
|
||||||
|
|
||||||
|
-- Flag that indicates if the autocomplete box was manually triggered
|
||||||
|
-- with the autocomplete.complete() function to prevent the suggestions
|
||||||
|
-- from getting cluttered with arbitrary document symbols by using the
|
||||||
|
-- autocomplete.map_manually table.
|
||||||
|
local triggered_manually = false
|
||||||
|
|
||||||
local mt = { __tostring = function(t) return t.text end }
|
local mt = { __tostring = function(t) return t.text end }
|
||||||
|
|
||||||
function autocomplete.add(t)
|
function autocomplete.add(t, triggered_manually)
|
||||||
local items = {}
|
local items = {}
|
||||||
for text, info in pairs(t.items) do
|
for text, info in pairs(t.items) do
|
||||||
info = (type(info) == "string") and info
|
if type(info) == "table" then
|
||||||
table.insert(items, setmetatable({ text = text, info = info }, mt))
|
table.insert(
|
||||||
|
items,
|
||||||
|
setmetatable(
|
||||||
|
{
|
||||||
|
text = text,
|
||||||
|
info = info.info,
|
||||||
|
desc = info.desc, -- Description shown on item selected
|
||||||
|
cb = info.cb, -- A callback called once when item is selected
|
||||||
|
data = info.data -- Optional data that can be used on cb
|
||||||
|
},
|
||||||
|
mt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
info = (type(info) == "string") and info
|
||||||
|
table.insert(items, setmetatable({ text = text, info = info }, mt))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not triggered_manually then
|
||||||
|
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
||||||
|
else
|
||||||
|
autocomplete.map_manually[t.name] = { files = t.files or ".*", items = items }
|
||||||
end
|
end
|
||||||
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local max_symbols = config.max_symbols or 2000
|
--
|
||||||
|
-- Thread that scans open document symbols and cache them
|
||||||
|
--
|
||||||
|
local max_symbols = config.max_symbols
|
||||||
|
|
||||||
core.add_thread(function()
|
core.add_thread(function()
|
||||||
local cache = setmetatable({}, { __mode = "k" })
|
local cache = setmetatable({}, { __mode = "k" })
|
||||||
|
@ -109,16 +149,39 @@ local last_line, last_col
|
||||||
local function reset_suggestions()
|
local function reset_suggestions()
|
||||||
suggestions_idx = 1
|
suggestions_idx = 1
|
||||||
suggestions = {}
|
suggestions = {}
|
||||||
|
|
||||||
|
triggered_manually = false
|
||||||
|
|
||||||
|
local doc = core.active_view.doc
|
||||||
|
if autocomplete.on_close then
|
||||||
|
autocomplete.on_close(doc, suggestions[suggestions_idx])
|
||||||
|
autocomplete.on_close = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function in_table(value, table_array)
|
||||||
|
for i, element in pairs(table_array) do
|
||||||
|
if element == value then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local function update_suggestions()
|
local function update_suggestions()
|
||||||
local doc = core.active_view.doc
|
local doc = core.active_view.doc
|
||||||
local filename = doc and doc.filename or ""
|
local filename = doc and doc.filename or ""
|
||||||
|
|
||||||
|
local map = autocomplete.map
|
||||||
|
|
||||||
|
if triggered_manually then
|
||||||
|
map = autocomplete.map_manually
|
||||||
|
end
|
||||||
|
|
||||||
-- get all relevant suggestions for given filename
|
-- get all relevant suggestions for given filename
|
||||||
local items = {}
|
local items = {}
|
||||||
for _, v in pairs(autocomplete.map) do
|
for _, v in pairs(map) do
|
||||||
if common.match_pattern(filename, v.files) then
|
if common.match_pattern(filename, v.files) then
|
||||||
for _, item in pairs(v.items) do
|
for _, item in pairs(v.items) do
|
||||||
table.insert(items, item)
|
table.insert(items, item)
|
||||||
|
@ -138,7 +201,6 @@ local function update_suggestions()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_partial_symbol()
|
local function get_partial_symbol()
|
||||||
local doc = core.active_view.doc
|
local doc = core.active_view.doc
|
||||||
local line2, col2 = doc:get_selection()
|
local line2, col2 = doc:get_selection()
|
||||||
|
@ -146,14 +208,12 @@ local function get_partial_symbol()
|
||||||
return doc:get_text(line1, col1, line2, col2)
|
return doc:get_text(line1, col1, line2, col2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_active_view()
|
local function get_active_view()
|
||||||
if getmetatable(core.active_view) == DocView then
|
if getmetatable(core.active_view) == DocView then
|
||||||
return core.active_view
|
return core.active_view
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_suggestions_rect(av)
|
local function get_suggestions_rect(av)
|
||||||
if #suggestions == 0 then
|
if #suggestions == 0 then
|
||||||
return 0, 0, 0, 0
|
return 0, 0, 0, 0
|
||||||
|
@ -175,15 +235,67 @@ local function get_suggestions_rect(av)
|
||||||
max_width = math.max(max_width, w)
|
max_width = math.max(max_width, w)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local ah = config.plugins.autocomplete.max_height
|
||||||
|
|
||||||
|
local max_items = #suggestions
|
||||||
|
if max_items > ah then
|
||||||
|
max_items = ah
|
||||||
|
end
|
||||||
|
|
||||||
|
-- additional line to display total items
|
||||||
|
max_items = max_items + 1
|
||||||
|
|
||||||
|
if max_width < 150 then
|
||||||
|
max_width = 150
|
||||||
|
end
|
||||||
|
|
||||||
return
|
return
|
||||||
x - style.padding.x,
|
x - style.padding.x,
|
||||||
y - style.padding.y,
|
y - style.padding.y,
|
||||||
max_width + style.padding.x * 2,
|
max_width + style.padding.x * 2,
|
||||||
#suggestions * (th + style.padding.y) + style.padding.y
|
max_items * (th + style.padding.y) + style.padding.y
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function draw_description_box(text, av, sx, sy, sw, sh)
|
||||||
|
local width = 0
|
||||||
|
|
||||||
|
local lines = {}
|
||||||
|
for line in string.gmatch(text.."\n", "(.-)\n") do
|
||||||
|
width = math.max(width, style.font:get_width(line))
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
|
||||||
|
local height = #lines * style.font:get_height()
|
||||||
|
|
||||||
|
-- draw background rect
|
||||||
|
renderer.draw_rect(
|
||||||
|
sx + sw + style.padding.x / 4,
|
||||||
|
sy,
|
||||||
|
width + style.padding.x * 2,
|
||||||
|
height + style.padding.y * 2,
|
||||||
|
style.background3
|
||||||
|
)
|
||||||
|
|
||||||
|
-- draw text
|
||||||
|
local lh = style.font:get_height()
|
||||||
|
local y = sy + style.padding.y
|
||||||
|
local x = sx + sw + style.padding.x / 4
|
||||||
|
|
||||||
|
for _, line in pairs(lines) do
|
||||||
|
common.draw_text(
|
||||||
|
style.font, style.text, line, "left", x + style.padding.x, y, width, lh
|
||||||
|
)
|
||||||
|
y = y + lh
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function draw_suggestions_box(av)
|
local function draw_suggestions_box(av)
|
||||||
|
if #suggestions <= 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local ah = config.plugins.autocomplete.max_height
|
||||||
|
|
||||||
-- draw background rect
|
-- draw background rect
|
||||||
local rx, ry, rw, rh = get_suggestions_rect(av)
|
local rx, ry, rw, rh = get_suggestions_rect(av)
|
||||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||||
|
@ -192,7 +304,14 @@ local function draw_suggestions_box(av)
|
||||||
local font = av:get_font()
|
local font = av:get_font()
|
||||||
local lh = font:get_height() + style.padding.y
|
local lh = font:get_height() + style.padding.y
|
||||||
local y = ry + style.padding.y / 2
|
local y = ry + style.padding.y / 2
|
||||||
for i, s in ipairs(suggestions) do
|
local show_count = #suggestions <= ah and #suggestions or ah
|
||||||
|
local start_index = suggestions_idx > ah and (suggestions_idx-(ah-1)) or 1
|
||||||
|
|
||||||
|
for i=start_index, start_index+show_count-1, 1 do
|
||||||
|
if not suggestions[i] then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
local s = suggestions[i]
|
||||||
local color = (i == suggestions_idx) and style.accent or style.text
|
local color = (i == suggestions_idx) and style.accent or style.text
|
||||||
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
||||||
if s.info then
|
if s.info then
|
||||||
|
@ -200,26 +319,55 @@ local function draw_suggestions_box(av)
|
||||||
common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh)
|
common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh)
|
||||||
end
|
end
|
||||||
y = y + lh
|
y = y + lh
|
||||||
|
if suggestions_idx == i then
|
||||||
|
if s.cb then
|
||||||
|
s.cb(suggestions_idx, s)
|
||||||
|
s.cb = nil
|
||||||
|
s.data = nil
|
||||||
|
end
|
||||||
|
if s.desc and #s.desc > 0 then
|
||||||
|
draw_description_box(s.desc, av, rx, ry, rw, rh)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
renderer.draw_rect(rx, y, rw, 2, style.caret)
|
||||||
|
renderer.draw_rect(rx, y+2, rw, lh, style.background)
|
||||||
|
common.draw_text(
|
||||||
|
style.font,
|
||||||
|
style.accent,
|
||||||
|
"Items",
|
||||||
|
"left",
|
||||||
|
rx + style.padding.x, y, rw, lh
|
||||||
|
)
|
||||||
|
common.draw_text(
|
||||||
|
style.font,
|
||||||
|
style.accent,
|
||||||
|
tostring(suggestions_idx) .. "/" .. tostring(#suggestions),
|
||||||
|
"right",
|
||||||
|
rx, y, rw - style.padding.x, lh
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function show_autocomplete()
|
||||||
-- patch event logic into RootView
|
|
||||||
local on_text_input = RootView.on_text_input
|
|
||||||
local update = RootView.update
|
|
||||||
local draw = RootView.draw
|
|
||||||
|
|
||||||
|
|
||||||
RootView.on_text_input = function(...)
|
|
||||||
on_text_input(...)
|
|
||||||
|
|
||||||
local av = get_active_view()
|
local av = get_active_view()
|
||||||
if av then
|
if av then
|
||||||
-- update partial symbol and suggestions
|
-- update partial symbol and suggestions
|
||||||
partial = get_partial_symbol()
|
partial = get_partial_symbol()
|
||||||
if #partial >= 3 then
|
|
||||||
|
if #partial >= config.plugins.autocomplete.min_len or triggered_manually then
|
||||||
update_suggestions()
|
update_suggestions()
|
||||||
last_line, last_col = av.doc:get_selection()
|
|
||||||
|
if not triggered_manually then
|
||||||
|
last_line, last_col = av.doc:get_selection()
|
||||||
|
else
|
||||||
|
local line, col = av.doc:get_selection()
|
||||||
|
local char = av.doc:get_char(line, col-1, line, col-1)
|
||||||
|
|
||||||
|
if char:match("%s") or (char:match("%p") and col ~= last_col) then
|
||||||
|
reset_suggestions()
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
reset_suggestions()
|
reset_suggestions()
|
||||||
end
|
end
|
||||||
|
@ -233,6 +381,30 @@ RootView.on_text_input = function(...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Patch event logic into RootView and Doc
|
||||||
|
--
|
||||||
|
local on_text_input = RootView.on_text_input
|
||||||
|
local on_text_remove = Doc.remove
|
||||||
|
local update = RootView.update
|
||||||
|
local draw = RootView.draw
|
||||||
|
|
||||||
|
RootView.on_text_input = function(...)
|
||||||
|
on_text_input(...)
|
||||||
|
show_autocomplete()
|
||||||
|
end
|
||||||
|
|
||||||
|
Doc.remove = function(self, line1, col1, line2, col2)
|
||||||
|
on_text_remove(self, line1, col1, line2, col2)
|
||||||
|
|
||||||
|
if triggered_manually and line1 == line2 then
|
||||||
|
if last_col >= col1 then
|
||||||
|
reset_suggestions()
|
||||||
|
else
|
||||||
|
show_autocomplete()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
RootView.update = function(...)
|
RootView.update = function(...)
|
||||||
update(...)
|
update(...)
|
||||||
|
@ -241,13 +413,19 @@ RootView.update = function(...)
|
||||||
if av then
|
if av then
|
||||||
-- reset suggestions if caret was moved
|
-- reset suggestions if caret was moved
|
||||||
local line, col = av.doc:get_selection()
|
local line, col = av.doc:get_selection()
|
||||||
if line ~= last_line or col ~= last_col then
|
|
||||||
reset_suggestions()
|
if not triggered_manually then
|
||||||
|
if line ~= last_line or col ~= last_col then
|
||||||
|
reset_suggestions()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if line ~= last_line or col < last_col then
|
||||||
|
reset_suggestions()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
RootView.draw = function(...)
|
RootView.draw = function(...)
|
||||||
draw(...)
|
draw(...)
|
||||||
|
|
||||||
|
@ -258,12 +436,53 @@ RootView.draw = function(...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Public functions
|
||||||
|
--
|
||||||
|
function autocomplete.open(on_close)
|
||||||
|
triggered_manually = true
|
||||||
|
|
||||||
|
if on_close then
|
||||||
|
autocomplete.on_close = on_close
|
||||||
|
end
|
||||||
|
|
||||||
|
local av = get_active_view()
|
||||||
|
last_line, last_col = av.doc:get_selection()
|
||||||
|
update_suggestions()
|
||||||
|
end
|
||||||
|
|
||||||
|
function autocomplete.close()
|
||||||
|
reset_suggestions()
|
||||||
|
end
|
||||||
|
|
||||||
|
function autocomplete.is_open()
|
||||||
|
return #suggestions > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function autocomplete.complete(completions, on_close)
|
||||||
|
reset_suggestions()
|
||||||
|
|
||||||
|
autocomplete.map_manually = {}
|
||||||
|
autocomplete.add(completions, true)
|
||||||
|
|
||||||
|
autocomplete.open(on_close)
|
||||||
|
end
|
||||||
|
|
||||||
|
function autocomplete.can_complete()
|
||||||
|
if #partial >= config.plugins.autocomplete.min_len then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Commands
|
||||||
|
--
|
||||||
local function predicate()
|
local function predicate()
|
||||||
return get_active_view() and #suggestions > 0
|
return get_active_view() and #suggestions > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
command.add(predicate, {
|
command.add(predicate, {
|
||||||
["autocomplete:complete"] = function()
|
["autocomplete:complete"] = function()
|
||||||
local doc = core.active_view.doc
|
local doc = core.active_view.doc
|
||||||
|
@ -283,12 +502,19 @@ command.add(predicate, {
|
||||||
suggestions_idx = math.min(suggestions_idx + 1, #suggestions)
|
suggestions_idx = math.min(suggestions_idx + 1, #suggestions)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
["autocomplete:cycle"] = function()
|
||||||
|
local newidx = suggestions_idx + 1
|
||||||
|
suggestions_idx = newidx > #suggestions and 1 or newidx
|
||||||
|
end,
|
||||||
|
|
||||||
["autocomplete:cancel"] = function()
|
["autocomplete:cancel"] = function()
|
||||||
reset_suggestions()
|
reset_suggestions()
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Keymaps
|
||||||
|
--
|
||||||
keymap.add {
|
keymap.add {
|
||||||
["tab"] = "autocomplete:complete",
|
["tab"] = "autocomplete:complete",
|
||||||
["up"] = "autocomplete:previous",
|
["up"] = "autocomplete:previous",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local Doc = require "core.doc"
|
local Doc = require "core.doc"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
|
@ -42,6 +42,24 @@ keymap.add {
|
||||||
["menu"] = "context:show"
|
["menu"] = "context:show"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local function copy_log()
|
||||||
|
local item = core.active_view.hovered_item
|
||||||
|
if item then
|
||||||
|
system.set_clipboard(core.get_log(item))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function open_as_doc()
|
||||||
|
local doc = core.open_doc("logs.txt")
|
||||||
|
core.root_view:open_doc(doc)
|
||||||
|
doc:insert(1, 1, core.get_log())
|
||||||
|
end
|
||||||
|
|
||||||
|
menu:register("core.logview", {
|
||||||
|
{ text = "Copy entry", command = copy_log },
|
||||||
|
{ text = "Open as file", command = open_as_doc }
|
||||||
|
})
|
||||||
|
|
||||||
if require("plugins.scale") then
|
if require("plugins.scale") then
|
||||||
menu:register("core.docview", {
|
menu:register("core.docview", {
|
||||||
{ text = "Font +", command = "scale:increase" },
|
{ text = "Font +", command = "scale:increase" },
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
|
@ -102,6 +102,11 @@ end
|
||||||
local function update_cache(doc)
|
local function update_cache(doc)
|
||||||
local type, size, score = detect_indent_stat(doc)
|
local type, size, score = detect_indent_stat(doc)
|
||||||
local score_threshold = 4
|
local score_threshold = 4
|
||||||
|
if score < score_threshold then
|
||||||
|
-- use default values
|
||||||
|
type = config.tab_type
|
||||||
|
size = config.indent_size
|
||||||
|
end
|
||||||
cache[doc] = { type = type, size = size, confirmed = (score >= score_threshold) }
|
cache[doc] = { type = type, size = size, confirmed = (score >= score_threshold) }
|
||||||
doc.indent_info = cache[doc]
|
doc.indent_info = cache[doc]
|
||||||
end
|
end
|
||||||
|
@ -111,20 +116,14 @@ local new = Doc.new
|
||||||
function Doc:new(...)
|
function Doc:new(...)
|
||||||
new(self, ...)
|
new(self, ...)
|
||||||
update_cache(self)
|
update_cache(self)
|
||||||
if not cache[self].confirmed then
|
|
||||||
core.add_thread(function ()
|
|
||||||
while not cache[self].confirmed do
|
|
||||||
update_cache(self)
|
|
||||||
coroutine.yield(1)
|
|
||||||
end
|
|
||||||
end, self)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local clean = Doc.clean
|
local clean = Doc.clean
|
||||||
function Doc:clean(...)
|
function Doc:clean(...)
|
||||||
clean(self, ...)
|
clean(self, ...)
|
||||||
update_cache(self)
|
if not cache[self].confirmed then
|
||||||
|
update_cache(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -152,3 +151,78 @@ function DocView:draw(...)
|
||||||
return with_indent_override(self.doc, draw, self, ...)
|
return with_indent_override(self.doc, draw, self, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function set_indent_type(doc, type)
|
||||||
|
cache[doc] = {type = type,
|
||||||
|
size = cache[doc].value or config.indent_size,
|
||||||
|
confirmed = true}
|
||||||
|
doc.indent_info = cache[doc]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function set_indent_type_command()
|
||||||
|
core.command_view:enter(
|
||||||
|
"Specify indent style for this file",
|
||||||
|
function(value) -- submit
|
||||||
|
local doc = core.active_view.doc
|
||||||
|
value = value:lower()
|
||||||
|
set_indent_type(doc, value == "tabs" and "hard" or "soft")
|
||||||
|
end,
|
||||||
|
function(text) -- suggest
|
||||||
|
return common.fuzzy_match({"tabs", "spaces"}, text)
|
||||||
|
end,
|
||||||
|
nil, -- cancel
|
||||||
|
function(text) -- validate
|
||||||
|
local t = text:lower()
|
||||||
|
return t == "tabs" or t == "spaces"
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function set_indent_size(doc, size)
|
||||||
|
cache[doc] = {type = cache[doc].type or config.tab_type,
|
||||||
|
size = size,
|
||||||
|
confirmed = true}
|
||||||
|
doc.indent_info = cache[doc]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function set_indent_size_command()
|
||||||
|
core.command_view:enter(
|
||||||
|
"Specify indent size for current file",
|
||||||
|
function(value) -- submit
|
||||||
|
local value = math.floor(tonumber(value))
|
||||||
|
local doc = core.active_view.doc
|
||||||
|
set_indent_size(doc, value)
|
||||||
|
end,
|
||||||
|
nil, -- suggest
|
||||||
|
nil, -- cancel
|
||||||
|
function(value) -- validate
|
||||||
|
local value = tonumber(value)
|
||||||
|
return value ~= nil and value >= 1
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
command.add("core.docview", {
|
||||||
|
["indent:set-file-indent-type"] = set_indent_type_command,
|
||||||
|
["indent:set-file-indent-size"] = set_indent_size_command
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
command.add(function()
|
||||||
|
return core.active_view:is(DocView)
|
||||||
|
and cache[core.active_view.doc]
|
||||||
|
and cache[core.active_view.doc].type == "soft"
|
||||||
|
end, {
|
||||||
|
["indent:switch-file-to-tabs-indentation"] = function() set_indent_type(core.active_view.doc, "hard") end
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
command.add(function()
|
||||||
|
return core.active_view:is(DocView)
|
||||||
|
and cache[core.active_view.doc]
|
||||||
|
and cache[core.active_view.doc].type == "hard"
|
||||||
|
end, {
|
||||||
|
["indent:switch-file-to-spaces-indentation"] = function() set_indent_type(core.active_view.doc, "soft") end
|
||||||
|
})
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
|
|
||||||
|
local style = require "core.style"
|
||||||
|
local DocView = require "core.docview"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
local draw_line_text = DocView.draw_line_text
|
||||||
|
|
||||||
|
function DocView:draw_line_text(idx, x, y)
|
||||||
|
local font = (self:get_font() or style.syntax_fonts["comment"])
|
||||||
|
local color = style.syntax.comment
|
||||||
|
local ty = y + self:get_line_text_y_offset()
|
||||||
|
local tx
|
||||||
|
local text, offset, s, e = self.doc.lines[idx], 1
|
||||||
|
local x1, _, x2, _ = self:get_content_bounds()
|
||||||
|
local _offset = self:get_x_offset_col(idx, x1)
|
||||||
|
offset = _offset
|
||||||
|
while true do
|
||||||
|
s, e = text:find(" +", offset)
|
||||||
|
if not s then break end
|
||||||
|
tx = self:get_col_x_offset(idx, s) + x
|
||||||
|
renderer.draw_text(font, string.rep("·", e - s + 1), tx, ty, color)
|
||||||
|
if tx > x + x2 then break end
|
||||||
|
offset = e + 1
|
||||||
|
end
|
||||||
|
offset = _offset
|
||||||
|
while true do
|
||||||
|
s, e = text:find("\t", offset)
|
||||||
|
if not s then break end
|
||||||
|
tx = self:get_col_x_offset(idx, s) + x
|
||||||
|
renderer.draw_text(font, "»", tx, ty, color)
|
||||||
|
if tx > x + x2 then break end
|
||||||
|
offset = e + 1
|
||||||
|
end
|
||||||
|
draw_line_text(self, idx, x, y)
|
||||||
|
end
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
|
@ -55,6 +55,17 @@ syntax.add {
|
||||||
["true"] = "literal",
|
["true"] = "literal",
|
||||||
["false"] = "literal",
|
["false"] = "literal",
|
||||||
["NULL"] = "literal",
|
["NULL"] = "literal",
|
||||||
|
["#include"] = "keyword",
|
||||||
|
["#if"] = "keyword",
|
||||||
|
["#ifdef"] = "keyword",
|
||||||
|
["#ifndef"] = "keyword",
|
||||||
|
["#else"] = "keyword",
|
||||||
|
["#elseif"] = "keyword",
|
||||||
|
["#endif"] = "keyword",
|
||||||
|
["#define"] = "keyword",
|
||||||
|
["#warning"] = "keyword",
|
||||||
|
["#error"] = "keyword",
|
||||||
|
["#pragma"] = "keyword",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
pcall(require, "plugins.language_c")
|
pcall(require, "plugins.language_c")
|
||||||
|
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
|
|
|
@ -1,22 +1,41 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
files = { "%.md$", "%.markdown$" },
|
files = { "%.md$", "%.markdown$" },
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = "\\.", type = "normal" },
|
{ pattern = "\\.", type = "normal" },
|
||||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||||
{ pattern = { "```", "```" }, type = "string" },
|
{ pattern = { "```c", "```" }, type = "string", syntax = ".c" },
|
||||||
{ pattern = { "``", "``", "\\" }, type = "string" },
|
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
|
||||||
{ pattern = { "`", "`", "\\" }, type = "string" },
|
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" },
|
||||||
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
|
||||||
{ pattern = "%-%-%-+", type = "comment" },
|
{ pattern = { "```perl", "```" }, type = "string", syntax = ".pl" },
|
||||||
{ pattern = "%*%s+", type = "operator" },
|
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
|
||||||
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
{ pattern = { "```javascript", "```" }, type = "string", syntax = ".js" },
|
||||||
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
{ pattern = { "```html", "```" }, type = "string", syntax = ".html" },
|
||||||
{ pattern = "#.-\n", type = "keyword" },
|
{ pattern = { "```xml", "```" }, type = "string", syntax = ".xml" },
|
||||||
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
{ pattern = { "```css", "```" }, type = "string", syntax = ".css" },
|
||||||
{ pattern = "https?://%S+", type = "function" },
|
{ pattern = { "```lua", "```" }, type = "string", syntax = ".lua" },
|
||||||
|
{ pattern = { "```bash", "```" }, type = "string", syntax = ".sh" },
|
||||||
|
{ pattern = { "```java", "```" }, type = "string", syntax = ".java" },
|
||||||
|
{ pattern = { "```c#", "```" }, type = "string", syntax = ".cs" },
|
||||||
|
{ pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" },
|
||||||
|
{ pattern = { "```d", "```" }, type = "string", syntax = ".d" },
|
||||||
|
{ pattern = { "```glsl", "```" }, type = "string", syntax = ".glsl" },
|
||||||
|
{ pattern = { "```", "```" }, type = "string" },
|
||||||
|
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||||
|
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||||
|
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||||
|
{ pattern = "%-%-%-+", type = "comment" },
|
||||||
|
{ pattern = "%*%s+", type = "operator" },
|
||||||
|
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||||
|
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||||
|
{ pattern = "#.-\n", type = "keyword" },
|
||||||
|
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||||
|
{ pattern = "https?://%S+", type = "function" },
|
||||||
},
|
},
|
||||||
symbols = { },
|
symbols = { },
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
files = { "%.py$", "%.pyw$" },
|
files = { "%.py$", "%.pyw$", "%.rpy$" },
|
||||||
headers = "^#!.*[ /]python",
|
headers = "^#!.*[ /]python",
|
||||||
comment = "#",
|
comment = "#",
|
||||||
patterns = {
|
patterns = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local style = require "core.style"
|
local style = require "core.style"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
|
@ -6,9 +6,7 @@ local DocView = require "core.docview"
|
||||||
local draw_overlay = DocView.draw_overlay
|
local draw_overlay = DocView.draw_overlay
|
||||||
|
|
||||||
function DocView:draw_overlay(...)
|
function DocView:draw_overlay(...)
|
||||||
local ns = self:get_font():get_width_subpixel("n") * config.line_limit
|
local offset = self:get_font():get_width("n") * config.line_limit
|
||||||
local ss = self:get_font():subpixel_scale()
|
|
||||||
local offset = ns / ss
|
|
||||||
local x = self:get_line_screen_position(1) + offset
|
local x = self:get_line_screen_position(1) + offset
|
||||||
local y = self.position.y
|
local y = self.position.y
|
||||||
local w = math.ceil(SCALE * 1)
|
local w = math.ceil(SCALE * 1)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
|
@ -9,6 +9,7 @@ local View = require "core.view"
|
||||||
|
|
||||||
local ResultsView = View:extend()
|
local ResultsView = View:extend()
|
||||||
|
|
||||||
|
ResultsView.context = "session"
|
||||||
|
|
||||||
function ResultsView:new(text, fn)
|
function ResultsView:new(text, fn)
|
||||||
ResultsView.super.new(self)
|
ResultsView.super.new(self)
|
||||||
|
@ -91,7 +92,7 @@ end
|
||||||
function ResultsView:on_mouse_pressed(...)
|
function ResultsView:on_mouse_pressed(...)
|
||||||
local caught = ResultsView.super.on_mouse_pressed(self, ...)
|
local caught = ResultsView.super.on_mouse_pressed(self, ...)
|
||||||
if not caught then
|
if not caught then
|
||||||
self:open_selected_result()
|
return self:open_selected_result()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -107,6 +108,7 @@ function ResultsView:open_selected_result()
|
||||||
dv.doc:set_selection(res.line, res.col)
|
dv.doc:set_selection(res.line, res.col)
|
||||||
dv:scroll_to_line(res.line, false, true)
|
dv:scroll_to_line(res.line, false, true)
|
||||||
end)
|
end)
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,7 +172,7 @@ function ResultsView:draw()
|
||||||
local ox, oy = self:get_content_offset()
|
local ox, oy = self:get_content_offset()
|
||||||
local x, y = ox + style.padding.x, oy + style.padding.y
|
local x, y = ox + style.padding.x, oy + style.padding.y
|
||||||
local files_number = core.project_files_number()
|
local files_number = core.project_files_number()
|
||||||
local per = files_number and self.last_file_idx / files_number or 1
|
local per = common.clamp(files_number and self.last_file_idx / files_number or 1, 0, 1)
|
||||||
local text
|
local text
|
||||||
if self.searching then
|
if self.searching then
|
||||||
if files_number then
|
if files_number then
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
@ -13,7 +13,6 @@ config.plugins.scale = {
|
||||||
use_mousewheel = true
|
use_mousewheel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
local scale_level = 0
|
|
||||||
local scale_steps = 0.05
|
local scale_steps = 0.05
|
||||||
|
|
||||||
local current_scale = SCALE
|
local current_scale = SCALE
|
||||||
|
@ -34,9 +33,6 @@ local function set_scale(scale)
|
||||||
local s = scale / current_scale
|
local s = scale / current_scale
|
||||||
current_scale = scale
|
current_scale = scale
|
||||||
|
|
||||||
-- we set scale_level in case this was called by user
|
|
||||||
scale_level = (scale - default_scale) / scale_steps
|
|
||||||
|
|
||||||
if config.plugins.scale.mode == "ui" then
|
if config.plugins.scale.mode == "ui" then
|
||||||
SCALE = scale
|
SCALE = scale
|
||||||
|
|
||||||
|
@ -48,10 +44,14 @@ local function set_scale(scale)
|
||||||
style.tab_width = style.tab_width * s
|
style.tab_width = style.tab_width * s
|
||||||
|
|
||||||
for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do
|
for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do
|
||||||
renderer.font.set_size(style[name], s * style[name]:get_size())
|
style[name] = renderer.font.copy(style[name], s * style[name]:get_size())
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
renderer.font.set_size(style.code_font, s * style.code_font:get_size())
|
style.code_font = renderer.font.copy(style.code_font, s * style.code_font:get_size())
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, font in pairs(style.syntax_fonts) do
|
||||||
|
renderer.font.set_size(font, s * font:get_size())
|
||||||
end
|
end
|
||||||
|
|
||||||
-- restore scroll positions
|
-- restore scroll positions
|
||||||
|
@ -67,30 +67,16 @@ local function get_scale()
|
||||||
return current_scale
|
return current_scale
|
||||||
end
|
end
|
||||||
|
|
||||||
local on_mouse_wheel = RootView.on_mouse_wheel
|
|
||||||
|
|
||||||
function RootView:on_mouse_wheel(d, ...)
|
|
||||||
if keymap.modkeys["ctrl"] and config.plugins.scale.use_mousewheel then
|
|
||||||
if d < 0 then command.perform "scale:decrease" end
|
|
||||||
if d > 0 then command.perform "scale:increase" end
|
|
||||||
else
|
|
||||||
return on_mouse_wheel(self, d, ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function res_scale()
|
local function res_scale()
|
||||||
scale_level = 0
|
set_scale(default_scale)
|
||||||
set_scale(default_scale)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function inc_scale()
|
local function inc_scale()
|
||||||
scale_level = scale_level + 1
|
set_scale(current_scale + scale_steps)
|
||||||
set_scale(default_scale + scale_level * scale_steps)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function dec_scale()
|
local function dec_scale()
|
||||||
scale_level = scale_level - 1
|
set_scale(current_scale - scale_steps)
|
||||||
set_scale(default_scale + scale_level * scale_steps)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,6 +90,8 @@ keymap.add {
|
||||||
["ctrl+0"] = "scale:reset",
|
["ctrl+0"] = "scale:reset",
|
||||||
["ctrl+-"] = "scale:decrease",
|
["ctrl+-"] = "scale:decrease",
|
||||||
["ctrl+="] = "scale:increase",
|
["ctrl+="] = "scale:increase",
|
||||||
|
["ctrl+wheelup"] = "scale:increase",
|
||||||
|
["ctrl+wheeldown"] = "scale:decrease"
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local translate = require "core.doc.translate"
|
local translate = require "core.doc.translate"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
@ -243,7 +243,7 @@ function TreeView:on_mouse_pressed(button, x, y, clicks)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
core.try(function()
|
core.try(function()
|
||||||
local doc_filename = common.relative_path(core.project_dir, hovered_item.abs_filename)
|
local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename)
|
||||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -437,15 +437,31 @@ menu:register(
|
||||||
command.add(nil, {
|
command.add(nil, {
|
||||||
["treeview:toggle"] = function()
|
["treeview:toggle"] = function()
|
||||||
view.visible = not view.visible
|
view.visible = not view.visible
|
||||||
end,
|
end})
|
||||||
|
|
||||||
|
|
||||||
|
command.add(function() return view.hovered_item ~= nil end, {
|
||||||
["treeview:rename"] = function()
|
["treeview:rename"] = function()
|
||||||
local old_filename = view.hovered_item.filename
|
local old_filename = view.hovered_item.filename
|
||||||
|
local old_abs_filename = view.hovered_item.abs_filename
|
||||||
core.command_view:set_text(old_filename)
|
core.command_view:set_text(old_filename)
|
||||||
core.command_view:enter("Rename", function(filename)
|
core.command_view:enter("Rename", function(filename)
|
||||||
os.rename(old_filename, filename)
|
filename = core.normalize_to_project_dir(filename)
|
||||||
|
local abs_filename = core.project_absolute_path(filename)
|
||||||
|
local res, err = os.rename(old_abs_filename, abs_filename)
|
||||||
|
if res then -- successfully renamed
|
||||||
|
for _, doc in ipairs(core.docs) do
|
||||||
|
if doc.abs_filename and old_abs_filename == doc.abs_filename then
|
||||||
|
doc:set_filename(filename, abs_filename) -- make doc point to the new filename
|
||||||
|
doc:reset_syntax()
|
||||||
|
break -- only first needed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||||
|
else
|
||||||
|
core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
|
||||||
|
end
|
||||||
core.reschedule_project_scan()
|
core.reschedule_project_scan()
|
||||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
|
||||||
end, common.path_suggest)
|
end, common.path_suggest)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -521,7 +537,7 @@ command.add(nil, {
|
||||||
local hovered_item = view.hovered_item
|
local hovered_item = view.hovered_item
|
||||||
|
|
||||||
if PLATFORM == "Windows" then
|
if PLATFORM == "Windows" then
|
||||||
system.exec("start " .. hovered_item.abs_filename)
|
system.exec(string.format("start \"\" %q", hovered_item.abs_filename))
|
||||||
elseif string.find(PLATFORM, "Mac") then
|
elseif string.find(PLATFORM, "Mac") then
|
||||||
system.exec(string.format("open %q", hovered_item.abs_filename))
|
system.exec(string.format("open %q", hovered_item.abs_filename))
|
||||||
elseif PLATFORM == "Linux" then
|
elseif PLATFORM == "Linux" then
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local Doc = require "core.doc"
|
local Doc = require "core.doc"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
-- mod-version:2 -- lite-xl 2.0
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
|
|
|
@ -19,6 +19,9 @@ renderer.color = {}
|
||||||
---@class renderer.fontoptions
|
---@class renderer.fontoptions
|
||||||
---@field public antialiasing "'grayscale'" | "'subpixel'"
|
---@field public antialiasing "'grayscale'" | "'subpixel'"
|
||||||
---@field public hinting "'slight'" | "'none'" | '"full"'
|
---@field public hinting "'slight'" | "'none'" | '"full"'
|
||||||
|
-- @field public bold boolean
|
||||||
|
-- @field public italic boolean
|
||||||
|
-- @field public underline boolean
|
||||||
renderer.fontoptions = {}
|
renderer.fontoptions = {}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,198 +0,0 @@
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
// Anti-Grain Geometry - Version 2.4
|
|
||||||
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
|
|
||||||
//
|
|
||||||
// Permission to copy, use, modify, sell and distribute this software
|
|
||||||
// is granted provided this copyright notice appears in all copies.
|
|
||||||
// This software is provided "as is" without express or implied
|
|
||||||
// warranty, and with no claim as to its suitability for any purpose.
|
|
||||||
//
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
// Contact: mcseem@antigrain.com
|
|
||||||
// mcseemagg@yahoo.com
|
|
||||||
// http://www.antigrain.com
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
//
|
|
||||||
// See implementation agg_font_freetype.cpp
|
|
||||||
//
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#ifndef AGG_FONT_FREETYPE_INCLUDED
|
|
||||||
#define AGG_FONT_FREETYPE_INCLUDED
|
|
||||||
|
|
||||||
#include <ft2build.h>
|
|
||||||
#include FT_FREETYPE_H
|
|
||||||
|
|
||||||
|
|
||||||
#include "agg_scanline_storage_aa.h"
|
|
||||||
#include "agg_scanline_storage_bin.h"
|
|
||||||
#include "agg_scanline_u.h"
|
|
||||||
#include "agg_scanline_bin.h"
|
|
||||||
#include "agg_path_storage_integer.h"
|
|
||||||
#include "agg_rasterizer_scanline_aa.h"
|
|
||||||
#include "agg_conv_curve.h"
|
|
||||||
#include "agg_font_cache_manager.h"
|
|
||||||
#include "agg_trans_affine.h"
|
|
||||||
|
|
||||||
namespace agg
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------font_engine_freetype_base
|
|
||||||
class font_engine_freetype_base
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
typedef serialized_scanlines_adaptor_aa<int8u> gray8_adaptor_type;
|
|
||||||
typedef serialized_scanlines_adaptor_bin mono_adaptor_type;
|
|
||||||
typedef scanline_storage_aa8 scanlines_aa_type;
|
|
||||||
typedef scanline_storage_bin scanlines_bin_type;
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
~font_engine_freetype_base();
|
|
||||||
font_engine_freetype_base(bool flag32, unsigned max_faces = 32);
|
|
||||||
|
|
||||||
// Set font parameters
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
void resolution(unsigned dpi);
|
|
||||||
bool load_font(const char* font_name, unsigned face_index, glyph_rendering ren_type,
|
|
||||||
const char* font_mem = 0, const long font_mem_size = 0);
|
|
||||||
bool attach(const char* file_name);
|
|
||||||
bool char_map(FT_Encoding map);
|
|
||||||
bool height(double h);
|
|
||||||
bool width(double w);
|
|
||||||
void hinting(bool h);
|
|
||||||
void flip_y(bool f);
|
|
||||||
void transform(const trans_affine& affine);
|
|
||||||
|
|
||||||
// Set Gamma
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
template<class GammaF> void gamma(const GammaF& f)
|
|
||||||
{
|
|
||||||
m_rasterizer.gamma(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accessors
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
int last_error() const { return m_last_error; }
|
|
||||||
unsigned resolution() const { return m_resolution; }
|
|
||||||
const char* name() const { return m_name; }
|
|
||||||
unsigned num_faces() const;
|
|
||||||
FT_Encoding char_map() const { return m_char_map; }
|
|
||||||
double height() const { return double(m_height) / 64.0; }
|
|
||||||
double width() const { return double(m_width) / 64.0; }
|
|
||||||
double ascender() const;
|
|
||||||
double descender() const;
|
|
||||||
int face_height() const;
|
|
||||||
int face_units_em()const;
|
|
||||||
bool hinting() const { return m_hinting; }
|
|
||||||
bool flip_y() const { return m_flip_y; }
|
|
||||||
|
|
||||||
|
|
||||||
// Interface mandatory to implement for font_cache_manager
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
const char* font_signature() const { return m_signature; }
|
|
||||||
int change_stamp() const { return m_change_stamp; }
|
|
||||||
|
|
||||||
bool prepare_glyph(unsigned glyph_code);
|
|
||||||
unsigned glyph_index() const { return m_glyph_index; }
|
|
||||||
unsigned data_size() const { return m_data_size; }
|
|
||||||
glyph_data_type data_type() const { return m_data_type; }
|
|
||||||
const rect_i& bounds() const { return m_bounds; }
|
|
||||||
double advance_x() const { return m_advance_x; }
|
|
||||||
double advance_y() const { return m_advance_y; }
|
|
||||||
void write_glyph_to(int8u* data) const;
|
|
||||||
bool add_kerning(unsigned first, unsigned second,
|
|
||||||
double* x, double* y);
|
|
||||||
|
|
||||||
private:
|
|
||||||
font_engine_freetype_base(const font_engine_freetype_base&);
|
|
||||||
const font_engine_freetype_base& operator = (const font_engine_freetype_base&);
|
|
||||||
|
|
||||||
void update_char_size();
|
|
||||||
void update_signature();
|
|
||||||
int find_face(const char* face_name) const;
|
|
||||||
|
|
||||||
bool m_flag32;
|
|
||||||
int m_change_stamp;
|
|
||||||
int m_last_error;
|
|
||||||
char* m_name;
|
|
||||||
unsigned m_name_len;
|
|
||||||
unsigned m_face_index;
|
|
||||||
FT_Encoding m_char_map;
|
|
||||||
char* m_signature;
|
|
||||||
unsigned m_height;
|
|
||||||
unsigned m_width;
|
|
||||||
bool m_hinting;
|
|
||||||
bool m_flip_y;
|
|
||||||
bool m_library_initialized;
|
|
||||||
FT_Library m_library; // handle to library
|
|
||||||
FT_Face* m_faces; // A pool of font faces
|
|
||||||
char** m_face_names;
|
|
||||||
unsigned m_num_faces;
|
|
||||||
unsigned m_max_faces;
|
|
||||||
FT_Face m_cur_face; // handle to the current face object
|
|
||||||
int m_resolution;
|
|
||||||
glyph_rendering m_glyph_rendering;
|
|
||||||
unsigned m_glyph_index;
|
|
||||||
unsigned m_data_size;
|
|
||||||
glyph_data_type m_data_type;
|
|
||||||
rect_i m_bounds;
|
|
||||||
double m_advance_x;
|
|
||||||
double m_advance_y;
|
|
||||||
trans_affine m_affine;
|
|
||||||
|
|
||||||
path_storage_integer<int16, 6> m_path16;
|
|
||||||
path_storage_integer<int32, 6> m_path32;
|
|
||||||
conv_curve<path_storage_integer<int16, 6> > m_curves16;
|
|
||||||
conv_curve<path_storage_integer<int32, 6> > m_curves32;
|
|
||||||
scanline_u8 m_scanline_aa;
|
|
||||||
scanline_bin m_scanline_bin;
|
|
||||||
scanlines_aa_type m_scanlines_aa;
|
|
||||||
scanlines_bin_type m_scanlines_bin;
|
|
||||||
rasterizer_scanline_aa<> m_rasterizer;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------font_engine_freetype_int16
|
|
||||||
// This class uses values of type int16 (10.6 format) for the vector cache.
|
|
||||||
// The vector cache is compact, but when rendering glyphs of height
|
|
||||||
// more that 200 there integer overflow can occur.
|
|
||||||
//
|
|
||||||
class font_engine_freetype_int16 : public font_engine_freetype_base
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
typedef serialized_integer_path_adaptor<int16, 6> path_adaptor_type;
|
|
||||||
typedef font_engine_freetype_base::gray8_adaptor_type gray8_adaptor_type;
|
|
||||||
typedef font_engine_freetype_base::mono_adaptor_type mono_adaptor_type;
|
|
||||||
typedef font_engine_freetype_base::scanlines_aa_type scanlines_aa_type;
|
|
||||||
typedef font_engine_freetype_base::scanlines_bin_type scanlines_bin_type;
|
|
||||||
|
|
||||||
font_engine_freetype_int16(unsigned max_faces = 32) :
|
|
||||||
font_engine_freetype_base(false, max_faces) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
//------------------------------------------------font_engine_freetype_int32
|
|
||||||
// This class uses values of type int32 (26.6 format) for the vector cache.
|
|
||||||
// The vector cache is twice larger than in font_engine_freetype_int16,
|
|
||||||
// but it allows you to render glyphs of very large sizes.
|
|
||||||
//
|
|
||||||
class font_engine_freetype_int32 : public font_engine_freetype_base
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
typedef serialized_integer_path_adaptor<int32, 6> path_adaptor_type;
|
|
||||||
typedef font_engine_freetype_base::gray8_adaptor_type gray8_adaptor_type;
|
|
||||||
typedef font_engine_freetype_base::mono_adaptor_type mono_adaptor_type;
|
|
||||||
typedef font_engine_freetype_base::scanlines_aa_type scanlines_aa_type;
|
|
||||||
typedef font_engine_freetype_base::scanlines_bin_type scanlines_bin_type;
|
|
||||||
|
|
||||||
font_engine_freetype_int32(unsigned max_faces = 32) :
|
|
||||||
font_engine_freetype_base(true, max_faces) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,73 +0,0 @@
|
||||||
// Adapted by Francesco Abbate for GSL Shell
|
|
||||||
// Original code's copyright below.
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
// Anti-Grain Geometry - Version 2.4
|
|
||||||
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
|
|
||||||
//
|
|
||||||
// Permission to copy, use, modify, sell and distribute this software
|
|
||||||
// is granted provided this copyright notice appears in all copies.
|
|
||||||
// This software is provided "as is" without express or implied
|
|
||||||
// warranty, and with no claim as to its suitability for any purpose.
|
|
||||||
//
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
// Contact: mcseem@antigrain.com
|
|
||||||
// mcseemagg@yahoo.com
|
|
||||||
// http://www.antigrain.com
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#ifndef AGG_LCD_DISTRIBUTION_LUT_INCLUDED
|
|
||||||
#define AGG_LCD_DISTRIBUTION_LUT_INCLUDED
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#include "agg_basics.h"
|
|
||||||
|
|
||||||
namespace agg
|
|
||||||
{
|
|
||||||
|
|
||||||
//=====================================================lcd_distribution_lut
|
|
||||||
class lcd_distribution_lut
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
lcd_distribution_lut(double prim, double second, double tert)
|
|
||||||
{
|
|
||||||
double norm = 1.0 / (prim + second*2 + tert*2);
|
|
||||||
prim *= norm;
|
|
||||||
second *= norm;
|
|
||||||
tert *= norm;
|
|
||||||
for(unsigned i = 0; i < 256; i++)
|
|
||||||
{
|
|
||||||
unsigned b = (i << 8);
|
|
||||||
unsigned s = round(second * b);
|
|
||||||
unsigned t = round(tert * b);
|
|
||||||
unsigned p = b - (2*s + 2*t);
|
|
||||||
|
|
||||||
m_data[3*i + 1] = s; /* secondary */
|
|
||||||
m_data[3*i + 2] = t; /* tertiary */
|
|
||||||
m_data[3*i ] = p; /* primary */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned convolution(const int8u* covers, int i0, int i_min, int i_max) const
|
|
||||||
{
|
|
||||||
unsigned sum = 0;
|
|
||||||
int k_min = (i0 >= i_min + 2 ? -2 : i_min - i0);
|
|
||||||
int k_max = (i0 <= i_max - 2 ? 2 : i_max - i0);
|
|
||||||
for (int k = k_min; k <= k_max; k++)
|
|
||||||
{
|
|
||||||
/* select the primary, secondary or tertiary channel */
|
|
||||||
int channel = std::abs(k) % 3;
|
|
||||||
int8u c = covers[i0 + k];
|
|
||||||
sum += m_data[3*c + channel];
|
|
||||||
}
|
|
||||||
|
|
||||||
return (sum + 128) >> 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
unsigned short m_data[256*3];
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,93 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include "agg_basics.h"
|
|
||||||
#include "agg_rendering_buffer.h"
|
|
||||||
|
|
||||||
namespace agg
|
|
||||||
{
|
|
||||||
// This is a special purpose color type that only has the alpha channel.
|
|
||||||
// It can be thought as a gray color but with the intensity always on.
|
|
||||||
// It is actually used to store coverage information.
|
|
||||||
struct alpha8
|
|
||||||
{
|
|
||||||
typedef int8u value_type;
|
|
||||||
typedef int32u calc_type;
|
|
||||||
typedef int32 long_type;
|
|
||||||
enum base_scale_e
|
|
||||||
{
|
|
||||||
base_shift = 8,
|
|
||||||
base_scale = 1 << base_shift,
|
|
||||||
base_mask = base_scale - 1
|
|
||||||
};
|
|
||||||
|
|
||||||
value_type a;
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
alpha8(unsigned a_=base_mask) :
|
|
||||||
a(int8u(a_)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Pixer format to store coverage information.
|
|
||||||
class pixfmt_alpha8
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
typedef alpha8 color_type;
|
|
||||||
typedef int8u value_type;
|
|
||||||
typedef int32u calc_type;
|
|
||||||
typedef agg::rendering_buffer::row_data row_data;
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
pixfmt_alpha8(rendering_buffer& rb): m_rbuf(&rb)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
unsigned width() const {
|
|
||||||
return m_rbuf->width();
|
|
||||||
}
|
|
||||||
unsigned height() const {
|
|
||||||
return m_rbuf->height();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method should never be called when using the scanline_u8.
|
|
||||||
// The use of scanline_p8 should be avoided because if does not works
|
|
||||||
// properly for rendering fonts because single hspan are split in many
|
|
||||||
// hline/hspan elements and pixel whitening happens.
|
|
||||||
void blend_hline(int x, int y, unsigned len,
|
|
||||||
const color_type& c, int8u cover)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
void copy_hline(int x, int y, unsigned len, const color_type& c)
|
|
||||||
{
|
|
||||||
value_type* p = (value_type*) m_rbuf->row_ptr(y) + x;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
*p = c.a;
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
while(--len);
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
void blend_solid_hspan(int x, int y,
|
|
||||||
unsigned len,
|
|
||||||
const color_type& c,
|
|
||||||
const int8u* covers)
|
|
||||||
{
|
|
||||||
value_type* p = (value_type*) m_rbuf->row_ptr(y) + x;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
calc_type alpha = (calc_type(c.a) * (calc_type(*covers) + 1)) >> 8;
|
|
||||||
*p = alpha;
|
|
||||||
p++;
|
|
||||||
++covers;
|
|
||||||
}
|
|
||||||
while(--len);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
rendering_buffer* m_rbuf;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,445 +0,0 @@
|
||||||
#include "font_renderer.h"
|
|
||||||
|
|
||||||
#include "agg_lcd_distribution_lut.h"
|
|
||||||
#include "agg_pixfmt_rgb.h"
|
|
||||||
#include "agg_pixfmt_rgba.h"
|
|
||||||
|
|
||||||
#include "font_renderer_alpha.h"
|
|
||||||
|
|
||||||
// Important: when a subpixel scale is used the width below will be the width in logical pixel.
|
|
||||||
// As each logical pixel contains 3 subpixels it means that the 'pixels' pointer
|
|
||||||
// will hold enough space for '3 * width' uint8_t values.
|
|
||||||
struct FR_Bitmap {
|
|
||||||
agg::int8u *pixels;
|
|
||||||
int width, height;
|
|
||||||
};
|
|
||||||
|
|
||||||
class FR_Renderer {
|
|
||||||
public:
|
|
||||||
// Conventional LUT values: (1./3., 2./9., 1./9.)
|
|
||||||
// The values below are fine tuned as in the Elementary Plot library.
|
|
||||||
|
|
||||||
FR_Renderer(bool hinting, bool kerning, bool subpixel, bool prescale_x) :
|
|
||||||
m_renderer(hinting, kerning, subpixel, prescale_x),
|
|
||||||
m_lcd_lut(0.448, 0.184, 0.092),
|
|
||||||
m_subpixel(subpixel)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
font_renderer_alpha& renderer_alpha() { return m_renderer; }
|
|
||||||
agg::lcd_distribution_lut& lcd_distribution_lut() { return m_lcd_lut; }
|
|
||||||
int subpixel_scale() const { return (m_subpixel ? 3 : 1); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
font_renderer_alpha m_renderer;
|
|
||||||
agg::lcd_distribution_lut m_lcd_lut;
|
|
||||||
int m_subpixel;
|
|
||||||
};
|
|
||||||
|
|
||||||
FR_Renderer *FR_Renderer_New(unsigned int flags) {
|
|
||||||
bool hinting = ((flags & FR_HINTING) != 0);
|
|
||||||
bool kerning = ((flags & FR_KERNING) != 0);
|
|
||||||
bool subpixel = ((flags & FR_SUBPIXEL) != 0);
|
|
||||||
bool prescale_x = ((flags & FR_PRESCALE_X) != 0);
|
|
||||||
return new FR_Renderer(hinting, kerning, subpixel, prescale_x);
|
|
||||||
}
|
|
||||||
|
|
||||||
FR_Bitmap* FR_Bitmap_New(FR_Renderer *font_renderer, int width, int height) {
|
|
||||||
const int subpixel_scale = font_renderer->subpixel_scale();
|
|
||||||
FR_Bitmap *image = (FR_Bitmap *) malloc(sizeof(FR_Bitmap) + width * height * subpixel_scale);
|
|
||||||
if (!image) { return NULL; }
|
|
||||||
image->pixels = (agg::int8u *) (image + 1);
|
|
||||||
image->width = width;
|
|
||||||
image->height = height;
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FR_Bitmap_Free(FR_Bitmap *image) {
|
|
||||||
free(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FR_Renderer_Free(FR_Renderer *font_renderer) {
|
|
||||||
delete font_renderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
int FR_Subpixel_Scale(FR_Renderer *font_renderer) {
|
|
||||||
return font_renderer->subpixel_scale();
|
|
||||||
}
|
|
||||||
|
|
||||||
int FR_Load_Font(FR_Renderer *font_renderer, const char *filename) {
|
|
||||||
bool success = font_renderer->renderer_alpha().load_font(filename);
|
|
||||||
return (success ? 0 : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int FR_Get_Font_Height(FR_Renderer *font_renderer, float size) {
|
|
||||||
font_renderer_alpha& renderer_alpha = font_renderer->renderer_alpha();
|
|
||||||
double ascender, descender;
|
|
||||||
renderer_alpha.get_font_vmetrics(ascender, descender);
|
|
||||||
int face_height = renderer_alpha.get_face_height();
|
|
||||||
float scale = renderer_alpha.scale_for_em_to_pixels(size);
|
|
||||||
return int((ascender - descender) * face_height * scale + 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void glyph_trim_rect(agg::rendering_buffer& ren_buf, FR_Bitmap_Glyph_Metrics& gli, int subpixel_scale) {
|
|
||||||
const int height = ren_buf.height();
|
|
||||||
int x0 = gli.x0 * subpixel_scale, x1 = gli.x1 * subpixel_scale;
|
|
||||||
int y0 = gli.y0, y1 = gli.y1;
|
|
||||||
for (int y = gli.y0; y < gli.y1; y++) {
|
|
||||||
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
|
|
||||||
unsigned int row_bitsum = 0;
|
|
||||||
for (int x = x0; x < x1; x++) {
|
|
||||||
row_bitsum |= row[x];
|
|
||||||
}
|
|
||||||
if (row_bitsum == 0) {
|
|
||||||
y0++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int y = gli.y1 - 1; y >= y0; y--) {
|
|
||||||
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
|
|
||||||
unsigned int row_bitsum = 0;
|
|
||||||
for (int x = x0; x < x1; x++) {
|
|
||||||
row_bitsum |= row[x];
|
|
||||||
}
|
|
||||||
if (row_bitsum == 0) {
|
|
||||||
y1--;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int x = gli.x0 * subpixel_scale; x < gli.x1 * subpixel_scale; x += subpixel_scale) {
|
|
||||||
unsigned int xaccu = 0;
|
|
||||||
for (int y = y0; y < y1; y++) {
|
|
||||||
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
|
|
||||||
for (int i = 0; i < subpixel_scale; i++) {
|
|
||||||
xaccu |= row[x + i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (xaccu == 0) {
|
|
||||||
x0 += subpixel_scale;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int x = (gli.x1 - 1) * subpixel_scale; x >= x0; x -= subpixel_scale) {
|
|
||||||
unsigned int xaccu = 0;
|
|
||||||
for (int y = y0; y < y1; y++) {
|
|
||||||
const uint8_t *row = ren_buf.row_ptr(height - 1 - y);
|
|
||||||
for (int i = 0; i < subpixel_scale; i++) {
|
|
||||||
xaccu |= row[x + i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (xaccu == 0) {
|
|
||||||
x1 -= subpixel_scale;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gli.xoff += (x0 / subpixel_scale) - gli.x0;
|
|
||||||
gli.yoff += (y0 - gli.y0);
|
|
||||||
gli.x0 = x0 / subpixel_scale;
|
|
||||||
gli.y0 = y0;
|
|
||||||
gli.x1 = x1 / subpixel_scale;
|
|
||||||
gli.y1 = y1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void glyph_lut_convolution(agg::rendering_buffer ren_buf, agg::lcd_distribution_lut& lcd_lut, agg::int8u *covers_buf, FR_Bitmap_Glyph_Metrics& gli) {
|
|
||||||
const int subpixel = 3;
|
|
||||||
const int x0 = gli.x0, y0 = gli.y0, x1 = gli.x1, y1 = gli.y1;
|
|
||||||
const int len = (x1 - x0) * subpixel;
|
|
||||||
const int height = ren_buf.height();
|
|
||||||
for (int y = y0; y < y1; y++) {
|
|
||||||
agg::int8u *covers = ren_buf.row_ptr(height - 1 - y) + x0 * subpixel;
|
|
||||||
memcpy(covers_buf, covers, len);
|
|
||||||
for (int x = x0 - 1; x < x1 + 1; x++) {
|
|
||||||
for (int i = 0; i < subpixel; i++) {
|
|
||||||
const int cx = (x - x0) * subpixel + i;
|
|
||||||
covers[cx] = lcd_lut.convolution(covers_buf, cx, 0, len - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gli.x0 -= 1;
|
|
||||||
gli.x1 += 1;
|
|
||||||
gli.xoff -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The two functions below are needed because in C and C++ integer division
|
|
||||||
// is rounded toward zero.
|
|
||||||
|
|
||||||
// euclidean division rounded toward positive infinite
|
|
||||||
static int div_pos(int n, int p) {
|
|
||||||
return n >= 0 ? (n + p - 1) / p : (n / p);
|
|
||||||
}
|
|
||||||
|
|
||||||
// euclidean division rounded toward negative infinite
|
|
||||||
static int div_neg(int n, int p) {
|
|
||||||
return n >= 0 ? (n / p) : ((n - p + 1) / p);
|
|
||||||
}
|
|
||||||
|
|
||||||
FR_Bitmap *FR_Bake_Font_Bitmap(FR_Renderer *font_renderer, int font_height,
|
|
||||||
int first_char, int num_chars, FR_Bitmap_Glyph_Metrics *glyphs)
|
|
||||||
{
|
|
||||||
font_renderer_alpha& renderer_alpha = font_renderer->renderer_alpha();
|
|
||||||
agg::lcd_distribution_lut& lcd_lut = font_renderer->lcd_distribution_lut();
|
|
||||||
const int subpixel_scale = font_renderer->subpixel_scale();
|
|
||||||
|
|
||||||
double ascender, descender;
|
|
||||||
renderer_alpha.get_font_vmetrics(ascender, descender);
|
|
||||||
const int ascender_px = int(ascender * font_height);
|
|
||||||
const int pad_y = 1;
|
|
||||||
|
|
||||||
// When using subpixel font rendering it is needed to leave a padding pixel on the left and on the right.
|
|
||||||
// Since each pixel is composed by n subpixel we set below x_start to subpixel_scale instead than zero.
|
|
||||||
// In addition we need one more pixel on the left because of subpixel positioning so
|
|
||||||
// it adds up to 2 * subpixel_scale.
|
|
||||||
// Note about the coordinates: they are AGG-like so x is positive toward the right and
|
|
||||||
// y is positive in the upper direction.
|
|
||||||
const int x_start = 2 * subpixel_scale;
|
|
||||||
const agg::alpha8 text_color(0xff);
|
|
||||||
#ifdef FONT_RENDERER_HEIGHT_HACK
|
|
||||||
const int font_height_reduced = (font_height * 86) / 100;
|
|
||||||
#else
|
|
||||||
const int font_height_reduced = font_height;
|
|
||||||
#endif
|
|
||||||
renderer_alpha.set_font_height(font_height_reduced);
|
|
||||||
|
|
||||||
int *index = (int *) malloc(num_chars * sizeof(int));
|
|
||||||
agg::rect_i *bounds = (agg::rect_i *) malloc(num_chars * sizeof(agg::rect_i));
|
|
||||||
if (!index || !bounds) {
|
|
||||||
free(index);
|
|
||||||
free(bounds);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int x_size_sum = 0, glyph_count = 0;
|
|
||||||
for (int i = 0; i < num_chars; i++) {
|
|
||||||
int codepoint = first_char + i;
|
|
||||||
index[i] = i;
|
|
||||||
if (renderer_alpha.codepoint_bounds(codepoint, subpixel_scale, bounds[i])) {
|
|
||||||
// Invalid glyph
|
|
||||||
bounds[i].x1 = 0;
|
|
||||||
bounds[i].y1 = 0;
|
|
||||||
bounds[i].x2 = -1;
|
|
||||||
bounds[i].y2 = -1;
|
|
||||||
} else {
|
|
||||||
if (bounds[i].x2 > bounds[i].x1) {
|
|
||||||
x_size_sum += bounds[i].x2 - bounds[i].x1;
|
|
||||||
glyph_count++;
|
|
||||||
}
|
|
||||||
bounds[i].x1 = subpixel_scale * div_neg(bounds[i].x1, subpixel_scale);
|
|
||||||
bounds[i].x2 = subpixel_scale * div_pos(bounds[i].x2, subpixel_scale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple insertion sort algorithm: https://en.wikipedia.org/wiki/Insertion_sort
|
|
||||||
int i = 1;
|
|
||||||
while (i < num_chars) {
|
|
||||||
int j = i;
|
|
||||||
while (j > 0 && bounds[index[j-1]].y2 - bounds[index[j-1]].y1 > bounds[index[j]].y2 - bounds[index[j]].y1) {
|
|
||||||
int tmp = index[j];
|
|
||||||
index[j] = index[j-1];
|
|
||||||
index[j-1] = tmp;
|
|
||||||
j = j - 1;
|
|
||||||
}
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int glyph_avg_width = glyph_count > 0 ? x_size_sum / (glyph_count * subpixel_scale) : font_height;
|
|
||||||
const int pixels_width = glyph_avg_width * 28;
|
|
||||||
|
|
||||||
// dry run simulating pixel position to estimate required image's height
|
|
||||||
int x = x_start, y = 0, y_bottom = y;
|
|
||||||
for (int i = 0; i < num_chars; i++) {
|
|
||||||
const agg::rect_i& gbounds = bounds[index[i]];
|
|
||||||
if (gbounds.x2 < gbounds.x1) continue;
|
|
||||||
// 1. It is very important to ensure that the x's increment below (1) and in
|
|
||||||
// (2), (3) and (4) are perfectly the same.
|
|
||||||
// Note that x_step below is always an integer multiple of subpixel_scale.
|
|
||||||
const int x_step = gbounds.x2 + 3 * subpixel_scale;
|
|
||||||
if (x + x_step >= pixels_width * subpixel_scale) {
|
|
||||||
x = x_start;
|
|
||||||
y = y_bottom;
|
|
||||||
}
|
|
||||||
// 5. Ensure that y's increment below is exactly the same to the one used in (6)
|
|
||||||
const int glyph_y_bottom = y - 2 * pad_y - (gbounds.y2 - gbounds.y1);
|
|
||||||
y_bottom = (y_bottom > glyph_y_bottom ? glyph_y_bottom : y_bottom);
|
|
||||||
// 2. Ensure x's increment is aligned with (1)
|
|
||||||
x = x + x_step;
|
|
||||||
}
|
|
||||||
|
|
||||||
agg::int8u *cover_swap_buffer = (agg::int8u *) malloc(sizeof(agg::int8u) * (pixels_width * subpixel_scale));
|
|
||||||
if (!cover_swap_buffer) {
|
|
||||||
free(index);
|
|
||||||
free(bounds);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int pixels_height = -y_bottom + 1;
|
|
||||||
const int pixel_size = 1;
|
|
||||||
FR_Bitmap *image = FR_Bitmap_New(font_renderer, pixels_width, pixels_height);
|
|
||||||
if (!image) {
|
|
||||||
free(index);
|
|
||||||
free(bounds);
|
|
||||||
free(cover_swap_buffer);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
agg::int8u *pixels = image->pixels;
|
|
||||||
memset(pixels, 0x00, pixels_width * pixels_height * subpixel_scale * pixel_size);
|
|
||||||
agg::rendering_buffer ren_buf(pixels, pixels_width * subpixel_scale, pixels_height, -pixels_width * subpixel_scale * pixel_size);
|
|
||||||
|
|
||||||
// The variable y_bottom will be used to go down to the next row by taking into
|
|
||||||
// account the space occupied by each glyph of the current row along the y direction.
|
|
||||||
x = x_start;
|
|
||||||
// Set y to the image's height minus one to begin writing glyphs in the upper part of the image.
|
|
||||||
y = pixels_height - 1;
|
|
||||||
y_bottom = y;
|
|
||||||
for (int i = 0; i < num_chars; i++) {
|
|
||||||
// Important: the variable x in this loop should always be an integer multiple
|
|
||||||
// of subpixel_scale.
|
|
||||||
int codepoint = first_char + index[i];
|
|
||||||
const agg::rect_i& gbounds = bounds[index[i]];
|
|
||||||
if (gbounds.x2 < gbounds.x1) continue;
|
|
||||||
|
|
||||||
// 3. Ensure x's increment is aligned with (1)
|
|
||||||
// Note that x_step below is always an integer multiple of subpixel_scale.
|
|
||||||
// We need 3 * subpixel_scale because:
|
|
||||||
// . +1 pixel on the left, because of RGB color filter
|
|
||||||
// . +1 pixel on the right, because of RGB color filter
|
|
||||||
// . +1 pixel on the right, because of subpixel positioning
|
|
||||||
// and each pixel requires "subpixel_scale" sub-pixels.
|
|
||||||
const int x_step = gbounds.x2 + 3 * subpixel_scale;
|
|
||||||
if (x + x_step >= pixels_width * subpixel_scale) {
|
|
||||||
// No more space along x, begin writing the row below.
|
|
||||||
x = x_start;
|
|
||||||
y = y_bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int y_baseline = y - pad_y - gbounds.y2;
|
|
||||||
// 6. Ensure the y's increment below is aligned with the increment used in (5)
|
|
||||||
const int glyph_y_bottom = y - 2 * pad_y - (gbounds.y2 - gbounds.y1);
|
|
||||||
y_bottom = (y_bottom > glyph_y_bottom ? glyph_y_bottom : y_bottom);
|
|
||||||
|
|
||||||
double x_next = x, y_next = y_baseline;
|
|
||||||
renderer_alpha.render_codepoint(ren_buf, text_color, x_next, y_next, codepoint, subpixel_scale);
|
|
||||||
|
|
||||||
// The y coordinate for the glyph below is positive in the bottom direction,
|
|
||||||
// like is used by Lite's drawing system.
|
|
||||||
FR_Bitmap_Glyph_Metrics& glyph_info = glyphs[index[i]];
|
|
||||||
glyph_info.x0 = x / subpixel_scale;
|
|
||||||
glyph_info.y0 = pixels_height - 1 - (y_baseline + gbounds.y2 + pad_y);
|
|
||||||
glyph_info.x1 = div_pos(x_next + 0.5, subpixel_scale);
|
|
||||||
glyph_info.y1 = pixels_height - 1 - (y_baseline + gbounds.y1 - pad_y);
|
|
||||||
|
|
||||||
glyph_info.xoff = 0;
|
|
||||||
glyph_info.yoff = -pad_y - gbounds.y2 + ascender_px;
|
|
||||||
// Note that below the xadvance is in pixels times the subpixel_scale.
|
|
||||||
// This is meant for subpixel positioning.
|
|
||||||
glyph_info.xadvance = roundf(x_next - x);
|
|
||||||
|
|
||||||
if (subpixel_scale != 1 && glyph_info.x1 > glyph_info.x0) {
|
|
||||||
glyph_lut_convolution(ren_buf, lcd_lut, cover_swap_buffer, glyph_info);
|
|
||||||
}
|
|
||||||
glyph_trim_rect(ren_buf, glyph_info, subpixel_scale);
|
|
||||||
|
|
||||||
// When subpixel is activated we need one padding pixel on the left and on the right
|
|
||||||
// and one more because of subpixel positioning.
|
|
||||||
// 4. Ensure x's increment is aligned with (1)
|
|
||||||
x = x + x_step;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(index);
|
|
||||||
free(bounds);
|
|
||||||
free(cover_swap_buffer);
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Order>
|
|
||||||
void blend_solid_hspan(agg::rendering_buffer& rbuf, int x, int y, unsigned len,
|
|
||||||
const agg::rgba8& c, const agg::int8u* covers)
|
|
||||||
{
|
|
||||||
const int pixel_size = 4;
|
|
||||||
agg::int8u* p = rbuf.row_ptr(y) + x * pixel_size;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
const unsigned alpha = *covers;
|
|
||||||
const unsigned r = p[Order::R], g = p[Order::G], b = p[Order::B];
|
|
||||||
p[Order::R] = (((unsigned(c.r) - r) * alpha) >> 8) + r;
|
|
||||||
p[Order::G] = (((unsigned(c.g) - g) * alpha) >> 8) + g;
|
|
||||||
p[Order::B] = (((unsigned(c.b) - b) * alpha) >> 8) + b;
|
|
||||||
// Leave p[3], the alpha channel value unmodified.
|
|
||||||
p += 4;
|
|
||||||
++covers;
|
|
||||||
}
|
|
||||||
while(--len);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Order>
|
|
||||||
void blend_solid_hspan_subpixel(agg::rendering_buffer& rbuf, agg::lcd_distribution_lut& lcd_lut,
|
|
||||||
const int x, const int y, unsigned len,
|
|
||||||
const agg::rgba8& c,
|
|
||||||
const agg::int8u* covers)
|
|
||||||
{
|
|
||||||
const int pixel_size = 4;
|
|
||||||
const unsigned rgb[3] = { c.r, c.g, c.b };
|
|
||||||
agg::int8u* p = rbuf.row_ptr(y) + x * pixel_size;
|
|
||||||
|
|
||||||
// Indexes to adress RGB colors in a BGRA32 format.
|
|
||||||
const int pixel_index[3] = {Order::R, Order::G, Order::B};
|
|
||||||
for (unsigned cx = 0; cx < len; cx += 3)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
const unsigned cover_value = covers[cx + i];
|
|
||||||
const unsigned alpha = (cover_value + 1) * (c.a + 1);
|
|
||||||
const unsigned src_col = *(p + pixel_index[i]);
|
|
||||||
*(p + pixel_index[i]) = (((rgb[i] - src_col) * alpha) + (src_col << 16)) >> 16;
|
|
||||||
}
|
|
||||||
// Leave p[3], the alpha channel value unmodified.
|
|
||||||
p += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// destination implicitly BGRA32. Source implictly single-byte renderer_alpha coverage with subpixel scale = 3.
|
|
||||||
// FIXME: consider using something like RenColor* instead of uint8_t * for dst.
|
|
||||||
void FR_Blend_Glyph(FR_Renderer *font_renderer, FR_Clip_Area *clip, int x_mult, int y, uint8_t *dst, int dst_width, const FR_Bitmap *glyphs_bitmap, const FR_Bitmap_Glyph_Metrics *glyph, FR_Color color) {
|
|
||||||
agg::lcd_distribution_lut& lcd_lut = font_renderer->lcd_distribution_lut();
|
|
||||||
const int subpixel_scale = font_renderer->subpixel_scale();
|
|
||||||
const int pixel_size = 4; // Pixel size for BGRA32 format.
|
|
||||||
|
|
||||||
int x = x_mult / subpixel_scale;
|
|
||||||
|
|
||||||
x += glyph->xoff;
|
|
||||||
y += glyph->yoff;
|
|
||||||
|
|
||||||
int glyph_x = glyph->x0, glyph_y = glyph->y0;
|
|
||||||
int glyph_x_subpixel = -(x_mult % subpixel_scale);
|
|
||||||
int glyph_width = glyph->x1 - glyph->x0;
|
|
||||||
int glyph_height = glyph->y1 - glyph->y0;
|
|
||||||
|
|
||||||
int n;
|
|
||||||
if ((n = clip->left - x) > 0) { glyph_width -= n; glyph_x += n; x += n; }
|
|
||||||
if ((n = clip->top - y) > 0) { glyph_height -= n; glyph_y += n; y += n; }
|
|
||||||
if ((n = x + glyph_width - clip->right ) > 0) { glyph_width -= n; }
|
|
||||||
if ((n = y + glyph_height - clip->bottom) > 0) { glyph_height -= n; }
|
|
||||||
|
|
||||||
if (glyph_width <= 0 || glyph_height <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dst += (x + y * dst_width) * pixel_size;
|
|
||||||
agg::rendering_buffer dst_ren_buf(dst, glyph_width, glyph_height, dst_width * pixel_size);
|
|
||||||
|
|
||||||
uint8_t *src = glyphs_bitmap->pixels + (glyph_x + glyph_y * glyphs_bitmap->width) * subpixel_scale + glyph_x_subpixel;
|
|
||||||
int src_stride = glyphs_bitmap->width * subpixel_scale;
|
|
||||||
|
|
||||||
const agg::rgba8 color_a(color.r, color.g, color.b);
|
|
||||||
for (int x = 0, y = 0; y < glyph_height; y++) {
|
|
||||||
agg::int8u *covers = src + y * src_stride;
|
|
||||||
if (subpixel_scale == 1) {
|
|
||||||
blend_solid_hspan<agg::order_bgra>(dst_ren_buf, x, y, glyph_width, color_a, covers);
|
|
||||||
} else {
|
|
||||||
blend_solid_hspan_subpixel<agg::order_bgra>(dst_ren_buf, lcd_lut, x, y, glyph_width * subpixel_scale, color_a, covers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
#ifndef FONT_RENDERER_H
|
|
||||||
#define FONT_RENDERER_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
unsigned short x0, y0, x1, y1;
|
|
||||||
float xoff, yoff, xadvance;
|
|
||||||
} FR_Bitmap_Glyph_Metrics;
|
|
||||||
|
|
||||||
typedef struct FR_Bitmap FR_Bitmap;
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
class FR_Renderer;
|
|
||||||
#else
|
|
||||||
struct FR_Renderer;
|
|
||||||
typedef struct FR_Renderer FR_Renderer;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum {
|
|
||||||
FR_HINTING = 1 << 0,
|
|
||||||
FR_KERNING = 1 << 1,
|
|
||||||
FR_SUBPIXEL = 1 << 2,
|
|
||||||
FR_PRESCALE_X = 1 << 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t r, g, b;
|
|
||||||
} FR_Color;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int left, top, right, bottom;
|
|
||||||
} FR_Clip_Area;
|
|
||||||
|
|
||||||
FR_Renderer * FR_Renderer_New(unsigned int flags);
|
|
||||||
void FR_Renderer_Free(FR_Renderer *);
|
|
||||||
int FR_Load_Font(FR_Renderer *, const char *filename);
|
|
||||||
FR_Bitmap* FR_Bitmap_New(FR_Renderer *, int width, int height);
|
|
||||||
void FR_Bitmap_Free(FR_Bitmap *image);
|
|
||||||
int FR_Get_Font_Height(FR_Renderer *, float size);
|
|
||||||
FR_Bitmap * FR_Bake_Font_Bitmap(FR_Renderer *, int font_height,
|
|
||||||
int first_char, int num_chars, FR_Bitmap_Glyph_Metrics *glyph_info);
|
|
||||||
void FR_Blend_Glyph(FR_Renderer *font_renderer,
|
|
||||||
FR_Clip_Area *clip, int x, int y,
|
|
||||||
uint8_t *dst, int dst_width,
|
|
||||||
const FR_Bitmap *glyphs_bitmap,
|
|
||||||
const FR_Bitmap_Glyph_Metrics *glyph, FR_Color color);
|
|
||||||
int FR_Subpixel_Scale(FR_Renderer *);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,164 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "agg_basics.h"
|
|
||||||
#include "agg_conv_curve.h"
|
|
||||||
#include "agg_conv_transform.h"
|
|
||||||
#include "agg_gamma_lut.h"
|
|
||||||
#include "agg_font_freetype.h"
|
|
||||||
#include "agg_pixfmt_alpha8.h"
|
|
||||||
#include "agg_rasterizer_scanline_aa.h"
|
|
||||||
#include "agg_renderer_primitives.h"
|
|
||||||
#include "agg_renderer_scanline.h"
|
|
||||||
#include "agg_rendering_buffer.h"
|
|
||||||
#include "agg_scanline_u.h"
|
|
||||||
|
|
||||||
class font_renderer_alpha
|
|
||||||
{
|
|
||||||
typedef agg::pixfmt_alpha8 pixfmt_type;
|
|
||||||
typedef agg::renderer_base<pixfmt_type> base_ren_type;
|
|
||||||
typedef agg::renderer_scanline_aa_solid<base_ren_type> renderer_solid;
|
|
||||||
typedef agg::font_engine_freetype_int32 font_engine_type;
|
|
||||||
typedef agg::font_cache_manager<font_engine_type> font_manager_type;
|
|
||||||
|
|
||||||
font_engine_type m_feng;
|
|
||||||
font_manager_type m_fman;
|
|
||||||
|
|
||||||
// Font rendering options.
|
|
||||||
bool m_hinting;
|
|
||||||
bool m_kerning;
|
|
||||||
bool m_subpixel;
|
|
||||||
bool m_prescale_x;
|
|
||||||
|
|
||||||
bool m_font_loaded;
|
|
||||||
|
|
||||||
// Pipeline to process the vectors glyph paths (curves + contour)
|
|
||||||
agg::trans_affine m_mtx;
|
|
||||||
agg::conv_curve<font_manager_type::path_adaptor_type> m_curves;
|
|
||||||
agg::conv_transform<agg::conv_curve<font_manager_type::path_adaptor_type> > m_trans;
|
|
||||||
public:
|
|
||||||
typedef agg::pixfmt_alpha8::color_type color_type;
|
|
||||||
|
|
||||||
font_renderer_alpha(bool hinting, bool kerning, bool subpixel, bool prescale_x):
|
|
||||||
m_feng(),
|
|
||||||
m_fman(m_feng),
|
|
||||||
m_hinting(hinting),
|
|
||||||
m_kerning(kerning),
|
|
||||||
m_subpixel(subpixel),
|
|
||||||
m_prescale_x(prescale_x),
|
|
||||||
m_font_loaded(false),
|
|
||||||
m_curves(m_fman.path_adaptor()),
|
|
||||||
m_trans(m_curves, m_mtx)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
int get_face_height() const {
|
|
||||||
return m_feng.face_height();
|
|
||||||
}
|
|
||||||
|
|
||||||
void get_font_vmetrics(double& ascender, double& descender) {
|
|
||||||
double current_height = m_feng.height();
|
|
||||||
m_feng.height(1.0);
|
|
||||||
ascender = m_feng.ascender();
|
|
||||||
descender = m_feng.descender();
|
|
||||||
m_feng.height(current_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
float scale_for_em_to_pixels(float size) {
|
|
||||||
int units_per_em = m_feng.face_units_em();
|
|
||||||
if (units_per_em > 0) {
|
|
||||||
return size / units_per_em;
|
|
||||||
}
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool load_font(const char *font_filename) {
|
|
||||||
if(m_feng.load_font(font_filename, 0, agg::glyph_ren_outline)) {
|
|
||||||
m_font_loaded = true;
|
|
||||||
m_feng.hinting(m_hinting);
|
|
||||||
}
|
|
||||||
return m_font_loaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_font_height(double height) {
|
|
||||||
const double scale_x = (m_prescale_x ? 100.0 : 1.0);
|
|
||||||
m_feng.height(height);
|
|
||||||
m_feng.width(height * scale_x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Rasterizer, class Scanline, class RenSolid>
|
|
||||||
void draw_codepoint(Rasterizer& ras, Scanline& sl,
|
|
||||||
RenSolid& ren_solid, const color_type color,
|
|
||||||
int codepoint, double& x, double& y, const int subpixel_scale)
|
|
||||||
{
|
|
||||||
const double scale_x = (m_prescale_x ? 100.0 : 1.0);
|
|
||||||
// Coefficient to scale back the glyph to the final scale.
|
|
||||||
const double cx_inv_scale = subpixel_scale / scale_x;
|
|
||||||
|
|
||||||
// Represent the delta in x scaled by scale_x.
|
|
||||||
double x_delta = 0;
|
|
||||||
double start_x = x;
|
|
||||||
|
|
||||||
const agg::glyph_cache* glyph = m_fman.glyph(codepoint);
|
|
||||||
if(glyph)
|
|
||||||
{
|
|
||||||
if(m_kerning)
|
|
||||||
{
|
|
||||||
m_fman.add_kerning(&x_delta, &y);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_fman.init_embedded_adaptors(glyph, 0, 0);
|
|
||||||
if(glyph->data_type == agg::glyph_data_outline)
|
|
||||||
{
|
|
||||||
double ty = m_hinting ? floor(y + 0.5) : y;
|
|
||||||
ras.reset();
|
|
||||||
m_mtx.reset();
|
|
||||||
m_mtx *= agg::trans_affine_scaling(cx_inv_scale, 1);
|
|
||||||
m_mtx *= agg::trans_affine_translation(start_x + cx_inv_scale * x_delta, ty);
|
|
||||||
ras.add_path(m_trans);
|
|
||||||
ren_solid.color(color);
|
|
||||||
agg::render_scanlines(ras, sl, ren_solid);
|
|
||||||
}
|
|
||||||
|
|
||||||
y += glyph->advance_y;
|
|
||||||
x += cx_inv_scale * (x_delta + glyph->advance_x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear(agg::rendering_buffer& ren_buf, const color_type color) {
|
|
||||||
pixfmt_type pf(ren_buf);
|
|
||||||
base_ren_type ren_base(pf);
|
|
||||||
ren_base.clear(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void render_codepoint(agg::rendering_buffer& ren_buf,
|
|
||||||
const color_type text_color,
|
|
||||||
double& x, double& y,
|
|
||||||
int codepoint, const int subpixel_scale)
|
|
||||||
{
|
|
||||||
if (!m_font_loaded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
agg::scanline_u8 sl;
|
|
||||||
agg::rasterizer_scanline_aa<> ras;
|
|
||||||
ras.clip_box(0, 0, ren_buf.width(), ren_buf.height());
|
|
||||||
|
|
||||||
agg::pixfmt_alpha8 pf(ren_buf);
|
|
||||||
base_ren_type ren_base(pf);
|
|
||||||
renderer_solid ren_solid(ren_base);
|
|
||||||
draw_codepoint(ras, sl, ren_solid, text_color, codepoint, x, y, subpixel_scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
int codepoint_bounds(int codepoint, const int subpixel_scale, agg::rect_i& bounds)
|
|
||||||
{
|
|
||||||
if (!m_font_loaded) return 1;
|
|
||||||
const double scale_x = (m_prescale_x ? 100.0 : 1.0);
|
|
||||||
const double cx_inv_scale = subpixel_scale / scale_x;
|
|
||||||
const agg::glyph_cache* glyph = m_fman.glyph(codepoint);
|
|
||||||
if (glyph) {
|
|
||||||
bounds = glyph->bounds;
|
|
||||||
bounds.x1 *= cx_inv_scale;
|
|
||||||
bounds.x2 *= cx_inv_scale;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,23 +0,0 @@
|
||||||
freetype_dep = dependency('freetype2')
|
|
||||||
|
|
||||||
libagg_dep = dependency('libagg', required: false)
|
|
||||||
if not libagg_dep.found()
|
|
||||||
libagg_subproject = subproject('libagg')
|
|
||||||
libagg_dep = libagg_subproject.get_variable('libagg_dep')
|
|
||||||
endif
|
|
||||||
|
|
||||||
font_renderer_sources = [
|
|
||||||
'agg_font_freetype.cpp',
|
|
||||||
'font_renderer.cpp',
|
|
||||||
]
|
|
||||||
|
|
||||||
font_renderer_cdefs = ['-DFONT_RENDERER_HEIGHT_HACK']
|
|
||||||
|
|
||||||
font_renderer_include = include_directories('.')
|
|
||||||
|
|
||||||
libfontrenderer = static_library('fontrenderer',
|
|
||||||
font_renderer_sources,
|
|
||||||
dependencies: [libagg_dep, freetype_dep],
|
|
||||||
cpp_args: font_renderer_cdefs,
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
```c
|
|
||||||
stbtt_InitFont
|
|
||||||
|
|
||||||
stbtt_ScaleForMappingEmToPixels x 3
|
|
||||||
stbtt_ScaleForPixelHeight
|
|
||||||
stbtt_BakeFontBitmap
|
|
||||||
stbtt_GetFontVMetrics x 2
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap
|
|
||||||
float xoff, yoff, xadvance;
|
|
||||||
} stbtt_bakedchar;
|
|
||||||
|
|
||||||
struct RenImage {
|
|
||||||
RenColor *pixels;
|
|
||||||
int width, height;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
RenImage *image;
|
|
||||||
stbtt_bakedchar glyphs[256];
|
|
||||||
} GlyphSet;
|
|
||||||
|
|
||||||
struct RenFont {
|
|
||||||
void *data;
|
|
||||||
stbtt_fontinfo stbfont;
|
|
||||||
GlyphSet *sets[MAX_GLYPHSET];
|
|
||||||
float size;
|
|
||||||
int height;
|
|
||||||
};
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
The function stbtt_BakeFontBitmap is used to write bitmap data into set->image->pixels (where set is a GlyphSet).
|
|
||||||
Note that set->image->pixels need data in RGB format. After stbtt_BakeFontBitmap call the bitmap data are converted into RGB.
|
|
||||||
With a single call many glyphs corresponding to a range of codepoints, all in a
|
|
||||||
single image.
|
|
||||||
|
|
||||||
## STB truetype font metrics
|
|
||||||
|
|
||||||
stbtt_ScaleForPixelHeight takes a float 'height' and returns height / (ascent - descent).
|
|
||||||
|
|
||||||
stbtt_ScaleForMappingEmToPixels take a float 'pixels' and returns pixels / unitsPerEm.
|
|
||||||
|
|
||||||
### Computing RenFont
|
|
||||||
|
|
||||||
When loading a font, in renderer.c, the font->height is determined as:
|
|
||||||
|
|
||||||
```c
|
|
||||||
int ascent, descent, linegap;
|
|
||||||
stbtt_GetFontVMetrics(&font->stbfont, &ascent, &descent, &linegap);
|
|
||||||
float scale = stbtt_ScaleForMappingEmToPixels(&font->stbfont, font->size);
|
|
||||||
font->height = (ascent - descent + linegap) * scale + 0.5;
|
|
||||||
```
|
|
||||||
|
|
||||||
so, mathematically
|
|
||||||
|
|
||||||
```c
|
|
||||||
font->height = (ascent - descent + linegap) * font->size / unitsPerEm + 0.5;
|
|
||||||
```
|
|
||||||
|
|
||||||
**TO DO**: find out for what font->height is actually used.
|
|
||||||
|
|
||||||
### Call to BakeFontBitmap
|
|
||||||
|
|
||||||
In the same file, renderer.c, to create the glyphset image it computes:
|
|
||||||
|
|
||||||
```c
|
|
||||||
// Using stbtt functions
|
|
||||||
float s = ScaleForMappingEmToPixels(1) / ScaleForPixelHeight(1);
|
|
||||||
```
|
|
||||||
|
|
||||||
so 's' is actually equal to (ascent - descent) / unitsPerEm.
|
|
||||||
|
|
||||||
Then BakeFontBitmap is called and `font->size * s` is used for the pixel_height argument.
|
|
||||||
So BakeFontBitmap gets
|
|
||||||
|
|
||||||
```c
|
|
||||||
pixel_height = (ascent - descent) * font->size / unitsPerEm;
|
|
||||||
```
|
|
||||||
|
|
||||||
In turns BakeFontBitmap passes pixel_height to ScaleForPixelHeight() and uses the
|
|
||||||
resulting 'scale' to scale the glyphs by passing it to MakeGlyphBitmap().
|
|
||||||
|
|
||||||
This is equal almost equal to font->height except the 0.5, the missing linegap calculation
|
|
||||||
and the fact that this latter is an integer instead of a float.
|
|
||||||
|
|
||||||
## AGG Font Engine
|
|
||||||
|
|
||||||
Calls
|
|
||||||
|
|
||||||
`FT_Init_FreeType` (initialize the library)
|
|
||||||
|
|
||||||
In `load_font()` method:
|
|
||||||
`FT_New_Face` or `FT_New_Memory_Face` (use `FT_Done_Face` when done)
|
|
||||||
|
|
||||||
`FT_Attach_File`
|
|
||||||
`FT_Select_Charmap`
|
|
||||||
|
|
||||||
In `update_char_size()` method:
|
|
||||||
`FT_Set_Char_Size` or `FT_Set_Pixel_Sizes`
|
|
||||||
|
|
||||||
In `prepare_glyph()` method:
|
|
||||||
`FT_Get_Char_Index`
|
|
||||||
`FT_Load_Glyph`
|
|
||||||
`FT_Render_Glyph` (if glyph_render_native_mono or native_gray8)
|
|
||||||
|
|
||||||
in `add_kerning()` method
|
|
||||||
`FT_Get_Kerning`
|
|
||||||
|
|
||||||
`FT_Done_FreeType` (end with library)
|
|
||||||
|
|
||||||
## Freetype2's metrics related function and structs
|
|
||||||
|
|
||||||
`FT_New_Face` return a `FT_Face` (a pointer) with font face information.
|
|
||||||
|
|
||||||
## AGG font engine's font size
|
|
||||||
|
|
||||||
The variable `m_height` is the size of the font muliplied by 64.
|
|
||||||
It will be used to set font size with:
|
|
||||||
|
|
||||||
- `FT_Set_Char_Size` if m_resolution is set (> 0)
|
|
||||||
- `FT_Set_Pixel_Sizes`, divided by 64, if m_resolution is not set (= 0)
|
|
||||||
|
|
||||||
The method height() returns m_height / 64;
|
|
171
meson.build
171
meson.build
|
@ -1,74 +1,127 @@
|
||||||
project('lite-xl', 'c', 'cpp', default_options : ['c_std=gnu11', 'cpp_std=c++03'])
|
project('lite-xl',
|
||||||
|
['c'],
|
||||||
|
version : '2.0.2',
|
||||||
|
license : 'MIT',
|
||||||
|
meson_version : '>= 0.54',
|
||||||
|
default_options : ['c_std=gnu11']
|
||||||
|
)
|
||||||
|
|
||||||
version = get_option('version')
|
#===============================================================================
|
||||||
|
# Configuration
|
||||||
|
#===============================================================================
|
||||||
conf_data = configuration_data()
|
conf_data = configuration_data()
|
||||||
conf_data.set('PROJECT_VERSION', version)
|
conf_data.set('PROJECT_BUILD_DIR', meson.current_build_dir())
|
||||||
|
conf_data.set('PROJECT_SOURCE_DIR', meson.current_source_dir())
|
||||||
|
conf_data.set('PROJECT_VERSION', meson.project_version())
|
||||||
|
|
||||||
|
#===============================================================================
|
||||||
|
# Compiler Settings
|
||||||
|
#===============================================================================
|
||||||
if host_machine.system() == 'darwin'
|
if host_machine.system() == 'darwin'
|
||||||
add_languages('objc')
|
add_languages('objc')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
cc = meson.get_compiler('c')
|
cc = meson.get_compiler('c')
|
||||||
libm = cc.find_library('m', required : false)
|
|
||||||
libdl = cc.find_library('dl', required : false)
|
|
||||||
libx11 = dependency('x11', required : false)
|
|
||||||
lua_dep = dependency('lua5.2', required : false)
|
|
||||||
pcre2_dep = dependency('libpcre2-8')
|
|
||||||
sdl_dep = dependency('sdl2', method: 'config-tool')
|
|
||||||
|
|
||||||
if not lua_dep.found()
|
|
||||||
lua_subproject = subproject('lua', default_options: ['shared=false', 'use_readline=false', 'app=false'])
|
|
||||||
lua_dep = lua_subproject.get_variable('lua_dep')
|
|
||||||
endif
|
|
||||||
|
|
||||||
reproc_subproject = subproject('reproc', default_options: ['default_library=static', 'multithreaded=false', 'reproc-cpp=false', 'examples=false'])
|
|
||||||
reproc_dep = reproc_subproject.get_variable('reproc_dep')
|
|
||||||
|
|
||||||
lite_deps = [lua_dep, sdl_dep, reproc_dep, pcre2_dep, libm, libdl, libx11]
|
|
||||||
|
|
||||||
if host_machine.system() == 'windows'
|
|
||||||
# Note that we need to explicitly add the windows socket DLL because
|
|
||||||
# the pkg-config file from reproc does not include it.
|
|
||||||
lite_deps += meson.get_compiler('cpp').find_library('ws2_32', required: true)
|
|
||||||
endif
|
|
||||||
|
|
||||||
lite_cargs = []
|
lite_cargs = []
|
||||||
if get_option('portable')
|
|
||||||
lite_docdir = 'doc'
|
|
||||||
lite_datadir = 'data'
|
|
||||||
else
|
|
||||||
lite_docdir = 'share/doc/lite-xl'
|
|
||||||
lite_datadir = 'share/lite-xl'
|
|
||||||
endif
|
|
||||||
|
|
||||||
lite_include = include_directories('src')
|
|
||||||
foreach data_module : ['core', 'fonts', 'plugins', 'colors']
|
|
||||||
install_subdir('data' / data_module , install_dir : lite_datadir)
|
|
||||||
endforeach
|
|
||||||
|
|
||||||
install_data('licenses/licenses.md', install_dir : lite_docdir)
|
|
||||||
|
|
||||||
lite_link_args = []
|
|
||||||
if cc.get_id() == 'gcc' and get_option('buildtype') == 'release'
|
|
||||||
lite_link_args += ['-static-libgcc', '-static-libstdc++']
|
|
||||||
endif
|
|
||||||
if host_machine.system() == 'darwin'
|
|
||||||
lite_link_args += ['-framework', 'CoreServices', '-framework', 'Foundation']
|
|
||||||
endif
|
|
||||||
|
|
||||||
lite_rc = []
|
|
||||||
if host_machine.system() == 'windows'
|
|
||||||
windows = import('windows')
|
|
||||||
lite_rc += windows.compile_resources('resources/icons/icon.rc')
|
|
||||||
iss = configure_file(input : 'scripts/innosetup/innosetup.iss.in',
|
|
||||||
output : 'innosetup.iss',
|
|
||||||
configuration : conf_data)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# On macos we need to use the SDL renderer to support retina displays
|
# On macos we need to use the SDL renderer to support retina displays
|
||||||
if get_option('renderer') or host_machine.system() == 'darwin'
|
if get_option('renderer') or host_machine.system() == 'darwin'
|
||||||
lite_cargs += '-DLITE_USE_SDL_RENDERER'
|
lite_cargs += '-DLITE_USE_SDL_RENDERER'
|
||||||
endif
|
endif
|
||||||
|
#===============================================================================
|
||||||
|
# Linker Settings
|
||||||
|
#===============================================================================
|
||||||
|
lite_link_args = []
|
||||||
|
if cc.get_id() == 'gcc' and get_option('buildtype') == 'release'
|
||||||
|
lite_link_args += ['-static-libgcc']
|
||||||
|
endif
|
||||||
|
|
||||||
subdir('lib/font_renderer')
|
if host_machine.system() == 'darwin'
|
||||||
subdir('src')
|
lite_link_args += ['-framework', 'CoreServices', '-framework', 'Foundation']
|
||||||
|
endif
|
||||||
|
#===============================================================================
|
||||||
|
# Dependencies
|
||||||
|
#===============================================================================
|
||||||
|
if not get_option('source-only')
|
||||||
|
libm = cc.find_library('m', required : false)
|
||||||
|
libdl = cc.find_library('dl', required : false)
|
||||||
|
lua_dep = dependency('lua5.2', fallback: ['lua', 'lua_dep'],
|
||||||
|
default_options: ['shared=false', 'use_readline=false', 'app=false']
|
||||||
|
)
|
||||||
|
pcre2_dep = dependency('libpcre2-8')
|
||||||
|
freetype_dep = dependency('freetype2')
|
||||||
|
sdl_dep = dependency('sdl2', method: 'config-tool')
|
||||||
|
reproc_dep = dependency('reproc', fallback: ['reproc', 'reproc_dep'],
|
||||||
|
default_options: [
|
||||||
|
'default_library=static', 'multithreaded=false',
|
||||||
|
'reproc-cpp=false', 'examples=false'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
lite_deps = [lua_dep, sdl_dep, reproc_dep, pcre2_dep, libm, libdl, freetype_dep]
|
||||||
|
|
||||||
|
if host_machine.system() == 'windows'
|
||||||
|
# Note that we need to explicitly add the windows socket DLL because
|
||||||
|
# the pkg-config file from reproc does not include it.
|
||||||
|
lite_deps += meson.get_compiler('c').find_library('ws2_32', required: true)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
#===============================================================================
|
||||||
|
# Install Configuration
|
||||||
|
#===============================================================================
|
||||||
|
if get_option('portable') or host_machine.system() == 'windows'
|
||||||
|
lite_bindir = '/'
|
||||||
|
lite_docdir = '/doc'
|
||||||
|
lite_datadir = '/data'
|
||||||
|
elif get_option('bundle') and host_machine.system() == 'darwin'
|
||||||
|
lite_cargs += '-DMACOS_USE_BUNDLE'
|
||||||
|
lite_bindir = 'Contents/MacOS'
|
||||||
|
lite_docdir = 'Contents/Resources'
|
||||||
|
lite_datadir = 'Contents/Resources'
|
||||||
|
install_data('resources/icons/icon.icns', install_dir : 'Contents/Resources')
|
||||||
|
configure_file(
|
||||||
|
input : 'resources/macos/Info.plist.in',
|
||||||
|
output : 'Info.plist',
|
||||||
|
configuration : conf_data,
|
||||||
|
install : true,
|
||||||
|
install_dir : 'Contents'
|
||||||
|
)
|
||||||
|
else
|
||||||
|
lite_bindir = 'bin'
|
||||||
|
lite_docdir = 'share/doc/lite-xl'
|
||||||
|
lite_datadir = 'share/lite-xl'
|
||||||
|
if host_machine.system() == 'linux'
|
||||||
|
install_data('resources/icons/lite-xl.svg',
|
||||||
|
install_dir : 'share/icons/hicolor/scalable/apps'
|
||||||
|
)
|
||||||
|
install_data('resources/linux/org.lite_xl.lite_xl.desktop',
|
||||||
|
install_dir : 'share/applications'
|
||||||
|
)
|
||||||
|
install_data('resources/linux/org.lite_xl.lite_xl.appdata.xml',
|
||||||
|
install_dir : 'share/metainfo'
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
install_data('licenses/licenses.md', install_dir : lite_docdir)
|
||||||
|
|
||||||
|
install_subdir('data' / 'core' , install_dir : lite_datadir, exclude_files : 'start.lua')
|
||||||
|
foreach data_module : ['fonts', 'plugins', 'colors']
|
||||||
|
install_subdir('data' / data_module , install_dir : lite_datadir)
|
||||||
|
endforeach
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
input : 'data/core/start.lua',
|
||||||
|
output : 'start.lua',
|
||||||
|
configuration : conf_data,
|
||||||
|
install : true,
|
||||||
|
install_dir : lite_datadir / 'core',
|
||||||
|
)
|
||||||
|
|
||||||
|
#===============================================================================
|
||||||
|
# Targets
|
||||||
|
#===============================================================================
|
||||||
|
if not get_option('source-only')
|
||||||
|
subdir('src')
|
||||||
|
subdir('scripts')
|
||||||
|
endif
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
option('innosetup', type : 'boolean', value : false, description: 'Build Windows setup package')
|
option('bundle', type : 'boolean', value : false, description: 'Build a macOS bundle')
|
||||||
|
option('source-only', type : 'boolean', value : false, description: 'Configure source files only, doesn\'t checks for dependencies')
|
||||||
option('portable', type : 'boolean', value : false, description: 'Portable install')
|
option('portable', type : 'boolean', value : false, description: 'Portable install')
|
||||||
option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer')
|
option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer')
|
||||||
option('version', type : 'string', value : '0.0.0', description: 'Project version')
|
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,33 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<component type="desktop">
|
||||||
|
<id>org.lite_xl.lite_xl</id>
|
||||||
|
<metadata_license>MIT</metadata_license>
|
||||||
|
<project_license>MIT</project_license>
|
||||||
|
<name>Lite XL</name>
|
||||||
|
<summary>A lightweight text editor written in Lua</summary>
|
||||||
|
<content_rating type="oars-1.0" />
|
||||||
|
|
||||||
|
<description>
|
||||||
|
<p>
|
||||||
|
Lite XL is a text editor and development tool written mainly in Lua,
|
||||||
|
on top of a minimalistic C core using the SDL2 graphics library.
|
||||||
|
</p>
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<caption>The editor window</caption>
|
||||||
|
<image>https://lite-xl.github.io/assets/img/screenshots/editor.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
|
||||||
|
<url type="homepage">https://lite-xl.github.io</url>
|
||||||
|
|
||||||
|
<provides>
|
||||||
|
<binary>lite-xl</binary>
|
||||||
|
</provides>
|
||||||
|
|
||||||
|
<releases>
|
||||||
|
<release version="2.0.1" date="2021-08-28" />
|
||||||
|
</releases>
|
||||||
|
</component>
|
|
@ -5,6 +5,6 @@ Comment=A lightweight text editor written in Lua
|
||||||
Exec=lite-xl %F
|
Exec=lite-xl %F
|
||||||
Icon=lite-xl
|
Icon=lite-xl
|
||||||
Terminal=false
|
Terminal=false
|
||||||
StartupNotify=false
|
StartupWMClass=lite-xl
|
||||||
Categories=Utility;TextEditor;Development;
|
Categories=Development;IDE;
|
||||||
MimeType=text/plain;
|
MimeType=text/plain;
|
|
@ -0,0 +1,360 @@
|
||||||
|
#ifndef LITE_XL_PLUGIN_API
|
||||||
|
#define LITE_XL_PLUGIN_API
|
||||||
|
/*
|
||||||
|
The lite_xl plugin API is quite simple. Any shared library can be a plugin file, so long
|
||||||
|
as it has an entrypoint that looks like the following, where xxxxx is the plugin name:
|
||||||
|
|
||||||
|
#include "lite_xl_plugin_api.h"
|
||||||
|
|
||||||
|
int lua_open_lite_xl_xxxxx(lua_State* L, void* XL) {
|
||||||
|
lite_xl_plugin_init(XL);
|
||||||
|
...
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
In linux, to compile this file, you'd do: 'gcc -o xxxxx.so -shared xxxxx.c'. Simple!
|
||||||
|
Due to the way the API is structured, you *should not* link or include lua libraries.
|
||||||
|
|
||||||
|
This file was automatically generated by the below code. Do NOT MODIFY DIRECTLY.
|
||||||
|
|
||||||
|
|
||||||
|
#!/bin/sh
|
||||||
|
echo "#ifndef LITE_XL_PLUGIN_API"
|
||||||
|
echo "#define LITE_XL_PLUGIN_API"
|
||||||
|
echo "/* "
|
||||||
|
echo "The lite_xl plugin API is quite simple. Any shared library can be a plugin file, so long"
|
||||||
|
echo "as it has an entrypoint that looks like the following, where xxxxx is the plugin name:"
|
||||||
|
echo
|
||||||
|
echo '#include "lite_xl_plugin_api.h"'
|
||||||
|
echo
|
||||||
|
echo "int lua_open_lite_xl_xxxxx(lua_State* L, void* XL) {"
|
||||||
|
echo " lite_xl_plugin_init(XL);"
|
||||||
|
echo " ..."
|
||||||
|
echo " return 1;"
|
||||||
|
echo "}"
|
||||||
|
echo
|
||||||
|
echo "In linux, to compile this file, you'd do: 'gcc -o xxxxx.so -shared xxxxx.c'. Simple!"
|
||||||
|
echo "Due to the way the API is structured, you *should not* link or include lua libraries."
|
||||||
|
echo
|
||||||
|
echo "This file was automatically generated by the below code. Do NOT MODIFY DIRECTLY."
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
cat $0
|
||||||
|
echo "*""/"
|
||||||
|
echo "#include <stddef.h>"
|
||||||
|
echo "typedef struct lua_State lua_State; typedef double lua_Number; typedef int (*lua_CFunction)(lua_State*); typedef ptrdiff_t lua_Integer;"
|
||||||
|
echo "typedef unsigned long lua_Unsigned; typedef struct luaL_Buffer luaL_Buffer; typedef struct luaL_Reg luaL_Reg; typedef struct lua_Debug lua_Debug;"
|
||||||
|
echo "typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);"
|
||||||
|
echo "typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);"
|
||||||
|
echo "typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud);"
|
||||||
|
LUA_HEADERS=`pkg-config --cflags lua5.2 | sed 's/^-I//' | sed 's/$/\/*.h/'`
|
||||||
|
grep -h "^LUA\(LIB\)*_API" $LUA_HEADERS | sed "s/LUA\(LIB\)*_API //" | sed "s/(lua/(*lua/" | grep -v ",\s*$" | sed "s/^/static /"
|
||||||
|
grep -h "#define luaL*_" $LUA_HEADERS | grep -v "\\\s*$" | grep -v "\(assert\|lock\)" | grep -v "\(asm\|int32\)" | grep -v "#define lua_number2integer(i,n)\s*lua_number2int(i, n)"
|
||||||
|
echo "#define lua_pushliteral(L, s) lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1)"
|
||||||
|
echo "#define lua_pushglobaltable(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)"
|
||||||
|
echo "#define IMPORT_SYMBOL(name, ret, ...) name = (ret (*)(__VA_ARGS__))symbol(#name)"
|
||||||
|
echo "static void lite_xl_plugin_init(void* XL) {"
|
||||||
|
echo "\tvoid* (*symbol)(const char*) = (void* (*)(const char*))XL;"
|
||||||
|
grep -h "^LUA\(LIB\)*_API" $LUA_HEADERS | sed "s/LUA\(LIB\)*_API //" | sed "s/(lua/(*lua/" | grep -v ",\s*$" | sed "s/^\([^)]*\)(\*\(lua\w*\))\s*(/\tIMPORT_SYMBOL(\2, \1,/"
|
||||||
|
echo "}"
|
||||||
|
echo "#endif"
|
||||||
|
*/
|
||||||
|
#include <stddef.h>
|
||||||
|
typedef struct lua_State lua_State; typedef double lua_Number; typedef int (*lua_CFunction)(lua_State*); typedef ptrdiff_t lua_Integer;
|
||||||
|
typedef unsigned long lua_Unsigned; typedef struct luaL_Buffer luaL_Buffer; typedef struct luaL_Reg luaL_Reg; typedef struct lua_Debug lua_Debug;
|
||||||
|
typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);
|
||||||
|
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
|
||||||
|
typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud);
|
||||||
|
static void (*luaL_checkversion_) (lua_State *L, lua_Number ver);
|
||||||
|
static int (*luaL_getmetafield) (lua_State *L, int obj, const char *e);
|
||||||
|
static int (*luaL_callmeta) (lua_State *L, int obj, const char *e);
|
||||||
|
static const char *(*luaL_tolstring) (lua_State *L, int idx, size_t *len);
|
||||||
|
static int (*luaL_argerror) (lua_State *L, int numarg, const char *extramsg);
|
||||||
|
static lua_Number (*luaL_checknumber) (lua_State *L, int numArg);
|
||||||
|
static lua_Number (*luaL_optnumber) (lua_State *L, int nArg, lua_Number def);
|
||||||
|
static lua_Integer (*luaL_checkinteger) (lua_State *L, int numArg);
|
||||||
|
static lua_Unsigned (*luaL_checkunsigned) (lua_State *L, int numArg);
|
||||||
|
static void (*luaL_checkstack) (lua_State *L, int sz, const char *msg);
|
||||||
|
static void (*luaL_checktype) (lua_State *L, int narg, int t);
|
||||||
|
static void (*luaL_checkany) (lua_State *L, int narg);
|
||||||
|
static int (*luaL_newmetatable) (lua_State *L, const char *tname);
|
||||||
|
static void (*luaL_setmetatable) (lua_State *L, const char *tname);
|
||||||
|
static void *(*luaL_testudata) (lua_State *L, int ud, const char *tname);
|
||||||
|
static void *(*luaL_checkudata) (lua_State *L, int ud, const char *tname);
|
||||||
|
static void (*luaL_where) (lua_State *L, int lvl);
|
||||||
|
static int (*luaL_error) (lua_State *L, const char *fmt, ...);
|
||||||
|
static int (*luaL_fileresult) (lua_State *L, int stat, const char *fname);
|
||||||
|
static int (*luaL_execresult) (lua_State *L, int stat);
|
||||||
|
static int (*luaL_ref) (lua_State *L, int t);
|
||||||
|
static void (*luaL_unref) (lua_State *L, int t, int ref);
|
||||||
|
static int (*luaL_loadstring) (lua_State *L, const char *s);
|
||||||
|
static lua_State *(*luaL_newstate) (void);
|
||||||
|
static int (*luaL_len) (lua_State *L, int idx);
|
||||||
|
static void (*luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);
|
||||||
|
static int (*luaL_getsubtable) (lua_State *L, int idx, const char *fname);
|
||||||
|
static void (*luaL_buffinit) (lua_State *L, luaL_Buffer *B);
|
||||||
|
static char *(*luaL_prepbuffsize) (luaL_Buffer *B, size_t sz);
|
||||||
|
static void (*luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l);
|
||||||
|
static void (*luaL_addstring) (luaL_Buffer *B, const char *s);
|
||||||
|
static void (*luaL_addvalue) (luaL_Buffer *B);
|
||||||
|
static void (*luaL_pushresult) (luaL_Buffer *B);
|
||||||
|
static void (*luaL_pushresultsize) (luaL_Buffer *B, size_t sz);
|
||||||
|
static char *(*luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz);
|
||||||
|
static lua_State *(*lua_newstate) (lua_Alloc f, void *ud);
|
||||||
|
static void (*lua_close) (lua_State *L);
|
||||||
|
static lua_State *(*lua_newthread) (lua_State *L);
|
||||||
|
static lua_CFunction (*lua_atpanic) (lua_State *L, lua_CFunction panicf);
|
||||||
|
static const lua_Number *(*lua_version) (lua_State *L);
|
||||||
|
static int (*lua_absindex) (lua_State *L, int idx);
|
||||||
|
static int (*lua_gettop) (lua_State *L);
|
||||||
|
static void (*lua_settop) (lua_State *L, int idx);
|
||||||
|
static void (*lua_pushvalue) (lua_State *L, int idx);
|
||||||
|
static void (*lua_remove) (lua_State *L, int idx);
|
||||||
|
static void (*lua_insert) (lua_State *L, int idx);
|
||||||
|
static void (*lua_replace) (lua_State *L, int idx);
|
||||||
|
static void (*lua_copy) (lua_State *L, int fromidx, int toidx);
|
||||||
|
static int (*lua_checkstack) (lua_State *L, int sz);
|
||||||
|
static void (*lua_xmove) (lua_State *from, lua_State *to, int n);
|
||||||
|
static int (*lua_isnumber) (lua_State *L, int idx);
|
||||||
|
static int (*lua_isstring) (lua_State *L, int idx);
|
||||||
|
static int (*lua_iscfunction) (lua_State *L, int idx);
|
||||||
|
static int (*lua_isuserdata) (lua_State *L, int idx);
|
||||||
|
static int (*lua_type) (lua_State *L, int idx);
|
||||||
|
static const char *(*lua_typename) (lua_State *L, int tp);
|
||||||
|
static lua_Number (*lua_tonumberx) (lua_State *L, int idx, int *isnum);
|
||||||
|
static lua_Integer (*lua_tointegerx) (lua_State *L, int idx, int *isnum);
|
||||||
|
static lua_Unsigned (*lua_tounsignedx) (lua_State *L, int idx, int *isnum);
|
||||||
|
static int (*lua_toboolean) (lua_State *L, int idx);
|
||||||
|
static const char *(*lua_tolstring) (lua_State *L, int idx, size_t *len);
|
||||||
|
static size_t (*lua_rawlen) (lua_State *L, int idx);
|
||||||
|
static lua_CFunction (*lua_tocfunction) (lua_State *L, int idx);
|
||||||
|
static void *(*lua_touserdata) (lua_State *L, int idx);
|
||||||
|
static lua_State *(*lua_tothread) (lua_State *L, int idx);
|
||||||
|
static const void *(*lua_topointer) (lua_State *L, int idx);
|
||||||
|
static void (*lua_arith) (lua_State *L, int op);
|
||||||
|
static int (*lua_rawequal) (lua_State *L, int idx1, int idx2);
|
||||||
|
static int (*lua_compare) (lua_State *L, int idx1, int idx2, int op);
|
||||||
|
static void (*lua_pushnil) (lua_State *L);
|
||||||
|
static void (*lua_pushnumber) (lua_State *L, lua_Number n);
|
||||||
|
static void (*lua_pushinteger) (lua_State *L, lua_Integer n);
|
||||||
|
static void (*lua_pushunsigned) (lua_State *L, lua_Unsigned n);
|
||||||
|
static const char *(*lua_pushlstring) (lua_State *L, const char *s, size_t l);
|
||||||
|
static const char *(*lua_pushstring) (lua_State *L, const char *s);
|
||||||
|
static const char *(*lua_pushfstring) (lua_State *L, const char *fmt, ...);
|
||||||
|
static void (*lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
|
||||||
|
static void (*lua_pushboolean) (lua_State *L, int b);
|
||||||
|
static void (*lua_pushlightuserdata) (lua_State *L, void *p);
|
||||||
|
static int (*lua_pushthread) (lua_State *L);
|
||||||
|
static void (*lua_getglobal) (lua_State *L, const char *var);
|
||||||
|
static void (*lua_gettable) (lua_State *L, int idx);
|
||||||
|
static void (*lua_getfield) (lua_State *L, int idx, const char *k);
|
||||||
|
static void (*lua_rawget) (lua_State *L, int idx);
|
||||||
|
static void (*lua_rawgeti) (lua_State *L, int idx, int n);
|
||||||
|
static void (*lua_rawgetp) (lua_State *L, int idx, const void *p);
|
||||||
|
static void (*lua_createtable) (lua_State *L, int narr, int nrec);
|
||||||
|
static void *(*lua_newuserdata) (lua_State *L, size_t sz);
|
||||||
|
static int (*lua_getmetatable) (lua_State *L, int objindex);
|
||||||
|
static void (*lua_getuservalue) (lua_State *L, int idx);
|
||||||
|
static void (*lua_setglobal) (lua_State *L, const char *var);
|
||||||
|
static void (*lua_settable) (lua_State *L, int idx);
|
||||||
|
static void (*lua_setfield) (lua_State *L, int idx, const char *k);
|
||||||
|
static void (*lua_rawset) (lua_State *L, int idx);
|
||||||
|
static void (*lua_rawseti) (lua_State *L, int idx, int n);
|
||||||
|
static void (*lua_rawsetp) (lua_State *L, int idx, const void *p);
|
||||||
|
static int (*lua_setmetatable) (lua_State *L, int objindex);
|
||||||
|
static void (*lua_setuservalue) (lua_State *L, int idx);
|
||||||
|
static int (*lua_getctx) (lua_State *L, int *ctx);
|
||||||
|
static int (*lua_dump) (lua_State *L, lua_Writer writer, void *data);
|
||||||
|
static int (*lua_resume) (lua_State *L, lua_State *from, int narg);
|
||||||
|
static int (*lua_status) (lua_State *L);
|
||||||
|
static int (*lua_gc) (lua_State *L, int what, int data);
|
||||||
|
static int (*lua_error) (lua_State *L);
|
||||||
|
static int (*lua_next) (lua_State *L, int idx);
|
||||||
|
static void (*lua_concat) (lua_State *L, int n);
|
||||||
|
static void (*lua_len) (lua_State *L, int idx);
|
||||||
|
static lua_Alloc (*lua_getallocf) (lua_State *L, void **ud);
|
||||||
|
static void (*lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
|
||||||
|
static int (*lua_getstack) (lua_State *L, int level, lua_Debug *ar);
|
||||||
|
static int (*lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar);
|
||||||
|
static const char *(*lua_getlocal) (lua_State *L, const lua_Debug *ar, int n);
|
||||||
|
static const char *(*lua_setlocal) (lua_State *L, const lua_Debug *ar, int n);
|
||||||
|
static const char *(*lua_getupvalue) (lua_State *L, int funcindex, int n);
|
||||||
|
static const char *(*lua_setupvalue) (lua_State *L, int funcindex, int n);
|
||||||
|
static void *(*lua_upvalueid) (lua_State *L, int fidx, int n);
|
||||||
|
static int (*lua_sethook) (lua_State *L, lua_Hook func, int mask, int count);
|
||||||
|
static lua_Hook (*lua_gethook) (lua_State *L);
|
||||||
|
static int (*lua_gethookmask) (lua_State *L);
|
||||||
|
static int (*lua_gethookcount) (lua_State *L);
|
||||||
|
static void (*luaL_openlibs) (lua_State *L);
|
||||||
|
#define luaL_checkversion(L) luaL_checkversion_(L, LUA_VERSION_NUM)
|
||||||
|
#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL)
|
||||||
|
#define luaL_newlib(L,l) (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
|
||||||
|
#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL))
|
||||||
|
#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL))
|
||||||
|
#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))
|
||||||
|
#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d)))
|
||||||
|
#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n)))
|
||||||
|
#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d)))
|
||||||
|
#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i)))
|
||||||
|
#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
|
||||||
|
#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))
|
||||||
|
#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL)
|
||||||
|
#define luaL_addsize(B,s) ((B)->n += (s))
|
||||||
|
#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
|
||||||
|
#define luaL_register(L,n,l) (luaL_openlib(L,(n),(l),0))
|
||||||
|
#define lua_h
|
||||||
|
#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i))
|
||||||
|
#define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL)
|
||||||
|
#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL)
|
||||||
|
#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL)
|
||||||
|
#define lua_tonumber(L,i) lua_tonumberx(L,i,NULL)
|
||||||
|
#define lua_tointeger(L,i) lua_tointegerx(L,i,NULL)
|
||||||
|
#define lua_tounsigned(L,i) lua_tounsignedx(L,i,NULL)
|
||||||
|
#define lua_pop(L,n) lua_settop(L, -(n)-1)
|
||||||
|
#define lua_newtable(L) lua_createtable(L, 0, 0)
|
||||||
|
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
|
||||||
|
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)
|
||||||
|
#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION)
|
||||||
|
#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE)
|
||||||
|
#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
|
||||||
|
#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL)
|
||||||
|
#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN)
|
||||||
|
#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD)
|
||||||
|
#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE)
|
||||||
|
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0)
|
||||||
|
#define lua_tostring(L,i) lua_tolstring(L, (i), NULL)
|
||||||
|
#define lua_strlen(L,i) lua_rawlen(L, (i))
|
||||||
|
#define lua_objlen(L,i) lua_rawlen(L, (i))
|
||||||
|
#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ)
|
||||||
|
#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT)
|
||||||
|
#define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n))
|
||||||
|
#define lua_str2number(s,p) strtod((s), (p))
|
||||||
|
#define lua_strx2number(s,p) strtod((s), (p))
|
||||||
|
#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 IMPORT_SYMBOL(name, ret, ...) name = (ret (*)(__VA_ARGS__))symbol(#name)
|
||||||
|
static void lite_xl_plugin_init(void* XL) {
|
||||||
|
void* (*symbol)(const char*) = (void* (*)(const char*))XL;
|
||||||
|
IMPORT_SYMBOL(luaL_checkversion_, void ,lua_State *L, lua_Number ver);
|
||||||
|
IMPORT_SYMBOL(luaL_getmetafield, int ,lua_State *L, int obj, const char *e);
|
||||||
|
IMPORT_SYMBOL(luaL_callmeta, int ,lua_State *L, int obj, const char *e);
|
||||||
|
IMPORT_SYMBOL(luaL_tolstring, const char *,lua_State *L, int idx, size_t *len);
|
||||||
|
IMPORT_SYMBOL(luaL_argerror, int ,lua_State *L, int numarg, const char *extramsg);
|
||||||
|
IMPORT_SYMBOL(luaL_checknumber, lua_Number ,lua_State *L, int numArg);
|
||||||
|
IMPORT_SYMBOL(luaL_optnumber, lua_Number ,lua_State *L, int nArg, lua_Number def);
|
||||||
|
IMPORT_SYMBOL(luaL_checkinteger, lua_Integer ,lua_State *L, int numArg);
|
||||||
|
IMPORT_SYMBOL(luaL_checkunsigned, lua_Unsigned ,lua_State *L, int numArg);
|
||||||
|
IMPORT_SYMBOL(luaL_checkstack, void ,lua_State *L, int sz, const char *msg);
|
||||||
|
IMPORT_SYMBOL(luaL_checktype, void ,lua_State *L, int narg, int t);
|
||||||
|
IMPORT_SYMBOL(luaL_checkany, void ,lua_State *L, int narg);
|
||||||
|
IMPORT_SYMBOL(luaL_newmetatable, int ,lua_State *L, const char *tname);
|
||||||
|
IMPORT_SYMBOL(luaL_setmetatable, void ,lua_State *L, const char *tname);
|
||||||
|
IMPORT_SYMBOL(luaL_testudata, void *,lua_State *L, int ud, const char *tname);
|
||||||
|
IMPORT_SYMBOL(luaL_checkudata, void *,lua_State *L, int ud, const char *tname);
|
||||||
|
IMPORT_SYMBOL(luaL_where, void ,lua_State *L, int lvl);
|
||||||
|
IMPORT_SYMBOL(luaL_error, int ,lua_State *L, const char *fmt, ...);
|
||||||
|
IMPORT_SYMBOL(luaL_fileresult, int ,lua_State *L, int stat, const char *fname);
|
||||||
|
IMPORT_SYMBOL(luaL_execresult, int ,lua_State *L, int stat);
|
||||||
|
IMPORT_SYMBOL(luaL_ref, int ,lua_State *L, int t);
|
||||||
|
IMPORT_SYMBOL(luaL_unref, void ,lua_State *L, int t, int ref);
|
||||||
|
IMPORT_SYMBOL(luaL_loadstring, int ,lua_State *L, const char *s);
|
||||||
|
IMPORT_SYMBOL(luaL_newstate, lua_State *,void);
|
||||||
|
IMPORT_SYMBOL(luaL_len, int ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(luaL_setfuncs, void ,lua_State *L, const luaL_Reg *l, int nup);
|
||||||
|
IMPORT_SYMBOL(luaL_getsubtable, int ,lua_State *L, int idx, const char *fname);
|
||||||
|
IMPORT_SYMBOL(luaL_buffinit, void ,lua_State *L, luaL_Buffer *B);
|
||||||
|
IMPORT_SYMBOL(luaL_prepbuffsize, char *,luaL_Buffer *B, size_t sz);
|
||||||
|
IMPORT_SYMBOL(luaL_addlstring, void ,luaL_Buffer *B, const char *s, size_t l);
|
||||||
|
IMPORT_SYMBOL(luaL_addstring, void ,luaL_Buffer *B, const char *s);
|
||||||
|
IMPORT_SYMBOL(luaL_addvalue, void ,luaL_Buffer *B);
|
||||||
|
IMPORT_SYMBOL(luaL_pushresult, void ,luaL_Buffer *B);
|
||||||
|
IMPORT_SYMBOL(luaL_pushresultsize, void ,luaL_Buffer *B, size_t sz);
|
||||||
|
IMPORT_SYMBOL(luaL_buffinitsize, char *,lua_State *L, luaL_Buffer *B, size_t sz);
|
||||||
|
IMPORT_SYMBOL(lua_newstate, lua_State *,lua_Alloc f, void *ud);
|
||||||
|
IMPORT_SYMBOL(lua_close, void ,lua_State *L);
|
||||||
|
IMPORT_SYMBOL(lua_newthread, lua_State *,lua_State *L);
|
||||||
|
IMPORT_SYMBOL(lua_atpanic, lua_CFunction ,lua_State *L, lua_CFunction panicf);
|
||||||
|
IMPORT_SYMBOL(lua_version, const lua_Number *,lua_State *L);
|
||||||
|
IMPORT_SYMBOL(lua_absindex, int ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_gettop, int ,lua_State *L);
|
||||||
|
IMPORT_SYMBOL(lua_settop, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_pushvalue, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_remove, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_insert, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_replace, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_copy, void ,lua_State *L, int fromidx, int toidx);
|
||||||
|
IMPORT_SYMBOL(lua_checkstack, int ,lua_State *L, int sz);
|
||||||
|
IMPORT_SYMBOL(lua_xmove, void ,lua_State *from, lua_State *to, int n);
|
||||||
|
IMPORT_SYMBOL(lua_isnumber, int ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_isstring, int ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_iscfunction, int ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_isuserdata, int ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_type, int ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_typename, const char *,lua_State *L, int tp);
|
||||||
|
IMPORT_SYMBOL(lua_tonumberx, lua_Number ,lua_State *L, int idx, int *isnum);
|
||||||
|
IMPORT_SYMBOL(lua_tointegerx, lua_Integer ,lua_State *L, int idx, int *isnum);
|
||||||
|
IMPORT_SYMBOL(lua_tounsignedx, lua_Unsigned ,lua_State *L, int idx, int *isnum);
|
||||||
|
IMPORT_SYMBOL(lua_toboolean, int ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_tolstring, const char *,lua_State *L, int idx, size_t *len);
|
||||||
|
IMPORT_SYMBOL(lua_rawlen, size_t ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_tocfunction, lua_CFunction ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_touserdata, void *,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_tothread, lua_State *,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_topointer, const void *,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_arith, void ,lua_State *L, int op);
|
||||||
|
IMPORT_SYMBOL(lua_rawequal, int ,lua_State *L, int idx1, int idx2);
|
||||||
|
IMPORT_SYMBOL(lua_compare, int ,lua_State *L, int idx1, int idx2, int op);
|
||||||
|
IMPORT_SYMBOL(lua_pushnil, void ,lua_State *L);
|
||||||
|
IMPORT_SYMBOL(lua_pushnumber, void ,lua_State *L, lua_Number n);
|
||||||
|
IMPORT_SYMBOL(lua_pushinteger, void ,lua_State *L, lua_Integer n);
|
||||||
|
IMPORT_SYMBOL(lua_pushunsigned, void ,lua_State *L, lua_Unsigned n);
|
||||||
|
IMPORT_SYMBOL(lua_pushlstring, const char *,lua_State *L, const char *s, size_t l);
|
||||||
|
IMPORT_SYMBOL(lua_pushstring, const char *,lua_State *L, const char *s);
|
||||||
|
IMPORT_SYMBOL(lua_pushfstring, const char *,lua_State *L, const char *fmt, ...);
|
||||||
|
IMPORT_SYMBOL(lua_pushcclosure, void ,lua_State *L, lua_CFunction fn, int n);
|
||||||
|
IMPORT_SYMBOL(lua_pushboolean, void ,lua_State *L, int b);
|
||||||
|
IMPORT_SYMBOL(lua_pushlightuserdata, void ,lua_State *L, void *p);
|
||||||
|
IMPORT_SYMBOL(lua_pushthread, int ,lua_State *L);
|
||||||
|
IMPORT_SYMBOL(lua_getglobal, void ,lua_State *L, const char *var);
|
||||||
|
IMPORT_SYMBOL(lua_gettable, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_getfield, void ,lua_State *L, int idx, const char *k);
|
||||||
|
IMPORT_SYMBOL(lua_rawget, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_rawgeti, void ,lua_State *L, int idx, int n);
|
||||||
|
IMPORT_SYMBOL(lua_rawgetp, void ,lua_State *L, int idx, const void *p);
|
||||||
|
IMPORT_SYMBOL(lua_createtable, void ,lua_State *L, int narr, int nrec);
|
||||||
|
IMPORT_SYMBOL(lua_newuserdata, void *,lua_State *L, size_t sz);
|
||||||
|
IMPORT_SYMBOL(lua_getmetatable, int ,lua_State *L, int objindex);
|
||||||
|
IMPORT_SYMBOL(lua_getuservalue, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_setglobal, void ,lua_State *L, const char *var);
|
||||||
|
IMPORT_SYMBOL(lua_settable, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_setfield, void ,lua_State *L, int idx, const char *k);
|
||||||
|
IMPORT_SYMBOL(lua_rawset, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_rawseti, void ,lua_State *L, int idx, int n);
|
||||||
|
IMPORT_SYMBOL(lua_rawsetp, void ,lua_State *L, int idx, const void *p);
|
||||||
|
IMPORT_SYMBOL(lua_setmetatable, int ,lua_State *L, int objindex);
|
||||||
|
IMPORT_SYMBOL(lua_setuservalue, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_getctx, int ,lua_State *L, int *ctx);
|
||||||
|
IMPORT_SYMBOL(lua_dump, int ,lua_State *L, lua_Writer writer, void *data);
|
||||||
|
IMPORT_SYMBOL(lua_resume, int ,lua_State *L, lua_State *from, int narg);
|
||||||
|
IMPORT_SYMBOL(lua_status, int ,lua_State *L);
|
||||||
|
IMPORT_SYMBOL(lua_gc, int ,lua_State *L, int what, int data);
|
||||||
|
IMPORT_SYMBOL(lua_error, int ,lua_State *L);
|
||||||
|
IMPORT_SYMBOL(lua_next, int ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_concat, void ,lua_State *L, int n);
|
||||||
|
IMPORT_SYMBOL(lua_len, void ,lua_State *L, int idx);
|
||||||
|
IMPORT_SYMBOL(lua_getallocf, lua_Alloc ,lua_State *L, void **ud);
|
||||||
|
IMPORT_SYMBOL(lua_setallocf, void ,lua_State *L, lua_Alloc f, void *ud);
|
||||||
|
IMPORT_SYMBOL(lua_getstack, int ,lua_State *L, int level, lua_Debug *ar);
|
||||||
|
IMPORT_SYMBOL(lua_getinfo, int ,lua_State *L, const char *what, lua_Debug *ar);
|
||||||
|
IMPORT_SYMBOL(lua_getlocal, const char *,lua_State *L, const lua_Debug *ar, int n);
|
||||||
|
IMPORT_SYMBOL(lua_setlocal, const char *,lua_State *L, const lua_Debug *ar, int n);
|
||||||
|
IMPORT_SYMBOL(lua_getupvalue, const char *,lua_State *L, int funcindex, int n);
|
||||||
|
IMPORT_SYMBOL(lua_setupvalue, const char *,lua_State *L, int funcindex, int n);
|
||||||
|
IMPORT_SYMBOL(lua_upvalueid, void *,lua_State *L, int fidx, int n);
|
||||||
|
IMPORT_SYMBOL(lua_sethook, int ,lua_State *L, lua_Hook func, int mask, int count);
|
||||||
|
IMPORT_SYMBOL(lua_gethook, lua_Hook ,lua_State *L);
|
||||||
|
IMPORT_SYMBOL(lua_gethookmask, int ,lua_State *L);
|
||||||
|
IMPORT_SYMBOL(lua_gethookcount, int ,lua_State *L);
|
||||||
|
IMPORT_SYMBOL(luaL_openlibs, void ,lua_State *L);
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -2,25 +2,30 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>lite-xl</string>
|
<string>lite-xl</string>
|
||||||
<key>CFBundleGetInfoString</key>
|
<key>CFBundleGetInfoString</key>
|
||||||
<string>lite-xl</string>
|
<string>lite-xl</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>icon</string>
|
<string>icon.icns</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>lite-xl</string>
|
<string>Lite XL</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>MinimumOSVersion</key><string>10.13</string>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<key>NSDocumentsFolderUsageDescription</key><string>To access, edit and index your projects.</string>
|
<string>10.11</string>
|
||||||
<key>NSDesktopFolderUsageDescription</key><string>To access, edit and index your projects.</string>
|
<key>NSDocumentsFolderUsageDescription</key>
|
||||||
<key>NSDownloadsFolderUsageDescription</key><string>To access, edit and index your projects.</string>
|
<string>To access, edit and index your projects.</string>
|
||||||
|
<key>NSDesktopFolderUsageDescription</key>
|
||||||
|
<string>To access, edit and index your projects.</string>
|
||||||
|
<key>NSDownloadsFolderUsageDescription</key>
|
||||||
|
<string>To access, edit and index your projects.</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.16.10</string>
|
<string>@PROJECT_VERSION@</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>© 2019-2021 Francesco Abbate</string>
|
<string>© 2019-2021 Francesco Abbate</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
|
@ -0,0 +1,29 @@
|
||||||
|
# Scripts
|
||||||
|
|
||||||
|
Various scripts and configurations used to configure, build, and package Lite XL.
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
- **build.sh**
|
||||||
|
- **build-packages.sh**: In root directory, as all in one script; relies to the
|
||||||
|
ones in this directory.
|
||||||
|
|
||||||
|
### Package
|
||||||
|
|
||||||
|
- **appdmg.sh**: Create a macOS DMG image using [AppDMG][1].
|
||||||
|
- **appimage.sh**: [AppImage][2] builder.
|
||||||
|
- **innosetup.sh**: Creates a 32/64 bit [InnoSetup][3] installer package.
|
||||||
|
- **package.sh**: Creates all binary / DMG image / installer / source packages.
|
||||||
|
|
||||||
|
### Utility
|
||||||
|
|
||||||
|
- **common.sh**: Common functions used by other scripts.
|
||||||
|
- **install-dependencies.sh**: Installs required applications to build, package
|
||||||
|
and run Lite XL, mainly useful for CI and documentation purpose.
|
||||||
|
Preferably not to be used in user systems.
|
||||||
|
- **fontello-config.json**: Used by the icons generator.
|
||||||
|
- **keymap-generator**: Generates a JSON file containing the keymap
|
||||||
|
|
||||||
|
[1]: https://github.com/LinusU/node-appdmg
|
||||||
|
[2]: https://docs.appimage.org/
|
||||||
|
[3]: https://jrsoftware.org/isinfo.php
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
if [ ! -e "src/api/api.h" ]; then
|
||||||
|
echo "Please run this script from the root directory of Lite XL."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > lite-xl-dmg.json << EOF
|
||||||
|
{
|
||||||
|
"title": "Lite XL",
|
||||||
|
"icon": "$(pwd)/resources/icons/icon.icns",
|
||||||
|
"background": "$(pwd)/resources/macos/appdmg.png",
|
||||||
|
"window": {
|
||||||
|
"position": {
|
||||||
|
"x": 360,
|
||||||
|
"y": 360
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 480,
|
||||||
|
"height": 360
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contents": [
|
||||||
|
{ "x": 144, "y": 248, "type": "file", "path": "$(pwd)/Lite XL.app" },
|
||||||
|
{ "x": 336, "y": 248, "type": "link", "path": "/Applications" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
~/node_modules/appdmg/bin/appdmg.js lite-xl-dmg.json "$(pwd)/$1.dmg"
|
|
@ -0,0 +1,162 @@
|
||||||
|
#!/bin/env bash
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
if [ ! -e "src/api/api.h" ]; then
|
||||||
|
echo "Please run this script from the root directory of Lite XL."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source scripts/common.sh
|
||||||
|
|
||||||
|
show_help(){
|
||||||
|
echo
|
||||||
|
echo "Usage: $0 <OPTIONS>"
|
||||||
|
echo
|
||||||
|
echo "Available options:"
|
||||||
|
echo
|
||||||
|
echo "-h --help Show this help and exits."
|
||||||
|
echo "-b --builddir DIRNAME Sets the name of the build dir (no path)."
|
||||||
|
echo " Default: 'build'."
|
||||||
|
echo "-n --nobuild Skips the build step, use existing files."
|
||||||
|
echo "-s --static Specify if building using static libraries"
|
||||||
|
echo " by using lhelper tool."
|
||||||
|
echo "-v --version VERSION Specify a version, non whitespace separated string."
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
ARCH="$(uname -m)"
|
||||||
|
BUILD_DIR="$(get_default_build_dir)"
|
||||||
|
RUN_BUILD=true
|
||||||
|
STATIC_BUILD=false
|
||||||
|
|
||||||
|
for i in "$@"; do
|
||||||
|
case $i in
|
||||||
|
-h|--belp)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-b|--builddir)
|
||||||
|
BUILD_DIR="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-n|--nobuild)
|
||||||
|
RUN_BUILD=false
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-s|--static)
|
||||||
|
STATIC_BUILD=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-v|--version)
|
||||||
|
VERSION="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# unknown option
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# TODO: Versioning using git
|
||||||
|
#if [[ -z $VERSION && -d .git ]]; then
|
||||||
|
# VERSION=$(git describe --tags --long | sed 's/^v//; s/\([^-]*-g\)/r\1/; s/-/./g')
|
||||||
|
#fi
|
||||||
|
|
||||||
|
if [[ -n $1 ]]; then
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
setup_appimagetool() {
|
||||||
|
if ! which appimagetool > /dev/null ; then
|
||||||
|
if [ ! -e appimagetool ]; then
|
||||||
|
if ! wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${ARCH}.AppImage" ; then
|
||||||
|
echo "Could not download the appimagetool for the arch '${ARCH}'."
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
chmod 0755 appimagetool
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
download_appimage_apprun() {
|
||||||
|
if [ ! -e AppRun ]; then
|
||||||
|
if ! wget -O AppRun "https://github.com/AppImage/AppImageKit/releases/download/continuous/AppRun-${ARCH}" ; then
|
||||||
|
echo "Could not download AppRun for the arch '${ARCH}'."
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
chmod 0755 AppRun
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
build_litexl() {
|
||||||
|
if [ -e build ]; then
|
||||||
|
rm -rf build
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e ${BUILD_DIR} ]; then
|
||||||
|
rm -rf ${BUILD_DIR}
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Build lite-xl..."
|
||||||
|
sleep 1
|
||||||
|
meson setup --buildtype=release --prefix /usr ${BUILD_DIR}
|
||||||
|
meson compile -C ${BUILD_DIR}
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_appimage() {
|
||||||
|
if [ -e LiteXL.AppDir ]; then
|
||||||
|
rm -rf LiteXL.AppDir
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating LiteXL.AppDir..."
|
||||||
|
|
||||||
|
DESTDIR="$(realpath LiteXL.AppDir)" meson install --skip-subprojects -C ${BUILD_DIR}
|
||||||
|
mv AppRun LiteXL.AppDir/
|
||||||
|
# These could be symlinks but it seems they doesn't work with AppimageLauncher
|
||||||
|
cp resources/icons/lite-xl.svg LiteXL.AppDir/
|
||||||
|
cp resources/linux/org.lite_xl.lite_xl.desktop LiteXL.AppDir/
|
||||||
|
|
||||||
|
if [[ $STATIC_BUILD == false ]]; then
|
||||||
|
echo "Copying libraries..."
|
||||||
|
|
||||||
|
mkdir -p LiteXL.AppDir/usr/lib/
|
||||||
|
|
||||||
|
local allowed_libs=(
|
||||||
|
libfreetype
|
||||||
|
libpcre2
|
||||||
|
libSDL2
|
||||||
|
libsndio
|
||||||
|
liblua
|
||||||
|
)
|
||||||
|
|
||||||
|
while read line; do
|
||||||
|
local libname="$(echo $line | cut -d' ' -f1)"
|
||||||
|
local libpath="$(echo $line | cut -d' ' -f2)"
|
||||||
|
for lib in "${allowed_libs[@]}" ; do
|
||||||
|
if echo "$libname" | grep "$lib" > /dev/null ; then
|
||||||
|
cp "$libpath" LiteXL.AppDir/usr/lib/
|
||||||
|
continue 2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo " Ignoring: $libname"
|
||||||
|
done < <(ldd build/src/lite-xl | awk '{print $1 " " $3}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Generating AppImage..."
|
||||||
|
local version=""
|
||||||
|
if [ -n "$VERSION" ]; then
|
||||||
|
version="-$VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
./appimagetool LiteXL.AppDir LiteXL${version}-${ARCH}.AppImage
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_appimagetool
|
||||||
|
download_appimage_apprun
|
||||||
|
if [[ $RUN_BUILD == true ]]; then build_litexl; fi
|
||||||
|
generate_appimage $1
|
|
@ -0,0 +1,117 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ ! -e "src/api/api.h" ]; then
|
||||||
|
echo "Please run this script from the root directory of Lite XL."; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source scripts/common.sh
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
echo
|
||||||
|
echo "Usage: $0 <OPTIONS>"
|
||||||
|
echo
|
||||||
|
echo "Available options:"
|
||||||
|
echo
|
||||||
|
echo "-b --builddir DIRNAME Sets the name of the build directory (not path)."
|
||||||
|
echo " Default: '$(get_default_build_dir)'."
|
||||||
|
echo " --debug Debug this script."
|
||||||
|
echo "-f --forcefallback Force to build dependencies statically."
|
||||||
|
echo "-h --help Show this help and exit."
|
||||||
|
echo "-p --prefix PREFIX Install directory prefix. Default: '/'."
|
||||||
|
echo "-B --bundle Create an App bundle (macOS only)"
|
||||||
|
echo "-P --portable Create a portable binary package."
|
||||||
|
echo "-O --pgo Use profile guided optimizations (pgo)."
|
||||||
|
echo " macOS: disabled when used with --bundle,"
|
||||||
|
echo " Windows: Implicit being the only option."
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local platform="$(get_platform_name)"
|
||||||
|
local build_dir="$(get_default_build_dir)"
|
||||||
|
local prefix=/
|
||||||
|
local force_fallback
|
||||||
|
local bundle
|
||||||
|
local portable
|
||||||
|
local pgo
|
||||||
|
|
||||||
|
for i in "$@"; do
|
||||||
|
case $i in
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-b|--builddir)
|
||||||
|
build_dir="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--debug)
|
||||||
|
set -x
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-f|--forcefallback)
|
||||||
|
force_fallback="--wrap-mode=forcefallback"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-p|--prefix)
|
||||||
|
prefix="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-B|--bundle)
|
||||||
|
if [[ "$platform" != "macos" ]]; then
|
||||||
|
echo "Warning: ignoring --bundle option, works only under macOS."
|
||||||
|
else
|
||||||
|
bundle="-Dbundle=true"
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-P|--portable)
|
||||||
|
portable="-Dportable=true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-O|--pgo)
|
||||||
|
pgo="-Db_pgo=generate"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# unknown option
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n $1 ]]; then
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $platform == "macos" && -n $bundle && -n $portable ]]; then
|
||||||
|
echo "Warning: \"bundle\" and \"portable\" specified; excluding portable package."
|
||||||
|
portable=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "${build_dir}"
|
||||||
|
|
||||||
|
CFLAGS=$CFLAGS LDFLAGS=$LDFLAGS meson setup \
|
||||||
|
--buildtype=release \
|
||||||
|
--prefix "$prefix" \
|
||||||
|
$force_fallback \
|
||||||
|
$bundle \
|
||||||
|
$portable \
|
||||||
|
$pgo \
|
||||||
|
"${build_dir}"
|
||||||
|
|
||||||
|
meson compile -C "${build_dir}"
|
||||||
|
|
||||||
|
if [ ! -z ${pgo+x} ]; then
|
||||||
|
cp -r data "${build_dir}/src"
|
||||||
|
"${build_dir}/src/lite-xl"
|
||||||
|
meson configure -Db_pgo=use "${build_dir}"
|
||||||
|
meson compile -C "${build_dir}"
|
||||||
|
rm -fr "${build_dir}/data"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
get_platform_name() {
|
||||||
|
if [[ "$OSTYPE" == "msys" ]]; then
|
||||||
|
echo "windows"
|
||||||
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
echo "macos"
|
||||||
|
elif [[ "$OSTYPE" == "linux"* || "$OSTYPE" == "freebsd"* ]]; then
|
||||||
|
echo "linux"
|
||||||
|
else
|
||||||
|
echo "UNSUPPORTED-OS"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_default_build_dir() {
|
||||||
|
platform=$(get_platform_name)
|
||||||
|
echo "build-$platform-$(uname -m)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $(get_platform_name) == "UNSUPPORTED-OS" ]]; then
|
||||||
|
echo "Error: unknown OS type: \"$OSTYPE\""
|
||||||
|
exit 1
|
||||||
|
fi
|
|
@ -4,12 +4,12 @@
|
||||||
#define MyAppURL "https://lite-xl.github.io"
|
#define MyAppURL "https://lite-xl.github.io"
|
||||||
#define MyAppExeName "lite-xl.exe"
|
#define MyAppExeName "lite-xl.exe"
|
||||||
#define BuildDir "@PROJECT_BUILD_DIR@"
|
#define BuildDir "@PROJECT_BUILD_DIR@"
|
||||||
#define SourceDir "."
|
#define SourceDir "@PROJECT_SOURCE_DIR@"
|
||||||
|
|
||||||
; Use /dArch option to create a setup for a different architecture, e.g.:
|
; Use /dArch option to create a setup for a different architecture, e.g.:
|
||||||
; iscc /dArch=x86 innosetup.iss
|
; iscc /dArch=x86 innosetup.iss
|
||||||
#ifndef Arch
|
#ifndef Arch
|
||||||
#define Arch "x64"
|
#define Arch "x64"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
|
@ -27,14 +27,17 @@ AppSupportURL={#MyAppURL}
|
||||||
AppUpdatesURL={#MyAppURL}
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
|
||||||
#if Arch=="x64"
|
#if Arch=="x64"
|
||||||
ArchitecturesAllowed=x64
|
ArchitecturesAllowed=x64
|
||||||
ArchitecturesInstallIn64BitMode={#Arch}
|
ArchitecturesInstallIn64BitMode=x64
|
||||||
|
#define ArchInternal "x86_64"
|
||||||
|
#else
|
||||||
|
#define ArchInternal "i686"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
AllowNoIcons=yes
|
AllowNoIcons=yes
|
||||||
Compression=lzma
|
Compression=lzma
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
DefaultDirName={autopf}\{#MyAppName}
|
DefaultDirName={autopf}/{#MyAppName}
|
||||||
DefaultGroupName={#MyAppPublisher}
|
DefaultGroupName={#MyAppPublisher}
|
||||||
UninstallFilesDir={app}
|
UninstallFilesDir={app}
|
||||||
|
|
||||||
|
@ -48,14 +51,14 @@ PrivilegesRequiredOverridesAllowed=dialog
|
||||||
UsedUserAreasWarning=no
|
UsedUserAreasWarning=no
|
||||||
|
|
||||||
OutputDir=.
|
OutputDir=.
|
||||||
OutputBaseFilename=LiteXL-{#MyAppVersion}-{#Arch}-setup
|
OutputBaseFilename=LiteXL-{#MyAppVersion}-{#ArchInternal}-setup
|
||||||
;DisableDirPage=yes
|
;DisableDirPage=yes
|
||||||
;DisableProgramGroupPage=yes
|
;DisableProgramGroupPage=yes
|
||||||
|
|
||||||
LicenseFile={#SourceDir}\LICENSE
|
LicenseFile={#SourceDir}/LICENSE
|
||||||
SetupIconFile={#SourceDir}\icon.ico
|
SetupIconFile={#SourceDir}/resources/icons/icon.ico
|
||||||
WizardImageFile="wizard-modern-image.bmp"
|
WizardImageFile="{#SourceDir}/scripts/innosetup/wizard-modern-image.bmp"
|
||||||
WizardSmallImageFile="litexl-55px.bmp"
|
WizardSmallImageFile="{#SourceDir}/scripts/innosetup/litexl-55px.bmp"
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
@ -66,18 +69,20 @@ Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescrip
|
||||||
Name: "portablemode"; Description: "Portable Mode"; Flags: unchecked
|
Name: "portablemode"; Description: "Portable Mode"; Flags: unchecked
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "{#BuildDir}\lite-xl.exe"; DestDir: "{app}"; Flags: ignoreversion
|
Source: "{#BuildDir}/src/lite-xl.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
Source: "{#BuildDir}\data\*"; DestDir: "{app}\data"; Flags: ignoreversion recursesubdirs
|
Source: "{#BuildDir}/mingwLibs{#Arch}/*"; DestDir: "{app}"; Flags: ignoreversion ; Check: DirExists(ExpandConstant('{#BuildDir}/mingwLibs{#Arch}'))
|
||||||
|
Source: "{#SourceDir}/data/*"; DestDir: "{app}/data"; Flags: ignoreversion recursesubdirs
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: not IsTaskSelected('portablemode')
|
|
||||||
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"; Check: not IsTaskSelected('portablemode')
|
|
||||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||||
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon; Check: not IsTaskSelected('portablemode')
|
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: not WizardIsTaskSelected('portablemode')
|
||||||
|
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"; Check: not WizardIsTaskSelected('portablemode')
|
||||||
|
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon; Check: not WizardIsTaskSelected('portablemode')
|
||||||
|
; Name: "{usersendto}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
Filename: "{app}/{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
Uninstallable=not IsTaskSelected('portablemode')
|
Uninstallable=not WizardIsTaskSelected('portablemode')
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ ! -e "src/api/api.h" ]; then
|
||||||
|
echo "Please run this script from the root directory of Lite XL."; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source scripts/common.sh
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
echo
|
||||||
|
echo "Usage: $0 <OPTIONS>"
|
||||||
|
echo
|
||||||
|
echo "Available options:"
|
||||||
|
echo
|
||||||
|
echo "-b --builddir DIRNAME Sets the name of the build directory (not path)."
|
||||||
|
echo " Default: '$(get_default_build_dir)'."
|
||||||
|
echo " --debug Debug this script."
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local build_dir=$(get_default_build_dir)
|
||||||
|
local arch
|
||||||
|
|
||||||
|
if [[ $MSYSTEM == "MINGW64" ]]; then arch=x64; else arch=Win32; fi
|
||||||
|
|
||||||
|
for i in "$@"; do
|
||||||
|
case $i in
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-b|--builddir)
|
||||||
|
build_dir="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--debug)
|
||||||
|
set -x
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# unknown option
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n $1 ]]; then
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy MinGW libraries dependencies.
|
||||||
|
# MSYS2 ldd command seems to be only 64bit, so use ntldd
|
||||||
|
# see https://github.com/msys2/MINGW-packages/issues/4164
|
||||||
|
local mingwLibsDir="${build_dir}/mingwLibs$arch"
|
||||||
|
mkdir -p "$mingwLibsDir"
|
||||||
|
ntldd -R "${build_dir}/src/lite-xl.exe" | grep mingw | awk '{print $3}' | sed 's#\\#/#g' | xargs -I '{}' cp -v '{}' $mingwLibsDir
|
||||||
|
|
||||||
|
"/c/Program Files (x86)/Inno Setup 6/ISCC.exe" -dARCH=$arch "${build_dir}/scripts/innosetup.iss"
|
||||||
|
pushd "${build_dir}/scripts"; mv LiteXL*.exe "./../../"; popd
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
|
@ -0,0 +1,74 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
if [ ! -e "src/api/api.h" ]; then
|
||||||
|
echo "Please run this script from the root directory of Lite XL."; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
echo
|
||||||
|
echo "Lite XL dependecies installer. Mainly used for CI but can also work on users systems."
|
||||||
|
echo "USE IT AT YOUR OWN RISK!"
|
||||||
|
echo
|
||||||
|
echo "Usage: $0 <OPTIONS>"
|
||||||
|
echo
|
||||||
|
echo "Available options:"
|
||||||
|
echo
|
||||||
|
echo "-l --lhelper Install tools required by LHelper and doesn't"
|
||||||
|
echo " install external libraries."
|
||||||
|
echo " --debug Debug this script."
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local lhelper=false
|
||||||
|
|
||||||
|
for i in "$@"; do
|
||||||
|
case $i in
|
||||||
|
-s|--lhelper)
|
||||||
|
lhelper=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--debug)
|
||||||
|
set -x
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# unknown option
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n $1 ]]; then
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$OSTYPE" == "linux"* ]]; then
|
||||||
|
if [[ $lhelper == true ]]; then
|
||||||
|
sudo apt-get install -qq ninja-build
|
||||||
|
else
|
||||||
|
sudo apt-get install -qq ninja-build libsdl2-dev libfreetype6
|
||||||
|
fi
|
||||||
|
pip3 install meson
|
||||||
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
if [[ $lhelper == true ]]; then
|
||||||
|
brew install bash md5sha1sum ninja
|
||||||
|
else
|
||||||
|
brew install bash ninja sdl2
|
||||||
|
fi
|
||||||
|
pip3 install meson
|
||||||
|
cd ~; npm install appdmg; cd -
|
||||||
|
~/node_modules/appdmg/bin/appdmg.js --version
|
||||||
|
elif [[ "$OSTYPE" == "msys" ]]; then
|
||||||
|
if [[ $lhelper == true ]]; then
|
||||||
|
pacman --noconfirm -S \
|
||||||
|
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config} unzip
|
||||||
|
else
|
||||||
|
pacman --noconfirm -S \
|
||||||
|
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,freetype,pcre2,SDL2} unzip
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
|
@ -0,0 +1,714 @@
|
||||||
|
-- Module options:
|
||||||
|
local always_try_using_lpeg = true
|
||||||
|
local register_global_module_table = false
|
||||||
|
local global_module_name = 'json'
|
||||||
|
|
||||||
|
--[==[
|
||||||
|
|
||||||
|
David Kolf's JSON module for Lua 5.1/5.2
|
||||||
|
|
||||||
|
Version 2.5
|
||||||
|
|
||||||
|
|
||||||
|
For the documentation see the corresponding readme.txt or visit
|
||||||
|
<http://dkolf.de/src/dkjson-lua.fsl/>.
|
||||||
|
|
||||||
|
You can contact the author by sending an e-mail to 'david' at the
|
||||||
|
domain 'dkolf.de'.
|
||||||
|
|
||||||
|
|
||||||
|
Copyright (C) 2010-2013 David Heiko Kolf
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
--]==]
|
||||||
|
|
||||||
|
-- global dependencies:
|
||||||
|
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
|
||||||
|
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
|
||||||
|
local error, require, pcall, select = error, require, pcall, select
|
||||||
|
local floor, huge = math.floor, math.huge
|
||||||
|
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
|
||||||
|
string.rep, string.gsub, string.sub, string.byte, string.char,
|
||||||
|
string.find, string.len, string.format
|
||||||
|
local strmatch = string.match
|
||||||
|
local concat = table.concat
|
||||||
|
|
||||||
|
local json = { version = "dkjson 2.5" }
|
||||||
|
|
||||||
|
if register_global_module_table then
|
||||||
|
_G[global_module_name] = json
|
||||||
|
end
|
||||||
|
|
||||||
|
local _ENV = nil -- blocking globals in Lua 5.2
|
||||||
|
|
||||||
|
pcall (function()
|
||||||
|
-- Enable access to blocked metatables.
|
||||||
|
-- Don't worry, this module doesn't change anything in them.
|
||||||
|
local debmeta = require "debug".getmetatable
|
||||||
|
if debmeta then getmetatable = debmeta end
|
||||||
|
end)
|
||||||
|
|
||||||
|
json.null = setmetatable ({}, {
|
||||||
|
__tojson = function () return "null" end
|
||||||
|
})
|
||||||
|
|
||||||
|
local function isarray (tbl)
|
||||||
|
local max, n, arraylen = 0, 0, 0
|
||||||
|
for k,v in pairs (tbl) do
|
||||||
|
if k == 'n' and type(v) == 'number' then
|
||||||
|
arraylen = v
|
||||||
|
if v > max then
|
||||||
|
max = v
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if k > max then
|
||||||
|
max = k
|
||||||
|
end
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if max > 10 and max > arraylen and max > n * 2 then
|
||||||
|
return false -- don't create an array with too many holes
|
||||||
|
end
|
||||||
|
return true, max
|
||||||
|
end
|
||||||
|
|
||||||
|
local escapecodes = {
|
||||||
|
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
|
||||||
|
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
|
||||||
|
}
|
||||||
|
|
||||||
|
local function escapeutf8 (uchar)
|
||||||
|
local value = escapecodes[uchar]
|
||||||
|
if value then
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
local a, b, c, d = strbyte (uchar, 1, 4)
|
||||||
|
a, b, c, d = a or 0, b or 0, c or 0, d or 0
|
||||||
|
if a <= 0x7f then
|
||||||
|
value = a
|
||||||
|
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
|
||||||
|
value = (a - 0xc0) * 0x40 + b - 0x80
|
||||||
|
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
|
||||||
|
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
|
||||||
|
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
|
||||||
|
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
|
||||||
|
else
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
if value <= 0xffff then
|
||||||
|
return strformat ("\\u%.4x", value)
|
||||||
|
elseif value <= 0x10ffff then
|
||||||
|
-- encode as UTF-16 surrogate pair
|
||||||
|
value = value - 0x10000
|
||||||
|
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
|
||||||
|
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
|
||||||
|
else
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fsub (str, pattern, repl)
|
||||||
|
-- gsub always builds a new string in a buffer, even when no match
|
||||||
|
-- exists. First using find should be more efficient when most strings
|
||||||
|
-- don't contain the pattern.
|
||||||
|
if strfind (str, pattern) then
|
||||||
|
return gsub (str, pattern, repl)
|
||||||
|
else
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function quotestring (value)
|
||||||
|
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
|
||||||
|
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
|
||||||
|
if strfind (value, "[\194\216\220\225\226\239]") then
|
||||||
|
value = fsub (value, "\194[\128-\159\173]", escapeutf8)
|
||||||
|
value = fsub (value, "\216[\128-\132]", escapeutf8)
|
||||||
|
value = fsub (value, "\220\143", escapeutf8)
|
||||||
|
value = fsub (value, "\225\158[\180\181]", escapeutf8)
|
||||||
|
value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
|
||||||
|
value = fsub (value, "\226\129[\160-\175]", escapeutf8)
|
||||||
|
value = fsub (value, "\239\187\191", escapeutf8)
|
||||||
|
value = fsub (value, "\239\191[\176-\191]", escapeutf8)
|
||||||
|
end
|
||||||
|
return "\"" .. value .. "\""
|
||||||
|
end
|
||||||
|
json.quotestring = quotestring
|
||||||
|
|
||||||
|
local function replace(str, o, n)
|
||||||
|
local i, j = strfind (str, o, 1, true)
|
||||||
|
if i then
|
||||||
|
return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
|
||||||
|
else
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- locale independent num2str and str2num functions
|
||||||
|
local decpoint, numfilter
|
||||||
|
|
||||||
|
local function updatedecpoint ()
|
||||||
|
decpoint = strmatch(tostring(0.5), "([^05+])")
|
||||||
|
-- build a filter that can be used to remove group separators
|
||||||
|
numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
|
||||||
|
end
|
||||||
|
|
||||||
|
updatedecpoint()
|
||||||
|
|
||||||
|
local function num2str (num)
|
||||||
|
return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function str2num (str)
|
||||||
|
local num = tonumber(replace(str, ".", decpoint))
|
||||||
|
if not num then
|
||||||
|
updatedecpoint()
|
||||||
|
num = tonumber(replace(str, ".", decpoint))
|
||||||
|
end
|
||||||
|
return num
|
||||||
|
end
|
||||||
|
|
||||||
|
local function addnewline2 (level, buffer, buflen)
|
||||||
|
buffer[buflen+1] = "\n"
|
||||||
|
buffer[buflen+2] = strrep (" ", level)
|
||||||
|
buflen = buflen + 2
|
||||||
|
return buflen
|
||||||
|
end
|
||||||
|
|
||||||
|
function json.addnewline (state)
|
||||||
|
if state.indent then
|
||||||
|
state.bufferlen = addnewline2 (state.level or 0,
|
||||||
|
state.buffer, state.bufferlen or #(state.buffer))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local encode2 -- forward declaration
|
||||||
|
|
||||||
|
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||||
|
local kt = type (key)
|
||||||
|
if kt ~= 'string' and kt ~= 'number' then
|
||||||
|
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
|
||||||
|
end
|
||||||
|
if prev then
|
||||||
|
buflen = buflen + 1
|
||||||
|
buffer[buflen] = ","
|
||||||
|
end
|
||||||
|
if indent then
|
||||||
|
buflen = addnewline2 (level, buffer, buflen)
|
||||||
|
end
|
||||||
|
buffer[buflen+1] = quotestring (key)
|
||||||
|
buffer[buflen+2] = ":"
|
||||||
|
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function appendcustom(res, buffer, state)
|
||||||
|
local buflen = state.bufferlen
|
||||||
|
if type (res) == 'string' then
|
||||||
|
buflen = buflen + 1
|
||||||
|
buffer[buflen] = res
|
||||||
|
end
|
||||||
|
return buflen
|
||||||
|
end
|
||||||
|
|
||||||
|
local function exception(reason, value, state, buffer, buflen, defaultmessage)
|
||||||
|
defaultmessage = defaultmessage or reason
|
||||||
|
local handler = state.exception
|
||||||
|
if not handler then
|
||||||
|
return nil, defaultmessage
|
||||||
|
else
|
||||||
|
state.bufferlen = buflen
|
||||||
|
local ret, msg = handler (reason, value, state, defaultmessage)
|
||||||
|
if not ret then return nil, msg or defaultmessage end
|
||||||
|
return appendcustom(ret, buffer, state)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function json.encodeexception(reason, value, state, defaultmessage)
|
||||||
|
return quotestring("<" .. defaultmessage .. ">")
|
||||||
|
end
|
||||||
|
|
||||||
|
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
|
||||||
|
local valtype = type (value)
|
||||||
|
local valmeta = getmetatable (value)
|
||||||
|
valmeta = type (valmeta) == 'table' and valmeta -- only tables
|
||||||
|
local valtojson = valmeta and valmeta.__tojson
|
||||||
|
if valtojson then
|
||||||
|
if tables[value] then
|
||||||
|
return exception('reference cycle', value, state, buffer, buflen)
|
||||||
|
end
|
||||||
|
tables[value] = true
|
||||||
|
state.bufferlen = buflen
|
||||||
|
local ret, msg = valtojson (value, state)
|
||||||
|
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
|
||||||
|
tables[value] = nil
|
||||||
|
buflen = appendcustom(ret, buffer, state)
|
||||||
|
elseif value == nil then
|
||||||
|
buflen = buflen + 1
|
||||||
|
buffer[buflen] = "null"
|
||||||
|
elseif valtype == 'number' then
|
||||||
|
local s
|
||||||
|
if value ~= value or value >= huge or -value >= huge then
|
||||||
|
-- This is the behaviour of the original JSON implementation.
|
||||||
|
s = "null"
|
||||||
|
else
|
||||||
|
s = num2str (value)
|
||||||
|
end
|
||||||
|
buflen = buflen + 1
|
||||||
|
buffer[buflen] = s
|
||||||
|
elseif valtype == 'boolean' then
|
||||||
|
buflen = buflen + 1
|
||||||
|
buffer[buflen] = value and "true" or "false"
|
||||||
|
elseif valtype == 'string' then
|
||||||
|
buflen = buflen + 1
|
||||||
|
buffer[buflen] = quotestring (value)
|
||||||
|
elseif valtype == 'table' then
|
||||||
|
if tables[value] then
|
||||||
|
return exception('reference cycle', value, state, buffer, buflen)
|
||||||
|
end
|
||||||
|
tables[value] = true
|
||||||
|
level = level + 1
|
||||||
|
local isa, n = isarray (value)
|
||||||
|
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
|
||||||
|
isa = false
|
||||||
|
end
|
||||||
|
local msg
|
||||||
|
if isa then -- JSON array
|
||||||
|
buflen = buflen + 1
|
||||||
|
buffer[buflen] = "["
|
||||||
|
for i = 1, n do
|
||||||
|
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
|
||||||
|
if not buflen then return nil, msg end
|
||||||
|
if i < n then
|
||||||
|
buflen = buflen + 1
|
||||||
|
buffer[buflen] = ","
|
||||||
|
end
|
||||||
|
end
|
||||||
|
buflen = buflen + 1
|
||||||
|
buffer[buflen] = "]"
|
||||||
|
else -- JSON object
|
||||||
|
local prev = false
|
||||||
|
buflen = buflen + 1
|
||||||
|
buffer[buflen] = "{"
|
||||||
|
local order = valmeta and valmeta.__jsonorder or globalorder
|
||||||
|
if order then
|
||||||
|
local used = {}
|
||||||
|
n = #order
|
||||||
|
for i = 1, n do
|
||||||
|
local k = order[i]
|
||||||
|
local v = value[k]
|
||||||
|
if v then
|
||||||
|
used[k] = true
|
||||||
|
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||||
|
prev = true -- add a seperator before the next element
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for k,v in pairs (value) do
|
||||||
|
if not used[k] then
|
||||||
|
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||||
|
if not buflen then return nil, msg end
|
||||||
|
prev = true -- add a seperator before the next element
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else -- unordered
|
||||||
|
for k,v in pairs (value) do
|
||||||
|
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||||
|
if not buflen then return nil, msg end
|
||||||
|
prev = true -- add a seperator before the next element
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if indent then
|
||||||
|
buflen = addnewline2 (level - 1, buffer, buflen)
|
||||||
|
end
|
||||||
|
buflen = buflen + 1
|
||||||
|
buffer[buflen] = "}"
|
||||||
|
end
|
||||||
|
tables[value] = nil
|
||||||
|
else
|
||||||
|
return exception ('unsupported type', value, state, buffer, buflen,
|
||||||
|
"type '" .. valtype .. "' is not supported by JSON.")
|
||||||
|
end
|
||||||
|
return buflen
|
||||||
|
end
|
||||||
|
|
||||||
|
function json.encode (value, state)
|
||||||
|
state = state or {}
|
||||||
|
local oldbuffer = state.buffer
|
||||||
|
local buffer = oldbuffer or {}
|
||||||
|
state.buffer = buffer
|
||||||
|
updatedecpoint()
|
||||||
|
local ret, msg = encode2 (value, state.indent, state.level or 0,
|
||||||
|
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
|
||||||
|
if not ret then
|
||||||
|
error (msg, 2)
|
||||||
|
elseif oldbuffer == buffer then
|
||||||
|
state.bufferlen = ret
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
state.bufferlen = nil
|
||||||
|
state.buffer = nil
|
||||||
|
return concat (buffer)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function loc (str, where)
|
||||||
|
local line, pos, linepos = 1, 1, 0
|
||||||
|
while true do
|
||||||
|
pos = strfind (str, "\n", pos, true)
|
||||||
|
if pos and pos < where then
|
||||||
|
line = line + 1
|
||||||
|
linepos = pos
|
||||||
|
pos = pos + 1
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return "line " .. line .. ", column " .. (where - linepos)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function unterminated (str, what, where)
|
||||||
|
return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function scanwhite (str, pos)
|
||||||
|
while true do
|
||||||
|
pos = strfind (str, "%S", pos)
|
||||||
|
if not pos then return nil end
|
||||||
|
local sub2 = strsub (str, pos, pos + 1)
|
||||||
|
if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
|
||||||
|
-- UTF-8 Byte Order Mark
|
||||||
|
pos = pos + 3
|
||||||
|
elseif sub2 == "//" then
|
||||||
|
pos = strfind (str, "[\n\r]", pos + 2)
|
||||||
|
if not pos then return nil end
|
||||||
|
elseif sub2 == "/*" then
|
||||||
|
pos = strfind (str, "*/", pos + 2)
|
||||||
|
if not pos then return nil end
|
||||||
|
pos = pos + 2
|
||||||
|
else
|
||||||
|
return pos
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local escapechars = {
|
||||||
|
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
|
||||||
|
["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
|
||||||
|
}
|
||||||
|
|
||||||
|
local function unichar (value)
|
||||||
|
if value < 0 then
|
||||||
|
return nil
|
||||||
|
elseif value <= 0x007f then
|
||||||
|
return strchar (value)
|
||||||
|
elseif value <= 0x07ff then
|
||||||
|
return strchar (0xc0 + floor(value/0x40),
|
||||||
|
0x80 + (floor(value) % 0x40))
|
||||||
|
elseif value <= 0xffff then
|
||||||
|
return strchar (0xe0 + floor(value/0x1000),
|
||||||
|
0x80 + (floor(value/0x40) % 0x40),
|
||||||
|
0x80 + (floor(value) % 0x40))
|
||||||
|
elseif value <= 0x10ffff then
|
||||||
|
return strchar (0xf0 + floor(value/0x40000),
|
||||||
|
0x80 + (floor(value/0x1000) % 0x40),
|
||||||
|
0x80 + (floor(value/0x40) % 0x40),
|
||||||
|
0x80 + (floor(value) % 0x40))
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function scanstring (str, pos)
|
||||||
|
local lastpos = pos + 1
|
||||||
|
local buffer, n = {}, 0
|
||||||
|
while true do
|
||||||
|
local nextpos = strfind (str, "[\"\\]", lastpos)
|
||||||
|
if not nextpos then
|
||||||
|
return unterminated (str, "string", pos)
|
||||||
|
end
|
||||||
|
if nextpos > lastpos then
|
||||||
|
n = n + 1
|
||||||
|
buffer[n] = strsub (str, lastpos, nextpos - 1)
|
||||||
|
end
|
||||||
|
if strsub (str, nextpos, nextpos) == "\"" then
|
||||||
|
lastpos = nextpos + 1
|
||||||
|
break
|
||||||
|
else
|
||||||
|
local escchar = strsub (str, nextpos + 1, nextpos + 1)
|
||||||
|
local value
|
||||||
|
if escchar == "u" then
|
||||||
|
value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
|
||||||
|
if value then
|
||||||
|
local value2
|
||||||
|
if 0xD800 <= value and value <= 0xDBff then
|
||||||
|
-- we have the high surrogate of UTF-16. Check if there is a
|
||||||
|
-- low surrogate escaped nearby to combine them.
|
||||||
|
if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
|
||||||
|
value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
|
||||||
|
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
|
||||||
|
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
|
||||||
|
else
|
||||||
|
value2 = nil -- in case it was out of range for a low surrogate
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
value = value and unichar (value)
|
||||||
|
if value then
|
||||||
|
if value2 then
|
||||||
|
lastpos = nextpos + 12
|
||||||
|
else
|
||||||
|
lastpos = nextpos + 6
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not value then
|
||||||
|
value = escapechars[escchar] or escchar
|
||||||
|
lastpos = nextpos + 2
|
||||||
|
end
|
||||||
|
n = n + 1
|
||||||
|
buffer[n] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if n == 1 then
|
||||||
|
return buffer[1], lastpos
|
||||||
|
elseif n > 1 then
|
||||||
|
return concat (buffer), lastpos
|
||||||
|
else
|
||||||
|
return "", lastpos
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local scanvalue -- forward declaration
|
||||||
|
|
||||||
|
local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
|
||||||
|
local len = strlen (str)
|
||||||
|
local tbl, n = {}, 0
|
||||||
|
local pos = startpos + 1
|
||||||
|
if what == 'object' then
|
||||||
|
setmetatable (tbl, objectmeta)
|
||||||
|
else
|
||||||
|
setmetatable (tbl, arraymeta)
|
||||||
|
end
|
||||||
|
while true do
|
||||||
|
pos = scanwhite (str, pos)
|
||||||
|
if not pos then return unterminated (str, what, startpos) end
|
||||||
|
local char = strsub (str, pos, pos)
|
||||||
|
if char == closechar then
|
||||||
|
return tbl, pos + 1
|
||||||
|
end
|
||||||
|
local val1, err
|
||||||
|
val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||||
|
if err then return nil, pos, err end
|
||||||
|
pos = scanwhite (str, pos)
|
||||||
|
if not pos then return unterminated (str, what, startpos) end
|
||||||
|
char = strsub (str, pos, pos)
|
||||||
|
if char == ":" then
|
||||||
|
if val1 == nil then
|
||||||
|
return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
|
||||||
|
end
|
||||||
|
pos = scanwhite (str, pos + 1)
|
||||||
|
if not pos then return unterminated (str, what, startpos) end
|
||||||
|
local val2
|
||||||
|
val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||||
|
if err then return nil, pos, err end
|
||||||
|
tbl[val1] = val2
|
||||||
|
pos = scanwhite (str, pos)
|
||||||
|
if not pos then return unterminated (str, what, startpos) end
|
||||||
|
char = strsub (str, pos, pos)
|
||||||
|
else
|
||||||
|
n = n + 1
|
||||||
|
tbl[n] = val1
|
||||||
|
end
|
||||||
|
if char == "," then
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
|
||||||
|
pos = pos or 1
|
||||||
|
pos = scanwhite (str, pos)
|
||||||
|
if not pos then
|
||||||
|
return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
|
||||||
|
end
|
||||||
|
local char = strsub (str, pos, pos)
|
||||||
|
if char == "{" then
|
||||||
|
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
|
||||||
|
elseif char == "[" then
|
||||||
|
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
|
||||||
|
elseif char == "\"" then
|
||||||
|
return scanstring (str, pos)
|
||||||
|
else
|
||||||
|
local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
|
||||||
|
if pstart then
|
||||||
|
local number = str2num (strsub (str, pstart, pend))
|
||||||
|
if number then
|
||||||
|
return number, pend + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
pstart, pend = strfind (str, "^%a%w*", pos)
|
||||||
|
if pstart then
|
||||||
|
local name = strsub (str, pstart, pend)
|
||||||
|
if name == "true" then
|
||||||
|
return true, pend + 1
|
||||||
|
elseif name == "false" then
|
||||||
|
return false, pend + 1
|
||||||
|
elseif name == "null" then
|
||||||
|
return nullval, pend + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, pos, "no valid JSON value at " .. loc (str, pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function optionalmetatables(...)
|
||||||
|
if select("#", ...) > 0 then
|
||||||
|
return ...
|
||||||
|
else
|
||||||
|
return {__jsontype = 'object'}, {__jsontype = 'array'}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function json.decode (str, pos, nullval, ...)
|
||||||
|
local objectmeta, arraymeta = optionalmetatables(...)
|
||||||
|
return scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||||
|
end
|
||||||
|
|
||||||
|
function json.use_lpeg ()
|
||||||
|
local g = require ("lpeg")
|
||||||
|
|
||||||
|
if g.version() == "0.11" then
|
||||||
|
error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
|
||||||
|
end
|
||||||
|
|
||||||
|
local pegmatch = g.match
|
||||||
|
local P, S, R = g.P, g.S, g.R
|
||||||
|
|
||||||
|
local function ErrorCall (str, pos, msg, state)
|
||||||
|
if not state.msg then
|
||||||
|
state.msg = msg .. " at " .. loc (str, pos)
|
||||||
|
state.pos = pos
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function Err (msg)
|
||||||
|
return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
|
||||||
|
end
|
||||||
|
|
||||||
|
local SingleLineComment = P"//" * (1 - S"\n\r")^0
|
||||||
|
local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
|
||||||
|
local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
|
||||||
|
|
||||||
|
local PlainChar = 1 - S"\"\\\n\r"
|
||||||
|
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
|
||||||
|
local HexDigit = R("09", "af", "AF")
|
||||||
|
local function UTF16Surrogate (match, pos, high, low)
|
||||||
|
high, low = tonumber (high, 16), tonumber (low, 16)
|
||||||
|
if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
|
||||||
|
return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function UTF16BMP (hex)
|
||||||
|
return unichar (tonumber (hex, 16))
|
||||||
|
end
|
||||||
|
local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
|
||||||
|
local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
|
||||||
|
local Char = UnicodeEscape + EscapeSequence + PlainChar
|
||||||
|
local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string")
|
||||||
|
local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
|
||||||
|
local Fractal = P"." * R"09"^0
|
||||||
|
local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
|
||||||
|
local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
|
||||||
|
local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
|
||||||
|
local SimpleValue = Number + String + Constant
|
||||||
|
local ArrayContent, ObjectContent
|
||||||
|
|
||||||
|
-- The functions parsearray and parseobject parse only a single value/pair
|
||||||
|
-- at a time and store them directly to avoid hitting the LPeg limits.
|
||||||
|
local function parsearray (str, pos, nullval, state)
|
||||||
|
local obj, cont
|
||||||
|
local npos
|
||||||
|
local t, nt = {}, 0
|
||||||
|
repeat
|
||||||
|
obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
|
||||||
|
if not npos then break end
|
||||||
|
pos = npos
|
||||||
|
nt = nt + 1
|
||||||
|
t[nt] = obj
|
||||||
|
until cont == 'last'
|
||||||
|
return pos, setmetatable (t, state.arraymeta)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseobject (str, pos, nullval, state)
|
||||||
|
local obj, key, cont
|
||||||
|
local npos
|
||||||
|
local t = {}
|
||||||
|
repeat
|
||||||
|
key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
|
||||||
|
if not npos then break end
|
||||||
|
pos = npos
|
||||||
|
t[key] = obj
|
||||||
|
until cont == 'last'
|
||||||
|
return pos, setmetatable (t, state.objectmeta)
|
||||||
|
end
|
||||||
|
|
||||||
|
local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
|
||||||
|
local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
|
||||||
|
local Value = Space * (Array + Object + SimpleValue)
|
||||||
|
local ExpectedValue = Value + Space * Err "value expected"
|
||||||
|
ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
|
||||||
|
local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
|
||||||
|
ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
|
||||||
|
local DecodeValue = ExpectedValue * g.Cp ()
|
||||||
|
|
||||||
|
function json.decode (str, pos, nullval, ...)
|
||||||
|
local state = {}
|
||||||
|
state.objectmeta, state.arraymeta = optionalmetatables(...)
|
||||||
|
local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
|
||||||
|
if state.msg then
|
||||||
|
return nil, state.pos, state.msg
|
||||||
|
else
|
||||||
|
return obj, retpos
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- use this function only once:
|
||||||
|
json.use_lpeg = function () return json end
|
||||||
|
|
||||||
|
json.using_lpeg = true
|
||||||
|
|
||||||
|
return json -- so you can get the module using json = require "dkjson".use_lpeg()
|
||||||
|
end
|
||||||
|
|
||||||
|
if always_try_using_lpeg then
|
||||||
|
pcall (json.use_lpeg)
|
||||||
|
end
|
||||||
|
|
||||||
|
return json
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
#!/usr/bin/env lua
|
||||||
|
local dkjson = require "dkjson"
|
||||||
|
|
||||||
|
|
||||||
|
local function load_keymap(target, target_map, macos)
|
||||||
|
_G.MACOS = macos
|
||||||
|
package.loaded["core.keymap"] = nil
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
|
||||||
|
if target then
|
||||||
|
keymap.map = {}
|
||||||
|
require(target)
|
||||||
|
end
|
||||||
|
|
||||||
|
target_map = target_map or {}
|
||||||
|
-- keymap.reverse_map does not do this?
|
||||||
|
for key, actions in pairs(keymap.map) do
|
||||||
|
for _, action in ipairs(actions) do
|
||||||
|
target_map[action] = target_map[action] or {}
|
||||||
|
table.insert(target_map[action], key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return target_map
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function normalize(map)
|
||||||
|
local result = {}
|
||||||
|
for action, keys in pairs(map) do
|
||||||
|
local uniq = {}
|
||||||
|
local r = { combination = {}, action = action }
|
||||||
|
for _, v in ipairs(keys) do
|
||||||
|
if not uniq[v] then
|
||||||
|
uniq[v] = true
|
||||||
|
r.combination[#r.combination+1] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result[#result+1] = r
|
||||||
|
end
|
||||||
|
table.sort(result, function(a, b) return a.action < b.action end)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function process_module(mod, filename)
|
||||||
|
local map = {}
|
||||||
|
load_keymap(mod, map)
|
||||||
|
load_keymap(mod, map, true)
|
||||||
|
map = normalize(map)
|
||||||
|
local f = assert(io.open(filename, "wb"))
|
||||||
|
f:write(dkjson.encode(map, { indent = true }))
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
print("Warning: this is not guaranteed to work outside lite-xl's own keymap. Proceed with caution")
|
||||||
|
local LITE_ROOT = arg[1]
|
||||||
|
if not LITE_ROOT then
|
||||||
|
error("LITE_ROOT is not given")
|
||||||
|
end
|
||||||
|
package.path = package.path .. ";" .. LITE_ROOT .. "/?.lua;" .. LITE_ROOT .. "/?/init.lua"
|
||||||
|
|
||||||
|
-- fix core.command (because we don't want load the entire thing)
|
||||||
|
package.loaded["core.command"] = {}
|
||||||
|
|
||||||
|
if #arg > 1 then
|
||||||
|
for i = 2, #arg do
|
||||||
|
process_module(arg[i], arg[i] .. ".json")
|
||||||
|
print(string.format("Exported keymap in %q.", arg[i]))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
process_module(nil, "core.keymap.json")
|
||||||
|
print("Exported the default keymap.")
|
||||||
|
end
|
|
@ -0,0 +1,75 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
echo
|
||||||
|
echo "Usage: $0 <OPTIONS>"
|
||||||
|
echo
|
||||||
|
echo "Available options:"
|
||||||
|
echo
|
||||||
|
echo " --debug Debug this script."
|
||||||
|
echo "-h --help Show this help and exit."
|
||||||
|
echo "-p --prefix PREFIX Install directory prefix."
|
||||||
|
echo " Default: '$HOME/.local'."
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local lhelper_prefix="$HOME/.local"
|
||||||
|
|
||||||
|
for i in "$@"; do
|
||||||
|
case $i in
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-p|--prefix)
|
||||||
|
lhelper_prefix="$2"
|
||||||
|
echo "LHelper prefix set to: \"${lhelper_prefix}\""
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--debug)
|
||||||
|
set -x
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# unknown option
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n $1 ]]; then show_help; exit 1; fi
|
||||||
|
|
||||||
|
if [[ ! -f ${lhelper_prefix}/bin/lhelper ]]; then
|
||||||
|
|
||||||
|
git clone https://github.com/franko/lhelper.git
|
||||||
|
|
||||||
|
# FIXME: This should be set in ~/.bash_profile if not using CI
|
||||||
|
# export PATH="${HOME}/.local/bin:${PATH}"
|
||||||
|
mkdir -p "${lhelper_prefix}/bin"
|
||||||
|
pushd lhelper; bash install "${lhelper_prefix}"; popd
|
||||||
|
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
CC=clang CXX=clang++ lhelper create lite-xl -n
|
||||||
|
else
|
||||||
|
lhelper create lite-xl -n
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Not using $(lhelper activate lite-xl) to support CI
|
||||||
|
source "$(lhelper env-source lite-xl)"
|
||||||
|
|
||||||
|
lhelper install freetype2
|
||||||
|
lhelper install sdl2 2.0.14-wait-event-timeout-1
|
||||||
|
lhelper install pcre2
|
||||||
|
|
||||||
|
# Help MSYS2 to find the SDL2 include and lib directories to avoid errors
|
||||||
|
# during build and linking when using lhelper.
|
||||||
|
if [[ "$OSTYPE" == "msys" ]]; then
|
||||||
|
CFLAGS=-I${LHELPER_ENV_PREFIX}/include/SDL2
|
||||||
|
LDFLAGS=-L${LHELPER_ENV_PREFIX}/lib
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
|
@ -0,0 +1,8 @@
|
||||||
|
if host_machine.system() == 'windows'
|
||||||
|
configure_file(
|
||||||
|
input : 'innosetup/innosetup.iss.in',
|
||||||
|
output : 'innosetup.iss',
|
||||||
|
configuration : conf_data
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ ! -e "src/api/api.h" ]; then
|
||||||
|
echo "Please run this script from the root directory of Lite XL."; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source scripts/common.sh
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
echo
|
||||||
|
echo "Usage: $0 <OPTIONS>"
|
||||||
|
echo
|
||||||
|
echo "Available options:"
|
||||||
|
echo
|
||||||
|
echo "-b --builddir DIRNAME Sets the name of the build directory (not path)."
|
||||||
|
echo " Default: '$(get_default_build_dir)'."
|
||||||
|
echo "-d --destdir DIRNAME Set the name of the package directory (not path)."
|
||||||
|
echo " Default: 'lite-xl'."
|
||||||
|
echo "-h --help Show this help and exit."
|
||||||
|
echo "-p --prefix PREFIX Install directory prefix. Default: '/'."
|
||||||
|
echo "-v --version VERSION Sets the version on the package name."
|
||||||
|
echo " --addons Install 3rd party addons (currently RXI colors)."
|
||||||
|
echo " --debug Debug this script."
|
||||||
|
echo "-A --appimage Create an AppImage (Linux only)."
|
||||||
|
echo "-B --binary Create a normal / portable package or macOS bundle,"
|
||||||
|
echo " depending on how the build was configured. (Default.)"
|
||||||
|
echo "-D --dmg Create a DMG disk image with AppDMG (macOS only)."
|
||||||
|
echo "-I --innosetup Create a InnoSetup package (Windows only)."
|
||||||
|
echo "-S --source Create a source code package,"
|
||||||
|
echo " including subprojects dependencies."
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# Addons installation: some distributions forbid external downloads
|
||||||
|
# so make it as optional module.
|
||||||
|
install_addons() {
|
||||||
|
local build_dir="$1"
|
||||||
|
local data_dir="$2"
|
||||||
|
|
||||||
|
if [[ -d "${build_dir}/third/data/colors" ]]; then
|
||||||
|
echo "Warning: found previous colors addons installation, skipping."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy third party color themes
|
||||||
|
curl --insecure \
|
||||||
|
-L "https://github.com/rxi/lite-colors/archive/master.zip" \
|
||||||
|
-o "${build_dir}/rxi-lite-colors.zip"
|
||||||
|
|
||||||
|
mkdir -p "${build_dir}/third/data/colors"
|
||||||
|
unzip "${build_dir}/rxi-lite-colors.zip" -d "${build_dir}"
|
||||||
|
mv "${build_dir}/lite-colors-master/colors" "${build_dir}/third/data"
|
||||||
|
rm -rf "${build_dir}/lite-colors-master"
|
||||||
|
|
||||||
|
for module_name in colors; do
|
||||||
|
cp -r "${build_dir}/third/data/$module_name" "${data_dir}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
source_package() {
|
||||||
|
local build_dir=build-src
|
||||||
|
local package_name=$1
|
||||||
|
|
||||||
|
rm -rf ${build_dir}
|
||||||
|
rm -rf ${package_name}
|
||||||
|
rm -f ${package_name}.tar.gz
|
||||||
|
|
||||||
|
meson subprojects download
|
||||||
|
meson setup ${build_dir} -Dsource-only=true
|
||||||
|
|
||||||
|
# Note: not using git-archive(-all) because it can't include subprojects ignored by git
|
||||||
|
rsync -arv \
|
||||||
|
--exclude /*build*/ \
|
||||||
|
--exclude *.git* \
|
||||||
|
--exclude lhelper \
|
||||||
|
--exclude lite-xl* \
|
||||||
|
--exclude submodules \
|
||||||
|
. ${package_name}
|
||||||
|
|
||||||
|
cp "${build_dir}/start.lua" "${package_name}/data/core"
|
||||||
|
|
||||||
|
tar rf ${package_name}.tar ${package_name}
|
||||||
|
gzip -9 ${package_name}.tar
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local arch="$(uname -m)"
|
||||||
|
local platform="$(get_platform_name)"
|
||||||
|
local build_dir="$(get_default_build_dir)"
|
||||||
|
local dest_dir=lite-xl
|
||||||
|
local prefix=/
|
||||||
|
local version
|
||||||
|
local addons=false
|
||||||
|
local appimage=false
|
||||||
|
local binary=false
|
||||||
|
local dmg=false
|
||||||
|
local innosetup=false
|
||||||
|
local source=false
|
||||||
|
|
||||||
|
for i in "$@"; do
|
||||||
|
case $i in
|
||||||
|
-b|--builddir)
|
||||||
|
build_dir="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-d|--destdir)
|
||||||
|
dest_dir="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-p|--prefix)
|
||||||
|
prefix="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-v|--version)
|
||||||
|
if [[ -n $2 ]]; then version="-$2"; fi
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-A|--appimage)
|
||||||
|
if [[ "$platform" != "linux" ]]; then
|
||||||
|
echo "Warning: ignoring --appimage option, works only under Linux."
|
||||||
|
else
|
||||||
|
appimage=true
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-B|--binary)
|
||||||
|
binary=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-D|--dmg)
|
||||||
|
if [[ "$platform" != "macos" ]]; then
|
||||||
|
echo "Warning: ignoring --dmg option, works only under macOS."
|
||||||
|
else
|
||||||
|
dmg=true
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-I|--innosetup)
|
||||||
|
if [[ "$platform" != "windows" ]]; then
|
||||||
|
echo "Warning: ignoring --innosetup option, works only under Windows."
|
||||||
|
else
|
||||||
|
innosetup=true
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-S|--source)
|
||||||
|
source=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--addons)
|
||||||
|
addons=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--debug)
|
||||||
|
set -x
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# unknown option
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n $1 ]]; then show_help; exit 1; fi
|
||||||
|
|
||||||
|
# The source package doesn't require a previous build,
|
||||||
|
# nor the following install step, so run it now.
|
||||||
|
if [[ $source == true ]]; then source_package "lite-xl$version-src"; fi
|
||||||
|
|
||||||
|
# No packages request
|
||||||
|
if [[ $appimage == false && $binary == false && $dmg == false && $innosetup == false ]]; then
|
||||||
|
# Source only, return.
|
||||||
|
if [[ $source == true ]]; then return 0; fi
|
||||||
|
# Build the binary package as default instead doing nothing.
|
||||||
|
binary=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "${dest_dir}"
|
||||||
|
|
||||||
|
DESTDIR="$(pwd)/${dest_dir}" meson install -C "${build_dir}"
|
||||||
|
|
||||||
|
local data_dir="$(pwd)/${dest_dir}/data"
|
||||||
|
local exe_file="$(pwd)/${dest_dir}/lite-xl"
|
||||||
|
local package_name=lite-xl$version-$platform-$arch
|
||||||
|
local bundle=false
|
||||||
|
local portable=false
|
||||||
|
local stripcmd="strip"
|
||||||
|
|
||||||
|
if [[ -d "${data_dir}" ]]; then
|
||||||
|
echo "Creating a portable, compressed archive..."
|
||||||
|
portable=true
|
||||||
|
exe_file="$(pwd)/${dest_dir}/lite-xl"
|
||||||
|
if [[ $platform == "windows" ]]; then
|
||||||
|
exe_file="${exe_file}.exe"
|
||||||
|
stripcmd="strip --strip-all"
|
||||||
|
else
|
||||||
|
# Windows archive is always portable
|
||||||
|
package_name+="-portable"
|
||||||
|
fi
|
||||||
|
elif [[ $platform == "macos" && ! -d "${data_dir}" ]]; then
|
||||||
|
data_dir="$(pwd)/${dest_dir}/Contents/Resources"
|
||||||
|
if [[ -d "${data_dir}" ]]; then
|
||||||
|
echo "Creating a macOS bundle application..."
|
||||||
|
bundle=true
|
||||||
|
# Specify "bundle" on compressed archive only, implicit on images
|
||||||
|
if [[ $dmg == false ]]; then package_name+="-bundle"; fi
|
||||||
|
rm -rf "Lite XL.app"; mv "${dest_dir}" "Lite XL.app"
|
||||||
|
dest_dir="Lite XL.app"
|
||||||
|
exe_file="$(pwd)/${dest_dir}/Contents/MacOS/lite-xl"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $bundle == false && $portable == false ]]; then
|
||||||
|
echo "Creating a compressed archive..."
|
||||||
|
data_dir="$(pwd)/${dest_dir}/$prefix/share/lite-xl"
|
||||||
|
exe_file="$(pwd)/${dest_dir}/$prefix/bin/lite-xl"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${data_dir}"
|
||||||
|
|
||||||
|
if [[ $addons == true ]]; then install_addons "${build_dir}" "${data_dir}"; fi
|
||||||
|
|
||||||
|
# TODO: use --skip-subprojects when 0.58.0 will be available on supported
|
||||||
|
# distributions to avoid subprojects' include and lib directories to be copied.
|
||||||
|
# Install Meson with PIP to get the latest version is not always possible.
|
||||||
|
pushd "${dest_dir}"
|
||||||
|
find . -type d -name 'include' -prune -exec rm -rf {} \;
|
||||||
|
find . -type d -name 'lib' -prune -exec rm -rf {} \;
|
||||||
|
find . -type d -empty -delete
|
||||||
|
popd
|
||||||
|
|
||||||
|
$stripcmd "${exe_file}"
|
||||||
|
|
||||||
|
if [[ $binary == true ]]; then
|
||||||
|
rm -f "${package_name}".tar.gz
|
||||||
|
rm -f "${package_name}".zip
|
||||||
|
|
||||||
|
if [[ $platform == "windows" ]]; then
|
||||||
|
zip -9rv ${package_name}.zip ${dest_dir}/*
|
||||||
|
else
|
||||||
|
tar czvf "${package_name}".tar.gz "${dest_dir}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $appimage == true ]]; then source scripts/appimage.sh; fi
|
||||||
|
if [[ $bundle == true && $dmg == true ]]; then source scripts/appdmg.sh "${package_name}"; fi
|
||||||
|
if [[ $innosetup == true ]]; then source scripts/innosetup/innosetup.sh -b "${build_dir}"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
|
@ -10,7 +10,7 @@ copy_directory_from_repo () {
|
||||||
fi
|
fi
|
||||||
local dirname="$1"
|
local dirname="$1"
|
||||||
local destdir="$2"
|
local destdir="$2"
|
||||||
git archive master "$dirname" --format=tar | tar xf - -C "$destdir" "${tar_options[@]}"
|
git archive "$lite_branch" "$dirname" --format=tar | tar xf - -C "$destdir" "${tar_options[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
lite_copy_third_party_modules () {
|
lite_copy_third_party_modules () {
|
||||||
|
@ -23,12 +23,17 @@ lite_copy_third_party_modules () {
|
||||||
rm "$build/rxi-lite-colors.zip"
|
rm "$build/rxi-lite-colors.zip"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lite_branch=master
|
||||||
while [ ! -z ${1+x} ]; do
|
while [ ! -z ${1+x} ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-dir)
|
-dir)
|
||||||
use_dir="$(realpath $2)"
|
use_dir="$(realpath $2)"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
-branch)
|
||||||
|
lite_branch="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "unknown option: $1"
|
echo "unknown option: $1"
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -73,6 +78,8 @@ for filename in $(ls -1 *.zip *.tar.*); do
|
||||||
fi
|
fi
|
||||||
rm "$filename"
|
rm "$filename"
|
||||||
find lite-xl -name lite -exec chmod a+x '{}' \;
|
find lite-xl -name lite -exec chmod a+x '{}' \;
|
||||||
|
start_file=$(find lite-xl -name start.lua)
|
||||||
|
lite_version=$(cat "$start_file" | awk 'match($0, /^\s*VERSION\s*=\s*"(.+)"/, a) { print(a[1]) }')
|
||||||
xcoredir="$(find lite-xl -type d -name 'core')"
|
xcoredir="$(find lite-xl -type d -name 'core')"
|
||||||
coredir="$(dirname $xcoredir)"
|
coredir="$(dirname $xcoredir)"
|
||||||
echo "coredir: $coredir"
|
echo "coredir: $coredir"
|
||||||
|
@ -81,6 +88,7 @@ for filename in $(ls -1 *.zip *.tar.*); do
|
||||||
rm -fr "$coredir/$module_name"
|
rm -fr "$coredir/$module_name"
|
||||||
(cd .. && copy_directory_from_repo --strip-components=1 "data/$module_name" "$workdir/$coredir")
|
(cd .. && copy_directory_from_repo --strip-components=1 "data/$module_name" "$workdir/$coredir")
|
||||||
done
|
done
|
||||||
|
sed -i "s/@PROJECT_VERSION@/$lite_version/g" "$start_file"
|
||||||
for module_name in plugins colors; do
|
for module_name in plugins colors; do
|
||||||
cp -r "third/data/$module_name" "$coredir"
|
cp -r "third/data/$module_name" "$coredir"
|
||||||
done
|
done
|
||||||
|
|
|
@ -47,10 +47,7 @@ if [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "mingw"* ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rundir=".run"
|
rundir=".run"
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
if [ "$option_portable" == on ]; then
|
||||||
bindir="$rundir"
|
|
||||||
datadir="$rundir"
|
|
||||||
elif [ "$option_portable" == on ]; then
|
|
||||||
bindir="$rundir"
|
bindir="$rundir"
|
||||||
datadir="$rundir/data"
|
datadir="$rundir/data"
|
||||||
else
|
else
|
||||||
|
@ -75,9 +72,14 @@ copy_lite_build () {
|
||||||
else
|
else
|
||||||
cp "$builddir/src/lite-xl" "$bindir"
|
cp "$builddir/src/lite-xl" "$bindir"
|
||||||
fi
|
fi
|
||||||
|
mkdir -p "$datadir/core"
|
||||||
for module_name in core plugins colors fonts; do
|
for module_name in core plugins colors fonts; do
|
||||||
cp -r "data/$module_name" "$datadir"
|
cp -r "data/$module_name" "$datadir"
|
||||||
done
|
done
|
||||||
|
# The start.lua file is generated by meson in $builddir but
|
||||||
|
# there is already a start.lua file in data/core so the command below
|
||||||
|
# should be executed after we copy the data/core directory.
|
||||||
|
cp "$builddir/start.lua" "$datadir/core"
|
||||||
}
|
}
|
||||||
|
|
||||||
run_lite () {
|
run_lite () {
|
||||||
|
|
|
@ -6,7 +6,6 @@ int luaopen_renderer(lua_State *L);
|
||||||
int luaopen_regex(lua_State *L);
|
int luaopen_regex(lua_State *L);
|
||||||
int luaopen_process(lua_State *L);
|
int luaopen_process(lua_State *L);
|
||||||
|
|
||||||
|
|
||||||
static const luaL_Reg libs[] = {
|
static const luaL_Reg libs[] = {
|
||||||
{ "system", luaopen_system },
|
{ "system", luaopen_system },
|
||||||
{ "renderer", luaopen_renderer },
|
{ "renderer", luaopen_renderer },
|
||||||
|
@ -16,7 +15,6 @@ static const luaL_Reg libs[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
void api_load_libs(lua_State *L) {
|
void api_load_libs(lua_State *L) {
|
||||||
for (int i = 0; libs[i].name; i++) {
|
for (int i = 0; libs[i].name; i++)
|
||||||
luaL_requiref(L, libs[i].name, libs[i].func, 1);
|
luaL_requiref(L, libs[i].name, libs[i].func, 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
#ifndef API_H
|
#ifndef API_H
|
||||||
#define API_H
|
#define API_H
|
||||||
|
|
||||||
#include "lua.h"
|
#include <lua.h>
|
||||||
#include "lauxlib.h"
|
#include <lauxlib.h>
|
||||||
#include "lualib.h"
|
#include <lualib.h>
|
||||||
|
|
||||||
#define API_TYPE_FONT "Font"
|
#define API_TYPE_FONT "Font"
|
||||||
#define API_TYPE_REPLACE "Replace"
|
|
||||||
#define API_TYPE_PROCESS "Process"
|
#define API_TYPE_PROCESS "Process"
|
||||||
|
|
||||||
void api_load_libs(lua_State *L);
|
void api_load_libs(lua_State *L);
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
#include "api.h"
|
|
||||||
#include "renderer.h"
|
|
||||||
|
|
||||||
|
|
||||||
static int f_new(lua_State *L) {
|
|
||||||
CPReplaceTable *rep_table = lua_newuserdata(L, sizeof(CPReplaceTable));
|
|
||||||
luaL_setmetatable(L, API_TYPE_REPLACE);
|
|
||||||
ren_cp_replace_init(rep_table);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int f_gc(lua_State *L) {
|
|
||||||
CPReplaceTable *rep_table = luaL_checkudata(L, 1, API_TYPE_REPLACE);
|
|
||||||
ren_cp_replace_free(rep_table);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int f_add(lua_State *L) {
|
|
||||||
CPReplaceTable *rep_table = luaL_checkudata(L, 1, API_TYPE_REPLACE);
|
|
||||||
const char *src = luaL_checkstring(L, 2);
|
|
||||||
const char *dst = luaL_checkstring(L, 3);
|
|
||||||
ren_cp_replace_add(rep_table, src, dst);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static const luaL_Reg lib[] = {
|
|
||||||
{ "__gc", f_gc },
|
|
||||||
{ "new", f_new },
|
|
||||||
{ "add", f_add },
|
|
||||||
{ NULL, NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
int luaopen_renderer_replacements(lua_State *L) {
|
|
||||||
luaL_newmetatable(L, API_TYPE_REPLACE);
|
|
||||||
luaL_setfuncs(L, lib, 0);
|
|
||||||
lua_pushvalue(L, -1);
|
|
||||||
lua_setfield(L, -2, "__index");
|
|
||||||
return 1;
|
|
||||||
}
|
|
|
@ -10,28 +10,166 @@
|
||||||
#include <reproc/reproc.h>
|
#include <reproc/reproc.h>
|
||||||
#include "api.h"
|
#include "api.h"
|
||||||
|
|
||||||
#define READ_BUF_SIZE 4096
|
#define READ_BUF_SIZE 2048
|
||||||
|
|
||||||
|
#define L_GETTABLE(L, idx, key, conv, def) ( \
|
||||||
|
lua_getfield(L, idx, key), \
|
||||||
|
conv(L, -1, def) \
|
||||||
|
)
|
||||||
|
|
||||||
|
#define L_GETNUM(L, idx, key, def) L_GETTABLE(L, idx, key, luaL_optnumber, def)
|
||||||
|
#define L_GETSTR(L, idx, key, def) L_GETTABLE(L, idx, key, luaL_optstring, def)
|
||||||
|
|
||||||
|
#define L_SETNUM(L, idx, key, n) (lua_pushnumber(L, n), lua_setfield(L, idx - 1, key))
|
||||||
|
|
||||||
|
#define L_RETURN_REPROC_ERROR(L, code) { \
|
||||||
|
lua_pushnil(L); \
|
||||||
|
lua_pushstring(L, reproc_strerror(code)); \
|
||||||
|
lua_pushnumber(L, code); \
|
||||||
|
return 3; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ASSERT_MALLOC(ptr) \
|
||||||
|
if (ptr == NULL) \
|
||||||
|
L_RETURN_REPROC_ERROR(L, REPROC_ENOMEM)
|
||||||
|
|
||||||
|
#define ASSERT_REPROC_ERRNO(L, code) { \
|
||||||
|
if (code < 0) \
|
||||||
|
L_RETURN_REPROC_ERROR(L, code) \
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
reproc_t * process;
|
reproc_t * process;
|
||||||
lua_State* L;
|
bool running;
|
||||||
|
int returncode;
|
||||||
} process_t;
|
} process_t;
|
||||||
|
|
||||||
static int process_new(lua_State* L)
|
// this function should be called instead of reproc_wait
|
||||||
|
static int poll_process(process_t* proc, int timeout)
|
||||||
{
|
{
|
||||||
process_t* self = (process_t*) lua_newuserdata(
|
int ret = reproc_wait(proc->process, timeout);
|
||||||
L, sizeof(process_t)
|
if (ret != REPROC_ETIMEDOUT) {
|
||||||
|
proc->running = false;
|
||||||
|
proc->returncode = ret;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int kill_process(process_t* proc)
|
||||||
|
{
|
||||||
|
int ret = reproc_stop(
|
||||||
|
proc->process,
|
||||||
|
(reproc_stop_actions) {
|
||||||
|
{REPROC_STOP_KILL, 0},
|
||||||
|
{REPROC_STOP_TERMINATE, 0},
|
||||||
|
{REPROC_STOP_NOOP, 0}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
memset(self, 0, sizeof(process_t));
|
if (ret != REPROC_ETIMEDOUT) {
|
||||||
|
proc->running = false;
|
||||||
|
proc->returncode = ret;
|
||||||
|
}
|
||||||
|
|
||||||
self->process = NULL;
|
return ret;
|
||||||
self->L = L;
|
}
|
||||||
|
|
||||||
luaL_getmetatable(L, API_TYPE_PROCESS);
|
static int process_start(lua_State* L)
|
||||||
lua_setmetatable(L, -2);
|
{
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
if (lua_isnoneornil(L, 2)) {
|
||||||
|
lua_settop(L, 1); // remove the nil if it's there
|
||||||
|
lua_newtable(L);
|
||||||
|
}
|
||||||
|
luaL_checktype(L, 2, LUA_TTABLE);
|
||||||
|
|
||||||
|
int cmd_len = lua_rawlen(L, 1);
|
||||||
|
const char** cmd = malloc(sizeof(char *) * (cmd_len + 1));
|
||||||
|
ASSERT_MALLOC(cmd);
|
||||||
|
cmd[cmd_len] = NULL;
|
||||||
|
|
||||||
|
for(int i = 0; i < cmd_len; i++) {
|
||||||
|
lua_rawgeti(L, 1, i + 1);
|
||||||
|
|
||||||
|
cmd[i] = luaL_checkstring(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int deadline = L_GETNUM(L, 2, "timeout", 0);
|
||||||
|
const char* cwd =L_GETSTR(L, 2, "cwd", NULL);
|
||||||
|
int redirect_in = L_GETNUM(L, 2, "stdin", REPROC_REDIRECT_DEFAULT);
|
||||||
|
int redirect_out = L_GETNUM(L, 2, "stdout", REPROC_REDIRECT_DEFAULT);
|
||||||
|
int redirect_err = L_GETNUM(L, 2, "stderr", REPROC_REDIRECT_DEFAULT);
|
||||||
|
lua_pop(L, 5); // remove args we just read
|
||||||
|
|
||||||
|
if (
|
||||||
|
redirect_in > REPROC_REDIRECT_STDOUT
|
||||||
|
|| redirect_out > REPROC_REDIRECT_STDOUT
|
||||||
|
|| redirect_err > REPROC_REDIRECT_STDOUT)
|
||||||
|
{
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_pushliteral(L, "redirect to handles, FILE* and paths are not supported");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// env
|
||||||
|
luaL_getsubtable(L, 2, "env");
|
||||||
|
const char **env = NULL;
|
||||||
|
int env_len = 0;
|
||||||
|
|
||||||
|
lua_pushnil(L);
|
||||||
|
while (lua_next(L, -2) != 0) {
|
||||||
|
env_len++;
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env_len > 0) {
|
||||||
|
env = malloc(sizeof(char*) * (env_len + 1));
|
||||||
|
env[env_len] = NULL;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
lua_pushnil(L);
|
||||||
|
while (lua_next(L, -2) != 0) {
|
||||||
|
lua_pushliteral(L, "=");
|
||||||
|
lua_pushvalue(L, -3); // push the key to the top
|
||||||
|
lua_concat(L, 3); // key=value
|
||||||
|
|
||||||
|
env[i++] = luaL_checkstring(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reproc_t* proc = reproc_new();
|
||||||
|
int out = reproc_start(
|
||||||
|
proc,
|
||||||
|
(const char* const*) cmd,
|
||||||
|
(reproc_options) {
|
||||||
|
.working_directory = cwd,
|
||||||
|
.deadline = deadline,
|
||||||
|
.nonblocking = true,
|
||||||
|
.env = {
|
||||||
|
.behavior = REPROC_ENV_EXTEND,
|
||||||
|
.extra = env
|
||||||
|
},
|
||||||
|
.redirect = {
|
||||||
|
.in.type = redirect_in,
|
||||||
|
.out.type = redirect_out,
|
||||||
|
.err.type = redirect_err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (out < 0) {
|
||||||
|
reproc_destroy(proc);
|
||||||
|
L_RETURN_REPROC_ERROR(L, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
process_t* self = lua_newuserdata(L, sizeof(process_t));
|
||||||
|
self->process = proc;
|
||||||
|
self->running = true;
|
||||||
|
|
||||||
|
// this is equivalent to using lua_setmetatable()
|
||||||
|
luaL_setmetatable(L, API_TYPE_PROCESS);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,24 +177,20 @@ static int process_strerror(lua_State* L)
|
||||||
{
|
{
|
||||||
int error_code = luaL_checknumber(L, 1);
|
int error_code = luaL_checknumber(L, 1);
|
||||||
|
|
||||||
if(error_code){
|
if (error_code < 0)
|
||||||
lua_pushstring(
|
lua_pushstring(L, reproc_strerror(error_code));
|
||||||
L,
|
else
|
||||||
reproc_strerror(error_code)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_gc(lua_State* L)
|
static int f_gc(lua_State* L)
|
||||||
{
|
{
|
||||||
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||||
|
|
||||||
if(self->process){
|
if(self->process) {
|
||||||
reproc_kill(self->process);
|
kill_process(self);
|
||||||
reproc_destroy(self->process);
|
reproc_destroy(self->process);
|
||||||
self->process = NULL;
|
self->process = NULL;
|
||||||
}
|
}
|
||||||
|
@ -64,330 +198,211 @@ static int process_gc(lua_State* L)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_start(lua_State* L)
|
static int f_tostring(lua_State* L)
|
||||||
{
|
{
|
||||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||||
|
|
||||||
luaL_checktype(L, 2, LUA_TTABLE);
|
|
||||||
|
|
||||||
char* path = NULL;
|
|
||||||
size_t path_len = 0;
|
|
||||||
|
|
||||||
if(lua_type(L, 3) == LUA_TSTRING){
|
|
||||||
path = (char*) lua_tolstring(L, 3, &path_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t deadline = 0;
|
|
||||||
|
|
||||||
if(lua_type(L, 4) == LUA_TNUMBER){
|
|
||||||
deadline = lua_tonumber(L, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t table_len = luaL_len(L, 2);
|
|
||||||
char* command[table_len+1];
|
|
||||||
command[table_len] = NULL;
|
|
||||||
|
|
||||||
int i;
|
|
||||||
for(i=1; i<=table_len; i++){
|
|
||||||
lua_pushnumber(L, i);
|
|
||||||
lua_gettable(L, 2);
|
|
||||||
|
|
||||||
command[i-1] = (char*) lua_tostring(L, -1);
|
|
||||||
|
|
||||||
lua_remove(L, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(self->process){
|
|
||||||
reproc_kill(self->process);
|
|
||||||
reproc_destroy(self->process);
|
|
||||||
}
|
|
||||||
|
|
||||||
self->process = reproc_new();
|
|
||||||
|
|
||||||
int out = reproc_start(
|
|
||||||
self->process,
|
|
||||||
(const char* const*) command,
|
|
||||||
(reproc_options){
|
|
||||||
.working_directory = path,
|
|
||||||
.deadline = deadline,
|
|
||||||
.nonblocking=true,
|
|
||||||
.redirect.err.type=REPROC_REDIRECT_PIPE
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if(out > 0) {
|
|
||||||
lua_pushboolean(L, 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reproc_destroy(self->process);
|
|
||||||
self->process = NULL;
|
|
||||||
lua_pushnumber(L, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
lua_pushliteral(L, API_TYPE_PROCESS);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_pid(lua_State* L)
|
static int f_pid(lua_State* L)
|
||||||
{
|
{
|
||||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||||
|
|
||||||
if(self->process){
|
lua_pushnumber(L, reproc_pid(self->process));
|
||||||
int id = reproc_pid(self->process);
|
return 1;
|
||||||
|
}
|
||||||
if(id > 0){
|
|
||||||
lua_pushnumber(L, id);
|
static int f_returncode(lua_State *L)
|
||||||
} else {
|
{
|
||||||
lua_pushnumber(L, 0);
|
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||||
}
|
int ret = poll_process(self, 0);
|
||||||
} else {
|
|
||||||
lua_pushnumber(L, 0);
|
if (self->running)
|
||||||
}
|
lua_pushnil(L);
|
||||||
|
else
|
||||||
|
lua_pushnumber(L, ret);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int g_read(lua_State* L, int stream)
|
static int g_read(lua_State* L, int stream)
|
||||||
{
|
{
|
||||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||||
|
unsigned long read_size = luaL_optunsigned(L, 2, READ_BUF_SIZE);
|
||||||
|
|
||||||
if(self->process){
|
luaL_Buffer b;
|
||||||
int read_size = READ_BUF_SIZE;
|
uint8_t* buffer = (uint8_t*) luaL_buffinitsize(L, &b, read_size);
|
||||||
if (lua_type(L, 2) == LUA_TNUMBER){
|
|
||||||
read_size = (int) lua_tonumber(L, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
int tries = 1;
|
int out = reproc_read(
|
||||||
if (lua_type(L, 3) == LUA_TNUMBER){
|
self->process,
|
||||||
tries = (int) lua_tonumber(L, 3);
|
stream,
|
||||||
}
|
buffer,
|
||||||
|
read_size
|
||||||
|
);
|
||||||
|
|
||||||
int out = 0;
|
if (out >= 0)
|
||||||
uint8_t buffer[read_size];
|
luaL_addsize(&b, out);
|
||||||
|
luaL_pushresult(&b);
|
||||||
|
|
||||||
int runs;
|
if (out == REPROC_EPIPE) {
|
||||||
for (runs=0; runs<tries; runs++){
|
kill_process(self);
|
||||||
out = reproc_read(
|
ASSERT_REPROC_ERRNO(L, out);
|
||||||
self->process,
|
|
||||||
REPROC_STREAM_OUT,
|
|
||||||
buffer,
|
|
||||||
read_size
|
|
||||||
);
|
|
||||||
|
|
||||||
if (out >= 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(out == REPROC_EPIPE){
|
|
||||||
reproc_kill(self->process);
|
|
||||||
reproc_destroy(self->process);
|
|
||||||
self->process = NULL;
|
|
||||||
|
|
||||||
lua_pushnil(L);
|
|
||||||
} else if(out > 0) {
|
|
||||||
lua_pushlstring(L, (const char*) buffer, out);
|
|
||||||
} else {
|
|
||||||
lua_pushnil(L);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lua_pushnil(L);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_read(lua_State* L)
|
static int f_read_stdout(lua_State* L)
|
||||||
{
|
{
|
||||||
return g_read(L, REPROC_STREAM_OUT);
|
return g_read(L, REPROC_STREAM_OUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_read_errors(lua_State* L)
|
static int f_read_stderr(lua_State* L)
|
||||||
{
|
{
|
||||||
return g_read(L, REPROC_STREAM_ERR);
|
return g_read(L, REPROC_STREAM_ERR);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_write(lua_State* L)
|
static int f_read(lua_State* L)
|
||||||
{
|
{
|
||||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
int stream = luaL_checknumber(L, 2);
|
||||||
|
lua_remove(L, 2);
|
||||||
|
if (stream > REPROC_STREAM_ERR)
|
||||||
|
L_RETURN_REPROC_ERROR(L, REPROC_EINVAL);
|
||||||
|
|
||||||
if(self->process){
|
return g_read(L, stream);
|
||||||
size_t data_size = 0;
|
}
|
||||||
const char* data = luaL_checklstring(L, 2, &data_size);
|
|
||||||
|
|
||||||
int out = 0;
|
static int f_write(lua_State* L)
|
||||||
|
{
|
||||||
|
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||||
|
|
||||||
out = reproc_write(
|
size_t data_size = 0;
|
||||||
self->process,
|
const char* data = luaL_checklstring(L, 2, &data_size);
|
||||||
(uint8_t*) data,
|
|
||||||
data_size
|
|
||||||
);
|
|
||||||
|
|
||||||
if(out == REPROC_EPIPE){
|
int out = reproc_write(
|
||||||
reproc_kill(self->process);
|
self->process,
|
||||||
reproc_destroy(self->process);
|
(uint8_t*) data,
|
||||||
self->process = NULL;
|
data_size
|
||||||
}
|
);
|
||||||
|
if (out == REPROC_EPIPE) {
|
||||||
lua_pushnumber(L, out);
|
kill_process(self);
|
||||||
} else {
|
L_RETURN_REPROC_ERROR(L, out);
|
||||||
lua_pushnumber(L, REPROC_EPIPE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lua_pushnumber(L, out);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f_close_stream(lua_State* L)
|
||||||
|
{
|
||||||
|
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||||
|
|
||||||
|
int stream = luaL_checknumber(L, 2);
|
||||||
|
int out = reproc_close(self->process, stream);
|
||||||
|
ASSERT_REPROC_ERRNO(L, out);
|
||||||
|
|
||||||
|
lua_pushboolean(L, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f_wait(lua_State* L)
|
||||||
|
{
|
||||||
|
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||||
|
|
||||||
|
int timeout = luaL_optnumber(L, 2, 0);
|
||||||
|
|
||||||
|
int ret = poll_process(self, timeout);
|
||||||
|
// negative returncode is also used for signals on POSIX
|
||||||
|
if (ret == REPROC_ETIMEDOUT)
|
||||||
|
L_RETURN_REPROC_ERROR(L, ret);
|
||||||
|
|
||||||
|
lua_pushnumber(L, ret);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f_terminate(lua_State* L)
|
||||||
|
{
|
||||||
|
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||||
|
|
||||||
|
int out = reproc_terminate(self->process);
|
||||||
|
ASSERT_REPROC_ERRNO(L, out);
|
||||||
|
|
||||||
|
poll_process(self, 0);
|
||||||
|
lua_pushboolean(L, 1);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_close_stream(lua_State* L)
|
static int f_kill(lua_State* L)
|
||||||
{
|
{
|
||||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||||
|
|
||||||
if(self->process){
|
int out = reproc_kill(self->process);
|
||||||
size_t stream = luaL_checknumber(L, 2);
|
ASSERT_REPROC_ERRNO(L, out);
|
||||||
|
|
||||||
int out = reproc_close(self->process, stream);
|
poll_process(self, 0);
|
||||||
|
lua_pushboolean(L, 1);
|
||||||
lua_pushnumber(L, out);
|
|
||||||
} else {
|
|
||||||
lua_pushnumber(L, REPROC_EINVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_wait(lua_State* L)
|
static int f_running(lua_State* L)
|
||||||
{
|
{
|
||||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||||
|
|
||||||
if(self->process){
|
poll_process(self, 0);
|
||||||
size_t timeout = luaL_checknumber(L, 2);
|
lua_pushboolean(L, self->running);
|
||||||
|
|
||||||
int out = reproc_wait(self->process, timeout);
|
|
||||||
|
|
||||||
if(out >= 0){
|
|
||||||
reproc_destroy(self->process);
|
|
||||||
self->process = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pushnumber(L, out);
|
|
||||||
} else {
|
|
||||||
lua_pushnumber(L, REPROC_EINVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_terminate(lua_State* L)
|
static const struct luaL_Reg lib[] = {
|
||||||
{
|
|
||||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
|
||||||
|
|
||||||
if(self->process){
|
|
||||||
int out = reproc_terminate(self->process);
|
|
||||||
|
|
||||||
if(out < 0){
|
|
||||||
lua_pushnumber(L, out);
|
|
||||||
} else {
|
|
||||||
reproc_destroy(self->process);
|
|
||||||
self->process = NULL;
|
|
||||||
lua_pushboolean(L, 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lua_pushnumber(L, REPROC_EINVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int process_kill(lua_State* L)
|
|
||||||
{
|
|
||||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
|
||||||
|
|
||||||
if(self->process){
|
|
||||||
int out = reproc_kill(self->process);
|
|
||||||
|
|
||||||
if(out < 0){
|
|
||||||
lua_pushnumber(L, out);
|
|
||||||
} else {
|
|
||||||
reproc_destroy(self->process);
|
|
||||||
self->process = NULL;
|
|
||||||
lua_pushboolean(L, 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lua_pushnumber(L, REPROC_EINVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int process_running(lua_State* L)
|
|
||||||
{
|
|
||||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
|
||||||
|
|
||||||
if(self->process){
|
|
||||||
lua_pushboolean(L, 1);
|
|
||||||
} else {
|
|
||||||
lua_pushboolean(L, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct luaL_Reg process_methods[] = {
|
|
||||||
{ "__gc", process_gc},
|
|
||||||
{"start", process_start},
|
{"start", process_start},
|
||||||
{"pid", process_pid},
|
|
||||||
{"read", process_read},
|
|
||||||
{"read_errors", process_read_errors},
|
|
||||||
{"write", process_write},
|
|
||||||
{"close_stream", process_close_stream},
|
|
||||||
{"wait", process_wait},
|
|
||||||
{"terminate", process_terminate},
|
|
||||||
{"kill", process_kill},
|
|
||||||
{"running", process_running},
|
|
||||||
{NULL, NULL}
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct luaL_Reg process[] = {
|
|
||||||
{"new", process_new},
|
|
||||||
{"strerror", process_strerror},
|
{"strerror", process_strerror},
|
||||||
{"ERROR_PIPE", NULL},
|
{"__gc", f_gc},
|
||||||
{"ERROR_WOULDBLOCK", NULL},
|
{"__tostring", f_tostring},
|
||||||
{"ERROR_TIMEDOUT", NULL},
|
{"pid", f_pid},
|
||||||
{"ERROR_INVALID", NULL},
|
{"returncode", f_returncode},
|
||||||
{"STREAM_STDIN", NULL},
|
{"read", f_read},
|
||||||
{"STREAM_STDOUT", NULL},
|
{"read_stdout", f_read_stdout},
|
||||||
{"STREAM_STDERR", NULL},
|
{"read_stderr", f_read_stderr},
|
||||||
{"WAIT_INFINITE", NULL},
|
{"write", f_write},
|
||||||
{"WAIT_DEADLINE", NULL},
|
{"close_stream", f_close_stream},
|
||||||
|
{"wait", f_wait},
|
||||||
|
{"terminate", f_terminate},
|
||||||
|
{"kill", f_kill},
|
||||||
|
{"running", f_running},
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
int luaopen_process(lua_State *L)
|
int luaopen_process(lua_State *L)
|
||||||
{
|
{
|
||||||
luaL_newmetatable(L, API_TYPE_PROCESS);
|
luaL_newmetatable(L, API_TYPE_PROCESS);
|
||||||
luaL_setfuncs(L, process_methods, 0);
|
luaL_setfuncs(L, lib, 0);
|
||||||
lua_pushvalue(L, -1);
|
lua_pushvalue(L, -1);
|
||||||
lua_setfield(L, -2, "__index");
|
lua_setfield(L, -2, "__index");
|
||||||
|
|
||||||
luaL_newlib(L, process);
|
// constants
|
||||||
|
L_SETNUM(L, -1, "ERROR_INVAL", REPROC_EINVAL);
|
||||||
|
L_SETNUM(L, -1, "ERROR_TIMEDOUT", REPROC_ETIMEDOUT);
|
||||||
|
L_SETNUM(L, -1, "ERROR_PIPE", REPROC_EPIPE);
|
||||||
|
L_SETNUM(L, -1, "ERROR_NOMEM", REPROC_ENOMEM);
|
||||||
|
L_SETNUM(L, -1, "ERROR_WOULDBLOCK", REPROC_EWOULDBLOCK);
|
||||||
|
|
||||||
lua_pushnumber(L, REPROC_EPIPE);
|
L_SETNUM(L, -1, "WAIT_INFINITE", REPROC_INFINITE);
|
||||||
lua_setfield(L, -2, "ERROR_PIPE");
|
L_SETNUM(L, -1, "WAIT_DEADLINE", REPROC_DEADLINE);
|
||||||
|
|
||||||
lua_pushnumber(L, REPROC_EWOULDBLOCK);
|
L_SETNUM(L, -1, "STREAM_STDIN", REPROC_STREAM_IN);
|
||||||
lua_setfield(L, -2, "ERROR_WOULDBLOCK");
|
L_SETNUM(L, -1, "STREAM_STDOUT", REPROC_STREAM_OUT);
|
||||||
|
L_SETNUM(L, -1, "STREAM_STDERR", REPROC_STREAM_ERR);
|
||||||
|
|
||||||
lua_pushnumber(L, REPROC_ETIMEDOUT);
|
L_SETNUM(L, -1, "REDIRECT_DEFAULT", REPROC_REDIRECT_DEFAULT);
|
||||||
lua_setfield(L, -2, "ERROR_TIMEDOUT");
|
L_SETNUM(L, -1, "REDIRECT_PIPE", REPROC_REDIRECT_PIPE);
|
||||||
|
L_SETNUM(L, -1, "REDIRECT_PARENT", REPROC_REDIRECT_PARENT);
|
||||||
lua_pushnumber(L, REPROC_EINVAL);
|
L_SETNUM(L, -1, "REDIRECT_DISCARD", REPROC_REDIRECT_DISCARD);
|
||||||
lua_setfield(L, -2, "ERROR_INVALID");
|
L_SETNUM(L, -1, "REDIRECT_STDOUT", REPROC_REDIRECT_STDOUT);
|
||||||
|
|
||||||
lua_pushnumber(L, REPROC_STREAM_IN);
|
|
||||||
lua_setfield(L, -2, "STREAM_STDIN");
|
|
||||||
|
|
||||||
lua_pushnumber(L, REPROC_STREAM_OUT);
|
|
||||||
lua_setfield(L, -2, "STREAM_STDOUT");
|
|
||||||
|
|
||||||
lua_pushnumber(L, REPROC_STREAM_ERR);
|
|
||||||
lua_setfield(L, -2, "STREAM_STDERR");
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,11 +74,11 @@ static int f_pcre_match(lua_State *L) {
|
||||||
}
|
}
|
||||||
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(md);
|
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(md);
|
||||||
if (ovector[0] > ovector[1]) {
|
if (ovector[0] > ovector[1]) {
|
||||||
/* We must guard against patterns such as /(?=.\K)/ that use \K in an
|
/* 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,
|
assertion to set the start of a match later than its end. In the editor,
|
||||||
we just detect this case and give up. */
|
we just detect this case and give up. */
|
||||||
luaL_error(L, "regex matching error: \\K was used in an assertion to "
|
luaL_error(L, "regex matching error: \\K was used in an assertion to "
|
||||||
" set the match start after its end");
|
" set the match start after its end");
|
||||||
pcre2_match_data_free(md);
|
pcre2_match_data_free(md);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -103,8 +103,8 @@ int luaopen_regex(lua_State *L) {
|
||||||
lua_setfield(L, LUA_REGISTRYINDEX, "regex");
|
lua_setfield(L, LUA_REGISTRYINDEX, "regex");
|
||||||
lua_pushnumber(L, PCRE2_ANCHORED);
|
lua_pushnumber(L, PCRE2_ANCHORED);
|
||||||
lua_setfield(L, -2, "ANCHORED");
|
lua_setfield(L, -2, "ANCHORED");
|
||||||
lua_pushnumber(L, PCRE2_ANCHORED) ;
|
lua_pushnumber(L, PCRE2_ANCHORED) ;
|
||||||
lua_setfield(L, -2, "ENDANCHORED");
|
lua_setfield(L, -2, "ENDANCHORED");
|
||||||
lua_pushnumber(L, PCRE2_NOTBOL);
|
lua_pushnumber(L, PCRE2_NOTBOL);
|
||||||
lua_setfield(L, -2, "NOTBOL");
|
lua_setfield(L, -2, "NOTBOL");
|
||||||
lua_pushnumber(L, PCRE2_NOTEOL);
|
lua_pushnumber(L, PCRE2_NOTEOL);
|
||||||
|
|
|
@ -2,6 +2,131 @@
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
#include "rencache.h"
|
#include "rencache.h"
|
||||||
|
|
||||||
|
static int f_font_load(lua_State *L) {
|
||||||
|
const char *filename = luaL_checkstring(L, 1);
|
||||||
|
float size = luaL_checknumber(L, 2);
|
||||||
|
unsigned int font_hinting = FONT_HINTING_SLIGHT, font_style = 0;
|
||||||
|
bool subpixel = true;
|
||||||
|
if (lua_gettop(L) > 2 && lua_istable(L, 3)) {
|
||||||
|
lua_getfield(L, 3, "antialiasing");
|
||||||
|
if (lua_isstring(L, -1)) {
|
||||||
|
const char *antialiasing = lua_tostring(L, -1);
|
||||||
|
if (antialiasing) {
|
||||||
|
if (strcmp(antialiasing, "grayscale") == 0) {
|
||||||
|
subpixel = false;
|
||||||
|
} else if (strcmp(antialiasing, "subpixel") == 0) {
|
||||||
|
subpixel = true;
|
||||||
|
} else {
|
||||||
|
return luaL_error(L, "error in renderer.font.load, unknown antialiasing option: \"%s\"", antialiasing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lua_getfield(L, 3, "hinting");
|
||||||
|
if (lua_isstring(L, -1)) {
|
||||||
|
const char *hinting = lua_tostring(L, -1);
|
||||||
|
if (hinting) {
|
||||||
|
if (strcmp(hinting, "slight") == 0) {
|
||||||
|
font_hinting = FONT_HINTING_SLIGHT;
|
||||||
|
} else if (strcmp(hinting, "none") == 0) {
|
||||||
|
font_hinting = FONT_HINTING_NONE;
|
||||||
|
} else if (strcmp(hinting, "full") == 0) {
|
||||||
|
font_hinting = FONT_HINTING_FULL;
|
||||||
|
} else {
|
||||||
|
return luaL_error(L, "error in renderer.font.load, unknown hinting option: \"%s\"", hinting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lua_getfield(L, 3, "italic");
|
||||||
|
if (lua_toboolean(L, -1))
|
||||||
|
font_style |= FONT_STYLE_ITALIC;
|
||||||
|
lua_getfield(L, 3, "bold");
|
||||||
|
if (lua_toboolean(L, -1))
|
||||||
|
font_style |= FONT_STYLE_BOLD;
|
||||||
|
lua_getfield(L, 3, "underline");
|
||||||
|
if (lua_toboolean(L, -1))
|
||||||
|
font_style |= FONT_STYLE_UNDERLINE;
|
||||||
|
lua_pop(L, 5);
|
||||||
|
}
|
||||||
|
RenFont** font = lua_newuserdata(L, sizeof(RenFont*));
|
||||||
|
*font = ren_font_load(filename, size, subpixel, font_hinting, font_style);
|
||||||
|
if (!*font)
|
||||||
|
return luaL_error(L, "failed to load font");
|
||||||
|
luaL_setmetatable(L, API_TYPE_FONT);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool font_retrieve(lua_State* L, RenFont** fonts, int idx) {
|
||||||
|
memset(fonts, 0, sizeof(RenFont*)*FONT_FALLBACK_MAX);
|
||||||
|
if (lua_type(L, 1) != LUA_TTABLE) {
|
||||||
|
fonts[0] = *(RenFont**)luaL_checkudata(L, idx, API_TYPE_FONT);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
do {
|
||||||
|
lua_rawgeti(L, idx, i+1);
|
||||||
|
fonts[i] = !lua_isnil(L, -1) ? *(RenFont**)luaL_checkudata(L, -1, API_TYPE_FONT) : NULL;
|
||||||
|
lua_pop(L, 1);
|
||||||
|
} while (fonts[i] && i++ < FONT_FALLBACK_MAX);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f_font_copy(lua_State *L) {
|
||||||
|
RenFont* fonts[FONT_FALLBACK_MAX];
|
||||||
|
bool table = font_retrieve(L, fonts, 1);
|
||||||
|
float size = lua_gettop(L) >= 2 ? luaL_checknumber(L, 2) : ren_font_group_get_height(fonts);
|
||||||
|
if (table) {
|
||||||
|
lua_newtable(L);
|
||||||
|
luaL_setmetatable(L, API_TYPE_FONT);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < FONT_FALLBACK_MAX && fonts[i]; ++i) {
|
||||||
|
RenFont** font = lua_newuserdata(L, sizeof(RenFont*));
|
||||||
|
*font = ren_font_copy(fonts[i], size);
|
||||||
|
if (!*font)
|
||||||
|
return luaL_error(L, "failed to copy font");
|
||||||
|
luaL_setmetatable(L, API_TYPE_FONT);
|
||||||
|
if (table)
|
||||||
|
lua_rawseti(L, -2, i+1);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f_font_group(lua_State* L) {
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
luaL_setmetatable(L, API_TYPE_FONT);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f_font_set_tab_size(lua_State *L) {
|
||||||
|
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
|
||||||
|
int n = luaL_checknumber(L, 2);
|
||||||
|
ren_font_group_set_tab_size(fonts, n);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f_font_gc(lua_State *L) {
|
||||||
|
RenFont** self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
||||||
|
ren_font_free(*self);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int f_font_get_width(lua_State *L) {
|
||||||
|
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
|
||||||
|
lua_pushnumber(L, ren_font_group_get_width(fonts, luaL_checkstring(L, 2)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f_font_get_height(lua_State *L) {
|
||||||
|
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
|
||||||
|
lua_pushnumber(L, ren_font_group_get_height(fonts));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int f_font_get_size(lua_State *L) {
|
||||||
|
RenFont* fonts[FONT_FALLBACK_MAX]; font_retrieve(L, fonts, 1);
|
||||||
|
lua_pushnumber(L, ren_font_group_get_size(fonts));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static RenColor checkcolor(lua_State *L, int idx, int def) {
|
static RenColor checkcolor(lua_State *L, int idx, int def) {
|
||||||
RenColor color;
|
RenColor color;
|
||||||
|
@ -71,40 +196,18 @@ static int f_draw_rect(lua_State *L) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int draw_text_subpixel_impl(lua_State *L, bool draw_subpixel) {
|
static int f_draw_text(lua_State *L) {
|
||||||
FontDesc *font_desc = luaL_checkudata(L, 1, API_TYPE_FONT);
|
RenFont* fonts[FONT_FALLBACK_MAX];
|
||||||
|
font_retrieve(L, fonts, 1);
|
||||||
const char *text = luaL_checkstring(L, 2);
|
const char *text = luaL_checkstring(L, 2);
|
||||||
/* The coordinate below will be in subpixel iff draw_subpixel is true.
|
float x = luaL_checknumber(L, 3);
|
||||||
Otherwise it will be in pixels. */
|
|
||||||
int x_subpixel = luaL_checknumber(L, 3);
|
|
||||||
int y = luaL_checknumber(L, 4);
|
int y = luaL_checknumber(L, 4);
|
||||||
RenColor color = checkcolor(L, 5, 255);
|
RenColor color = checkcolor(L, 5, 255);
|
||||||
|
x = rencache_draw_text(L, fonts, text, x, y, color);
|
||||||
CPReplaceTable *rep_table;
|
lua_pushnumber(L, x);
|
||||||
RenColor replace_color;
|
|
||||||
if (lua_gettop(L) >= 7) {
|
|
||||||
rep_table = luaL_checkudata(L, 6, API_TYPE_REPLACE);
|
|
||||||
replace_color = checkcolor(L, 7, 255);
|
|
||||||
} else {
|
|
||||||
rep_table = NULL;
|
|
||||||
replace_color = (RenColor) {0};
|
|
||||||
}
|
|
||||||
|
|
||||||
x_subpixel = rencache_draw_text(L, font_desc, 1, text, x_subpixel, y, color, draw_subpixel, rep_table, replace_color);
|
|
||||||
lua_pushnumber(L, x_subpixel);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int f_draw_text(lua_State *L) {
|
|
||||||
return draw_text_subpixel_impl(L, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int f_draw_text_subpixel(lua_State *L) {
|
|
||||||
return draw_text_subpixel_impl(L, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static const luaL_Reg lib[] = {
|
static const luaL_Reg lib[] = {
|
||||||
{ "show_debug", f_show_debug },
|
{ "show_debug", f_show_debug },
|
||||||
{ "get_size", f_get_size },
|
{ "get_size", f_get_size },
|
||||||
|
@ -113,19 +216,27 @@ static const luaL_Reg lib[] = {
|
||||||
{ "set_clip_rect", f_set_clip_rect },
|
{ "set_clip_rect", f_set_clip_rect },
|
||||||
{ "draw_rect", f_draw_rect },
|
{ "draw_rect", f_draw_rect },
|
||||||
{ "draw_text", f_draw_text },
|
{ "draw_text", f_draw_text },
|
||||||
{ "draw_text_subpixel", f_draw_text_subpixel },
|
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const luaL_Reg fontLib[] = {
|
||||||
int luaopen_renderer_font(lua_State *L);
|
{ "__gc", f_font_gc },
|
||||||
int luaopen_renderer_replacements(lua_State *L);
|
{ "load", f_font_load },
|
||||||
|
{ "copy", f_font_copy },
|
||||||
|
{ "group", f_font_group },
|
||||||
|
{ "set_tab_size", f_font_set_tab_size },
|
||||||
|
{ "get_width", f_font_get_width },
|
||||||
|
{ "get_height", f_font_get_height },
|
||||||
|
{ "get_size", f_font_get_size },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
int luaopen_renderer(lua_State *L) {
|
int luaopen_renderer(lua_State *L) {
|
||||||
luaL_newlib(L, lib);
|
luaL_newlib(L, lib);
|
||||||
luaopen_renderer_font(L);
|
luaL_newmetatable(L, API_TYPE_FONT);
|
||||||
|
luaL_setfuncs(L, fontLib, 0);
|
||||||
|
lua_pushvalue(L, -1);
|
||||||
|
lua_setfield(L, -2, "__index");
|
||||||
lua_setfield(L, -2, "font");
|
lua_setfield(L, -2, "font");
|
||||||
luaopen_renderer_replacements(L);
|
|
||||||
lua_setfield(L, -2, "replacements");
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,158 +0,0 @@
|
||||||
#include <lua.h>
|
|
||||||
#include <lauxlib.h>
|
|
||||||
|
|
||||||
#include "api.h"
|
|
||||||
#include "fontdesc.h"
|
|
||||||
#include "renderer.h"
|
|
||||||
#include "rencache.h"
|
|
||||||
|
|
||||||
static int f_load(lua_State *L) {
|
|
||||||
const char *filename = luaL_checkstring(L, 1);
|
|
||||||
float size = luaL_checknumber(L, 2);
|
|
||||||
unsigned int font_options = 0;
|
|
||||||
if (lua_gettop(L) > 2 && lua_istable(L, 3)) {
|
|
||||||
lua_getfield(L, 3, "antialiasing");
|
|
||||||
if (lua_isstring(L, -1)) {
|
|
||||||
const char *antialiasing = lua_tostring(L, -1);
|
|
||||||
if (antialiasing) {
|
|
||||||
if (strcmp(antialiasing, "grayscale") == 0) {
|
|
||||||
font_options |= RenFontGrayscale;
|
|
||||||
} else if (strcmp(antialiasing, "subpixel") == 0) {
|
|
||||||
font_options |= RenFontSubpixel;
|
|
||||||
} else {
|
|
||||||
return luaL_error(L, "error in renderer.font.load, unknown antialiasing option: \"%s\"", antialiasing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
lua_getfield(L, 3, "hinting");
|
|
||||||
if (lua_isstring(L, -1)) {
|
|
||||||
const char *hinting = lua_tostring(L, -1);
|
|
||||||
if (hinting) {
|
|
||||||
if (strcmp(hinting, "slight") == 0) {
|
|
||||||
font_options |= RenFontHintingSlight;
|
|
||||||
} else if (strcmp(hinting, "none") == 0) {
|
|
||||||
font_options |= RenFontHintingNone;
|
|
||||||
} else if (strcmp(hinting, "full") == 0) {
|
|
||||||
font_options |= RenFontHintingFull;
|
|
||||||
} else {
|
|
||||||
return luaL_error(L, "error in renderer.font.load, unknown hinting option: \"%s\"", hinting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ren_verify_font(filename)) {
|
|
||||||
luaL_error(L, "failed to load font");
|
|
||||||
}
|
|
||||||
|
|
||||||
FontDesc *font_desc = lua_newuserdata(L, font_desc_alloc_size(filename));
|
|
||||||
font_desc_init(font_desc, filename, size, font_options);
|
|
||||||
luaL_setmetatable(L, API_TYPE_FONT);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int f_copy(lua_State *L) {
|
|
||||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
|
||||||
float size;
|
|
||||||
if (lua_gettop(L) >= 2) {
|
|
||||||
size = luaL_checknumber(L, 2);
|
|
||||||
} else {
|
|
||||||
size = self->size;
|
|
||||||
}
|
|
||||||
FontDesc *new_font_desc = lua_newuserdata(L, font_desc_alloc_size(self->filename));
|
|
||||||
font_desc_init(new_font_desc, self->filename, size, self->options);
|
|
||||||
luaL_setmetatable(L, API_TYPE_FONT);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int f_set_tab_size(lua_State *L) {
|
|
||||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
|
||||||
int n = luaL_checknumber(L, 2);
|
|
||||||
font_desc_set_tab_size(self, n);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int f_gc(lua_State *L) {
|
|
||||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
|
||||||
font_desc_clear(self);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int f_get_width(lua_State *L) {
|
|
||||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
|
||||||
const char *text = luaL_checkstring(L, 2);
|
|
||||||
/* By calling ren_get_font_width with NULL as third arguments
|
|
||||||
we will obtain the width in points. */
|
|
||||||
int w = ren_get_font_width(self, text, NULL);
|
|
||||||
lua_pushnumber(L, w);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int f_subpixel_scale(lua_State *L) {
|
|
||||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
|
||||||
lua_pushnumber(L, ren_get_font_subpixel_scale(self));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int f_get_width_subpixel(lua_State *L) {
|
|
||||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
|
||||||
const char *text = luaL_checkstring(L, 2);
|
|
||||||
int subpixel_scale;
|
|
||||||
/* We need to pass a non-null subpixel_scale pointer to force
|
|
||||||
subpixel width calculation. */
|
|
||||||
lua_pushnumber(L, ren_get_font_width(self, text, &subpixel_scale));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int f_get_height(lua_State *L) {
|
|
||||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
|
||||||
lua_pushnumber(L, ren_get_font_height(self) );
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int f_get_size(lua_State *L) {
|
|
||||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
|
||||||
lua_pushnumber(L, self->size);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int f_set_size(lua_State *L) {
|
|
||||||
FontDesc *self = luaL_checkudata(L, 1, API_TYPE_FONT);
|
|
||||||
float new_size = luaL_checknumber(L, 2);
|
|
||||||
font_desc_clear(self);
|
|
||||||
self->size = new_size;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static const luaL_Reg lib[] = {
|
|
||||||
{ "__gc", f_gc },
|
|
||||||
{ "load", f_load },
|
|
||||||
{ "copy", f_copy },
|
|
||||||
{ "set_tab_size", f_set_tab_size },
|
|
||||||
{ "get_width", f_get_width },
|
|
||||||
{ "get_width_subpixel", f_get_width_subpixel },
|
|
||||||
{ "get_height", f_get_height },
|
|
||||||
{ "subpixel_scale", f_subpixel_scale },
|
|
||||||
{ "get_size", f_get_size },
|
|
||||||
{ "set_size", f_set_size },
|
|
||||||
{ NULL, NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
int luaopen_renderer_font(lua_State *L) {
|
|
||||||
luaL_newmetatable(L, API_TYPE_FONT);
|
|
||||||
luaL_setfuncs(L, lib, 0);
|
|
||||||
lua_pushvalue(L, -1);
|
|
||||||
lua_setfield(L, -2, "__index");
|
|
||||||
return 1;
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue