Merge branch 'dmon-1' into dmon-debug
This commit is contained in:
commit
fb1e08840e
|
@ -0,0 +1,11 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[meson.build]
|
||||
indent_size = 4
|
|
@ -1,11 +1,44 @@
|
|||
name: CI
|
||||
|
||||
# All builds use lhelper only for releases,
|
||||
# otherwise for normal builds dependencies are dynamically linked.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
# tags:
|
||||
# - 'v[0-9]*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
name: Build Linux
|
||||
archive_source_code:
|
||||
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
|
||||
strategy:
|
||||
matrix:
|
||||
|
@ -16,48 +49,204 @@ jobs:
|
|||
CC: ${{ matrix.config.cc }}
|
||||
CXX: ${{ matrix.config.cxx }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.6
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install -qq libsdl2-dev libfreetype6 ninja-build
|
||||
pip3 install meson
|
||||
- name: Build package
|
||||
run: bash build-packages.sh x86-64
|
||||
- name: upload packages
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Ubuntu Package
|
||||
path: lite-xl-linux-*.tar.gz
|
||||
- name: Set Environment Variables
|
||||
if: ${{ matrix.config.cc == 'gcc' }}
|
||||
run: |
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-linux-$(uname -m)" >> "$GITHUB_ENV"
|
||||
- uses: actions/checkout@v2
|
||||
- name: Python Setup
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.6
|
||||
- name: Update Packages
|
||||
run: sudo apt-get update
|
||||
- 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 --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:
|
||||
name: Build Mac OS X
|
||||
build_macos:
|
||||
name: macOS (x86_64)
|
||||
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:
|
||||
matrix:
|
||||
config:
|
||||
# - { name: "GCC", cc: gcc-10, cxx: g++-10 }
|
||||
- { name: "clang", cc: clang, cxx: clang++ }
|
||||
env:
|
||||
CC: ${{ matrix.config.cc }}
|
||||
CXX: ${{ matrix.config.cxx }}
|
||||
msystem: [MINGW32, MINGW64]
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip3 install meson
|
||||
brew install ninja sdl2
|
||||
- name: Build package
|
||||
run: bash build-packages.sh x86-64
|
||||
- name: upload packages
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Mac OS X Package
|
||||
path: lite-xl-macosx-*.zip
|
||||
- uses: actions/checkout@v2
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
#msystem: MINGW64
|
||||
msystem: ${{ matrix.msystem }}
|
||||
update: true
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
zip
|
||||
- name: Set Environment Variables
|
||||
run: |
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-windows-$(uname -m)" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
||||
- 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,11 +1,21 @@
|
|||
build*
|
||||
.build*
|
||||
.run*
|
||||
*.zip
|
||||
*.tar.gz
|
||||
build*/
|
||||
.build*/
|
||||
lhelper/
|
||||
submodules/
|
||||
subprojects/lua/
|
||||
subprojects/libagg/
|
||||
subprojects/reproc/
|
||||
/appimage*
|
||||
.ccls-cache
|
||||
.lite-debug.log
|
||||
subprojects/lua
|
||||
subprojects/libagg
|
||||
subprojects/reproc
|
||||
lite-xl
|
||||
.run*
|
||||
*.diff
|
||||
*.exe
|
||||
*.tar.gz
|
||||
*.zip
|
||||
*.DS_Store
|
||||
*App*
|
||||
compile_commands.json
|
||||
error.txt
|
||||
lite-xl*
|
||||
LiteXL*
|
||||
|
|
40
README.md
40
README.md
|
@ -1,17 +1,18 @@
|
|||
# Lite XL
|
||||
|
||||
[![CI]](https://github.com/lite-xl/lite-xl/actions/workflows/build.yml)
|
||||
[![Discord Badge Image]](https://discord.gg/RWzqC3nx7K)
|
||||
|
||||
![screenshot-dark]
|
||||
|
||||
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 color themes]** — Add additional colors themes.
|
||||
|
||||
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,
|
||||
since 1.16.7 release, it supports **retina displays** on macOS.
|
||||
|
@ -42,6 +43,40 @@ the [plugins repository] or in the [Lite XL plugins repository].
|
|||
Additional color themes can be found in the [colors repository].
|
||||
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.
|
||||
|
||||
## Contributing
|
||||
|
||||
Any additional functionality that can be added through a plugin should be done
|
||||
|
@ -60,6 +95,7 @@ the terms of the MIT license. See [LICENSE] for details.
|
|||
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
|
||||
[screenshot-dark]: https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png
|
||||
[lite]: https://github.com/rxi/lite
|
||||
|
|
|
@ -1,216 +1,164 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# strip-components is normally set to 1 to strip the initial "data" from the
|
||||
# directory path.
|
||||
copy_directory_from_repo () {
|
||||
local tar_options=()
|
||||
if [[ $1 == --strip-components=* ]]; then
|
||||
tar_options+=($1)
|
||||
shift
|
||||
fi
|
||||
local dirname="$1"
|
||||
local destdir="$2"
|
||||
git archive "$use_branch" "$dirname" --format=tar | tar xf - -C "$destdir" "${tar_options[@]}"
|
||||
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 "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.
|
||||
build_dir_is_usable () {
|
||||
local build="$1"
|
||||
if [[ $build == */* || -z "$build" ]]; then
|
||||
echo "invalid build directory, no path allowed: \"$build\""
|
||||
return 1
|
||||
fi
|
||||
git ls-files --error-unmatch "$build" &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
echo "invalid path, \"$build\" is under revision control"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
main() {
|
||||
local build_dir
|
||||
local build_dir_option=()
|
||||
local dest_dir
|
||||
local dest_dir_option=()
|
||||
local prefix
|
||||
local prefix_option=()
|
||||
local version
|
||||
local version_option=()
|
||||
local debug
|
||||
local force_fallback
|
||||
local appimage
|
||||
local bundle
|
||||
local innosetup
|
||||
local portable
|
||||
local pgo
|
||||
|
||||
# Ordinary release build
|
||||
lite_build () {
|
||||
local build="$1"
|
||||
build_dir_is_usable "$build" || exit 1
|
||||
rm -fr "$build"
|
||||
meson setup --buildtype=release "$build" || exit 1
|
||||
ninja -C "$build" || exit 1
|
||||
}
|
||||
|
||||
# Build using Profile Guided Optimizations (PGO)
|
||||
lite_build_pgo () {
|
||||
local build="$1"
|
||||
build_dir_is_usable "$build" || exit 1
|
||||
rm -fr "$build"
|
||||
meson setup --buildtype=release -Db_pgo=generate "$build" || exit 1
|
||||
ninja -C "$build" || exit 1
|
||||
copy_directory_from_repo data "$build/src"
|
||||
"$build/src/lite-xl"
|
||||
meson configure -Db_pgo=use "$build"
|
||||
ninja -C "$build" || exit 1
|
||||
}
|
||||
|
||||
lite_build_package_windows () {
|
||||
local portable="-msys"
|
||||
if [ "$1" == "-portable" ]; then
|
||||
portable=""
|
||||
shift
|
||||
fi
|
||||
local build="$1"
|
||||
local arch="$2"
|
||||
local os="win"
|
||||
local pdir=".package-build/lite-xl"
|
||||
if [ -z "$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"
|
||||
for i in "$@"; do
|
||||
case $i in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-b|--builddir)
|
||||
build_dir="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-d|--destdir)
|
||||
dest_dir="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-f|--forcefallback)
|
||||
force_fallback="--forcefallback"
|
||||
shift
|
||||
;;
|
||||
-p|--prefix)
|
||||
prefix="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-v|--version)
|
||||
version="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-A|--appimage)
|
||||
appimage="--appimage"
|
||||
shift
|
||||
;;
|
||||
-B|--bundle)
|
||||
bundle="--bundle"
|
||||
shift
|
||||
;;
|
||||
-D|--dmg)
|
||||
dmg="--dmg"
|
||||
shift
|
||||
;;
|
||||
-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
|
||||
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 () {
|
||||
local build="$1"
|
||||
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\""
|
||||
if [[ -n $1 ]]; then
|
||||
show_help
|
||||
exit 1
|
||||
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 () {
|
||||
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
|
||||
main "$@"
|
||||
|
|
39
build.sh
39
build.sh
|
@ -1,39 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
cflags+="-Wall -O3 -g -std=gnu11 -fno-strict-aliasing -Isrc -Ilib/font_renderer"
|
||||
cflags+=" $(pkg-config --cflags lua5.2) $(sdl2-config --cflags)"
|
||||
lflags="-static-libgcc -static-libstdc++"
|
||||
for package in libagg freetype2 lua5.2 x11 libpcre2-8 reproc; do
|
||||
lflags+=" $(pkg-config --libs $package)"
|
||||
done
|
||||
lflags+=" $(sdl2-config --libs) -lm"
|
||||
|
||||
if [[ $* == *windows* ]]; then
|
||||
echo "cross compiling for windows is not yet supported"
|
||||
exit 1
|
||||
else
|
||||
outfile="lite-xl"
|
||||
compiler="gcc"
|
||||
cxxcompiler="g++"
|
||||
fi
|
||||
|
||||
lib/font_renderer/build.sh || exit 1
|
||||
libs=libfontrenderer.a
|
||||
|
||||
echo "compiling lite-xl..."
|
||||
for f in `find src -name "*.c"`; do
|
||||
$compiler -c $cflags $f -o "${f//\//_}.o"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
got_error=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! $got_error ]]; then
|
||||
echo "linking..."
|
||||
$cxxcompiler -o $outfile *.o $libs $lflags
|
||||
fi
|
||||
|
||||
echo "cleaning up..."
|
||||
rm *.o *.a
|
||||
echo "done"
|
||||
|
80
changelog.md
80
changelog.md
|
@ -1,5 +1,85 @@
|
|||
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
|
||||
|
||||
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) }
|
|
@ -66,9 +66,8 @@ command.add(nil, {
|
|||
end,
|
||||
|
||||
["core:find-file"] = function()
|
||||
-- FIXME: core.project_files_limit was removed!
|
||||
if core.project_files_limit then
|
||||
return command.perform "core:open-file"
|
||||
if not core.project_files_number() then
|
||||
return command.perform "core:open-file"
|
||||
end
|
||||
local files = {}
|
||||
for dir, item in core.get_project_files() do
|
||||
|
@ -105,11 +104,20 @@ command.add(nil, {
|
|||
end, function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(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
|
||||
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
|
||||
core.error("Cannot open %q, is a folder", text)
|
||||
core.error("Cannot open %s, is a folder", text)
|
||||
else
|
||||
return true
|
||||
end
|
||||
|
@ -139,6 +147,10 @@ command.add(nil, {
|
|||
end,
|
||||
|
||||
["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)
|
||||
text = system.absolute_path(common.home_expand(item and item.text or text))
|
||||
if text == core.project_dir then return end
|
||||
|
@ -147,11 +159,15 @@ command.add(nil, {
|
|||
core.error("Cannot open folder %q", text)
|
||||
return
|
||||
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,
|
||||
|
||||
["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)
|
||||
text = common.home_expand(item and item.text or text)
|
||||
local path_stat = system.get_file_info(text)
|
||||
|
|
|
@ -43,9 +43,13 @@ local function append_line_if_last_line(line)
|
|||
end
|
||||
|
||||
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
|
||||
core.on_doc_save(saved_filename)
|
||||
core.log("Saved \"%s\"", saved_filename)
|
||||
end
|
||||
|
||||
|
@ -63,13 +67,14 @@ local function cut_or_copy(delete)
|
|||
doc().cursor_clipboard[idx] = ""
|
||||
end
|
||||
end
|
||||
doc().cursor_clipboard["full"] = full_text
|
||||
system.set_clipboard(full_text)
|
||||
end
|
||||
|
||||
local function split_cursor(direction)
|
||||
local new_cursors = {}
|
||||
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 })
|
||||
end
|
||||
end
|
||||
|
@ -95,8 +100,13 @@ local commands = {
|
|||
end,
|
||||
|
||||
["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
|
||||
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)
|
||||
end
|
||||
end,
|
||||
|
@ -364,12 +374,14 @@ local commands = {
|
|||
end
|
||||
core.command_view:set_text(old_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)
|
||||
if filename ~= old_filename then
|
||||
os.remove(old_filename)
|
||||
end
|
||||
end, common.path_suggest)
|
||||
end, function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||
end)
|
||||
end,
|
||||
|
||||
|
||||
|
@ -412,6 +424,7 @@ local translations = {
|
|||
["start-of-line"] = translate.start_of_line,
|
||||
["end-of-line"] = translate.end_of_line,
|
||||
["start-of-word"] = translate.start_of_word,
|
||||
["start-of-indentation"] = translate.start_of_indentation,
|
||||
["end-of-word"] = translate.end_of_word,
|
||||
["previous-line"] = DocView.translate.previous_line,
|
||||
["next-line"] = DocView.translate.next_line,
|
||||
|
|
|
@ -10,48 +10,62 @@ local StatusView = require "core.statusview"
|
|||
local max_last_finds = 50
|
||||
local last_finds, last_view, last_fn, last_text, last_sel
|
||||
|
||||
local case_insensitive = config.find_case_insensitive or false
|
||||
local plain = config.find_plain or false
|
||||
local case_sensitive = config.find_case_sensitive or false
|
||||
local find_regex = config.find_regex or false
|
||||
local found_expression
|
||||
|
||||
local function doc()
|
||||
return last_view and last_view.doc or core.active_view.doc
|
||||
return core.active_view:is(DocView) and core.active_view.doc or last_view.doc
|
||||
end
|
||||
|
||||
local function get_find_tooltip()
|
||||
local rf = keymap.get_binding("find-replace:repeat-find")
|
||||
local ti = keymap.get_binding("find-replace:toggle-insensitivity")
|
||||
local tr = keymap.get_binding("find-replace:toggle-plain")
|
||||
return (plain and "[Plain] " or "") ..
|
||||
(case_insensitive and "[Insensitive] " or "") ..
|
||||
local ti = keymap.get_binding("find-replace:toggle-sensitivity")
|
||||
local tr = keymap.get_binding("find-replace:toggle-regex")
|
||||
return (find_regex and "[Regex] " or "") ..
|
||||
(case_sensitive and "[Sensitive] " or "") ..
|
||||
(rf and ("Press " .. rf .. " to select the next match.") or "") ..
|
||||
(ti and (" " .. ti .. " toggles case sensitivity.") or "") ..
|
||||
(tr and (" " .. tr .. " toggles plain find.") or "")
|
||||
(tr and (" " .. tr .. " toggles regex find.") or "")
|
||||
end
|
||||
|
||||
local function update_preview(sel, search_fn, text)
|
||||
local ok, line1, col1, line2, col2 =
|
||||
pcall(search_fn, last_view.doc, sel[1], sel[2], text, case_insensitive, plain)
|
||||
local ok, line1, col1, line2, col2 = pcall(search_fn, last_view.doc,
|
||||
sel[1], sel[2], text, case_sensitive, find_regex)
|
||||
if ok and line1 and text ~= "" then
|
||||
last_view.doc:set_selection(line2, col2, line1, col1)
|
||||
last_view:scroll_to_line(line2, true)
|
||||
return true
|
||||
found_expression = true
|
||||
else
|
||||
last_view.doc:set_selection(unpack(sel))
|
||||
return false
|
||||
found_expression = false
|
||||
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)
|
||||
last_view, last_sel, last_finds = core.active_view,
|
||||
{ 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(unpack(last_sel))
|
||||
found_expression = false
|
||||
|
||||
core.command_view:set_text(text, true)
|
||||
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()
|
||||
if found then
|
||||
if found_expression then
|
||||
last_fn, last_text = search_fn, text
|
||||
else
|
||||
core.error("Couldn't find %q", text)
|
||||
|
@ -59,8 +73,9 @@ local function find(label, search_fn)
|
|||
last_view:scroll_to_make_visible(unpack(last_sel))
|
||||
end
|
||||
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
|
||||
return core.previous_find
|
||||
end, function(explicit)
|
||||
core.status_view:remove_tooltip()
|
||||
if explicit then
|
||||
|
@ -75,18 +90,25 @@ local function replace(kind, default, fn)
|
|||
core.command_view:set_text(default, true)
|
||||
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
core.command_view:set_hidden_suggestions()
|
||||
core.command_view:enter("Find To Replace " .. kind, function(old)
|
||||
insert_unique(core.previous_find, old)
|
||||
core.command_view:set_text(old, true)
|
||||
|
||||
local s = string.format("Replace %s %q With", kind, old)
|
||||
core.command_view:set_hidden_suggestions()
|
||||
core.command_view:enter(s, function(new)
|
||||
core.status_view:remove_tooltip()
|
||||
insert_unique(core.previous_replace, new)
|
||||
local n = doc():replace(function(text)
|
||||
return fn(text, old, new)
|
||||
end)
|
||||
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()
|
||||
end)
|
||||
end, function() return core.previous_find end, function()
|
||||
core.status_view:remove_tooltip()
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -94,29 +116,78 @@ local function has_selection()
|
|||
return core.active_view:is(DocView) and core.active_view.doc:has_selection()
|
||||
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()
|
||||
local l1, c1, l2, c2 = doc():get_selection(true)
|
||||
local text = doc():get_text(l1, c1, l2, c2)
|
||||
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||
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", {
|
||||
["find-replace:find"] = function()
|
||||
find("Find Text", function(doc, line, col, text, case_insensitive, plain)
|
||||
local opt = { wrap = true, no_case = case_insensitive, regex = not plain }
|
||||
find("Find Text", function(doc, line, col, text, case_sensitive, find_regex)
|
||||
local opt = { wrap = true, no_case = not case_sensitive, regex = find_regex }
|
||||
return search.find(doc, line, col, text, opt)
|
||||
end)
|
||||
end,
|
||||
|
||||
["find-replace:replace"] = function()
|
||||
replace("Text", doc():get_text(doc():get_selection(true)), function(text, old, new)
|
||||
if plain then
|
||||
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
|
||||
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
||||
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
|
||||
end)
|
||||
end,
|
||||
|
@ -150,7 +221,7 @@ command.add(valid_for_finding, {
|
|||
core.error("No find to continue from")
|
||||
else
|
||||
local sl1, sc1, sl2, sc2 = doc():get_selection(true)
|
||||
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc2, last_text, case_insensitive, plain)
|
||||
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc2, last_text, case_sensitive, find_regex)
|
||||
if line1 then
|
||||
if last_view.doc ~= doc() then
|
||||
last_finds = {}
|
||||
|
@ -177,15 +248,15 @@ command.add(valid_for_finding, {
|
|||
})
|
||||
|
||||
command.add("core.commandview", {
|
||||
["find-replace:toggle-insensitivity"] = function()
|
||||
case_insensitive = not case_insensitive
|
||||
["find-replace:toggle-sensitivity"] = function()
|
||||
case_sensitive = not case_sensitive
|
||||
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,
|
||||
|
||||
["find-replace:toggle-plain"] = function()
|
||||
plain = not plain
|
||||
["find-replace:toggle-regex"] = function()
|
||||
find_regex = not find_regex
|
||||
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
|
||||
})
|
||||
|
|
|
@ -21,9 +21,15 @@ local t = {
|
|||
end,
|
||||
|
||||
["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,
|
||||
|
||||
["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()
|
||||
local node = core.root_view:get_active_node()
|
||||
local idx = node:get_view_idx(core.active_view)
|
||||
|
|
|
@ -15,6 +15,8 @@ end
|
|||
|
||||
local CommandView = DocView:extend()
|
||||
|
||||
CommandView.context = "application"
|
||||
|
||||
local max_suggestions = 10
|
||||
|
||||
local noop = function() end
|
||||
|
@ -32,6 +34,7 @@ function CommandView:new()
|
|||
self.suggestion_idx = 1
|
||||
self.suggestions = {}
|
||||
self.suggestions_height = 0
|
||||
self.show_suggestions = true
|
||||
self.last_change_id = 0
|
||||
self.gutter_width = 0
|
||||
self.gutter_text_brightness = 0
|
||||
|
@ -43,6 +46,11 @@ function CommandView:new()
|
|||
end
|
||||
|
||||
|
||||
function CommandView:set_hidden_suggestions()
|
||||
self.show_suggestions = false
|
||||
end
|
||||
|
||||
|
||||
function CommandView:get_name()
|
||||
return View.get_name(self)
|
||||
end
|
||||
|
@ -81,10 +89,29 @@ end
|
|||
|
||||
|
||||
function CommandView:move_suggestion_idx(dir)
|
||||
local n = self.suggestion_idx + dir
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
if self.show_suggestions then
|
||||
local n = self.suggestion_idx + dir
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
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
|
||||
|
||||
|
||||
|
@ -132,6 +159,8 @@ function CommandView:exit(submitted, inexplicit)
|
|||
self.doc:reset()
|
||||
self.suggestions = {}
|
||||
if not submitted then cancel(not inexplicit) end
|
||||
self.show_suggestions = true
|
||||
self.save_suggestion = nil
|
||||
end
|
||||
|
||||
|
||||
|
@ -185,7 +214,7 @@ function CommandView:update()
|
|||
|
||||
-- update suggestions box 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)
|
||||
|
||||
-- update suggestion cursor offset
|
||||
|
@ -254,7 +283,9 @@ end
|
|||
|
||||
function CommandView:draw()
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -230,6 +230,12 @@ function common.basename(path)
|
|||
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)
|
||||
if HOME and string.find(text, HOME, 1, true) == 1 then
|
||||
local dir_pos = #HOME + 1
|
||||
|
@ -257,18 +263,11 @@ function common.home_expand(text)
|
|||
end
|
||||
|
||||
|
||||
function common.normalize_path(filename)
|
||||
if filename and 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 t = {}
|
||||
if s:match("^[/\\]") then
|
||||
t[#t + 1] = ""
|
||||
end
|
||||
for fragment in string.gmatch(s, "([^/\\]+)") do
|
||||
t[#t + 1] = fragment
|
||||
end
|
||||
|
@ -276,12 +275,39 @@ local function split_on_slash(s, sep_pattern)
|
|||
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)
|
||||
return filename and string.find(filename, path .. PATHSEP, 1, true) == 1
|
||||
return string.find(filename, path .. PATHSEP, 1, true) == 1
|
||||
end
|
||||
|
||||
|
||||
function common.relative_path(ref_dir, dir)
|
||||
local drive_pattern = "^(%a):\\"
|
||||
local drive, ref_drive = dir:match(drive_pattern), ref_dir:match(drive_pattern)
|
||||
if drive and ref_drive and drive ~= ref_drive then
|
||||
-- Windows, different drives, system.absolute_path fails for C:\..\D:\
|
||||
return dir
|
||||
end
|
||||
local ref_ls = split_on_slash(ref_dir)
|
||||
local dir_ls = split_on_slash(dir)
|
||||
local i = 1
|
||||
|
|
|
@ -11,6 +11,7 @@ config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
|||
config.undo_merge_timeout = 0.3
|
||||
config.max_undos = 10000
|
||||
config.max_tabs = 10
|
||||
config.always_show_tabs = false
|
||||
config.highlight_current_line = true
|
||||
config.line_height = 1.2
|
||||
config.indent_size = 2
|
||||
|
@ -27,7 +28,9 @@ config.tab_close_button = true
|
|||
|
||||
-- Disable plugin loading setting to false the config entry
|
||||
-- of the same name.
|
||||
config.trimwhitespace = false
|
||||
config.lineguide = false
|
||||
config.plugins = {}
|
||||
|
||||
config.plugins.trimwhitespace = false
|
||||
config.plugins.lineguide = false
|
||||
|
||||
return config
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local Object = require "core.object"
|
||||
|
||||
local border_width = 1
|
||||
local divider_width = 1
|
||||
local DIVIDER = {}
|
||||
|
||||
local ContextMenu = Object:extend()
|
||||
|
||||
ContextMenu.DIVIDER = DIVIDER
|
||||
|
||||
function ContextMenu:new()
|
||||
self.itemset = {}
|
||||
self.show_context_menu = false
|
||||
self.selected = -1
|
||||
self.height = 0
|
||||
self.position = { x = 0, y = 0 }
|
||||
end
|
||||
|
||||
local function get_item_size(item)
|
||||
local lw, lh
|
||||
if item == DIVIDER then
|
||||
lw = 0
|
||||
lh = divider_width
|
||||
else
|
||||
lw = style.font:get_width(item.text)
|
||||
if item.info then
|
||||
lw = lw + style.padding.x + style.font:get_width(item.info)
|
||||
end
|
||||
lh = style.font:get_height() + style.padding.y
|
||||
end
|
||||
return lw, lh
|
||||
end
|
||||
|
||||
function ContextMenu:register(predicate, items)
|
||||
if type(predicate) == "string" then
|
||||
predicate = require(predicate)
|
||||
end
|
||||
if type(predicate) == "table" then
|
||||
local class = predicate
|
||||
predicate = function() return core.active_view:is(class) end
|
||||
end
|
||||
|
||||
local width, height = 0, 0 --precalculate the size of context menu
|
||||
for i, item in ipairs(items) do
|
||||
if item ~= DIVIDER then
|
||||
item.info = keymap.reverse_map[item.command]
|
||||
end
|
||||
local lw, lh = get_item_size(item)
|
||||
width = math.max(width, lw)
|
||||
height = height + lh
|
||||
end
|
||||
width = width + style.padding.x * 2
|
||||
items.width, items.height = width, height
|
||||
table.insert(self.itemset, { predicate = predicate, items = items })
|
||||
end
|
||||
|
||||
function ContextMenu:show(x, y)
|
||||
self.items = nil
|
||||
local items_list = { width = 0, height = 0 }
|
||||
for _, items in ipairs(self.itemset) do
|
||||
if items.predicate(x, y) then
|
||||
items_list.width = math.max(items_list.width, items.items.width)
|
||||
items_list.height = items_list.height + items.items.height
|
||||
for _, subitems in ipairs(items.items) do
|
||||
table.insert(items_list, subitems)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #items_list > 0 then
|
||||
self.items = items_list
|
||||
local w, h = self.items.width, self.items.height
|
||||
|
||||
-- by default the box is opened on the right and below
|
||||
if x + w >= core.root_view.size.x then
|
||||
x = x - w
|
||||
end
|
||||
if y + h >= core.root_view.size.y then
|
||||
y = y - h
|
||||
end
|
||||
|
||||
self.position.x, self.position.y = x, y
|
||||
self.show_context_menu = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ContextMenu:hide()
|
||||
self.show_context_menu = false
|
||||
self.items = nil
|
||||
self.selected = -1
|
||||
self.height = 0
|
||||
end
|
||||
|
||||
function ContextMenu:each_item()
|
||||
local x, y, w = self.position.x, self.position.y, self.items.width
|
||||
local oy = y
|
||||
return coroutine.wrap(function()
|
||||
for i, item in ipairs(self.items) do
|
||||
local _, lh = get_item_size(item)
|
||||
if y - oy > self.height then break end
|
||||
coroutine.yield(i, item, x, y, w, lh)
|
||||
y = y + lh
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function ContextMenu:on_mouse_moved(px, py)
|
||||
if not self.show_context_menu then return end
|
||||
|
||||
self.selected = -1
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
if px > x and px <= x + w and py > y and py <= y + h then
|
||||
self.selected = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if self.selected >= 0 then
|
||||
core.request_cursor("arrow")
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function ContextMenu:on_selected(item)
|
||||
if type(item.command) == "string" then
|
||||
command.perform(item.command)
|
||||
else
|
||||
item.command()
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:on_mouse_pressed(button, x, y, clicks)
|
||||
local selected = (self.items or {})[self.selected]
|
||||
local caught = false
|
||||
|
||||
self:hide()
|
||||
if button == "left" then
|
||||
if selected then
|
||||
self:on_selected(selected)
|
||||
caught = true
|
||||
end
|
||||
end
|
||||
|
||||
if button == "right" then
|
||||
caught = self:show(x, y)
|
||||
end
|
||||
return caught
|
||||
end
|
||||
|
||||
-- copied from core.docview
|
||||
function ContextMenu:move_towards(t, k, dest, rate)
|
||||
if type(t) ~= "table" then
|
||||
return self:move_towards(self, t, k, dest, rate)
|
||||
end
|
||||
local val = t[k]
|
||||
if not config.transitions or math.abs(val - dest) < 0.5 then
|
||||
t[k] = dest
|
||||
else
|
||||
rate = rate or 0.5
|
||||
if config.fps ~= 60 or config.animation_rate ~= 1 then
|
||||
local dt = 60 / config.fps
|
||||
rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt)
|
||||
end
|
||||
t[k] = common.lerp(val, dest, rate)
|
||||
end
|
||||
if val ~= dest then
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:update()
|
||||
if self.show_context_menu then
|
||||
self:move_towards("height", self.items.height)
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:draw()
|
||||
if not self.show_context_menu then return end
|
||||
core.root_view:defer_draw(self.draw_context_menu, self)
|
||||
end
|
||||
|
||||
function ContextMenu:draw_context_menu()
|
||||
if not self.items then return end
|
||||
local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height
|
||||
|
||||
renderer.draw_rect(
|
||||
bx - border_width,
|
||||
by - border_width,
|
||||
bw + (border_width * 2),
|
||||
bh + (border_width * 2),
|
||||
style.divider
|
||||
)
|
||||
renderer.draw_rect(bx, by, bw, bh, style.background3)
|
||||
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
if item == DIVIDER then
|
||||
renderer.draw_rect(x, y, w, h, style.caret)
|
||||
else
|
||||
if i == self.selected then
|
||||
renderer.draw_rect(x, y, w, h, style.selection)
|
||||
end
|
||||
|
||||
common.draw_text(style.font, style.text, item.text, "left", x + style.padding.x, y, w, h)
|
||||
if item.info then
|
||||
common.draw_text(style.font, style.dim, item.info, "right", x, y, w - style.padding.x, h)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ContextMenu
|
|
@ -17,10 +17,15 @@ local function split_lines(text)
|
|||
return res
|
||||
end
|
||||
|
||||
function Doc:new(filename)
|
||||
|
||||
function Doc:new(filename, abs_filename, new_file)
|
||||
self.new_file = new_file
|
||||
self:reset()
|
||||
if filename then
|
||||
self:load(filename)
|
||||
self:set_filename(filename, abs_filename)
|
||||
if not new_file then
|
||||
self:load(filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -47,16 +52,15 @@ function Doc:reset_syntax()
|
|||
end
|
||||
|
||||
|
||||
function Doc:set_filename(filename)
|
||||
function Doc:set_filename(filename, abs_filename)
|
||||
self.filename = filename
|
||||
self.abs_filename = system.absolute_path(filename)
|
||||
self.abs_filename = abs_filename
|
||||
end
|
||||
|
||||
|
||||
function Doc:load(filename)
|
||||
local fp = assert( io.open(filename, "rb") )
|
||||
self:reset()
|
||||
self:set_filename(filename)
|
||||
self.lines = {}
|
||||
for line in fp:lines() do
|
||||
if line:byte(-1) == 13 then
|
||||
|
@ -73,17 +77,20 @@ function Doc:load(filename)
|
|||
end
|
||||
|
||||
|
||||
function Doc:save(filename)
|
||||
filename = filename or assert(self.filename, "no filename set to default to")
|
||||
function Doc:save(filename, abs_filename)
|
||||
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") )
|
||||
for _, line in ipairs(self.lines) do
|
||||
if self.crlf then line = line:gsub("\n", "\r\n") end
|
||||
fp:write(line)
|
||||
end
|
||||
fp:close()
|
||||
if filename then
|
||||
self:set_filename(filename)
|
||||
end
|
||||
self:set_filename(filename, abs_filename)
|
||||
self.new_file = false
|
||||
self:reset_syntax()
|
||||
self:clean()
|
||||
end
|
||||
|
@ -95,7 +102,7 @@ end
|
|||
|
||||
|
||||
function Doc:is_dirty()
|
||||
return self.clean_change_id ~= self:get_change_id()
|
||||
return self.clean_change_id ~= self:get_change_id() or self.new_file
|
||||
end
|
||||
|
||||
|
||||
|
@ -117,6 +124,19 @@ function Doc:get_selection(sort)
|
|||
return line1, col1, line2, col2, sort
|
||||
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()
|
||||
local line1, col1, line2, col2 = self:get_selection(false)
|
||||
return line1 ~= line2 or col1 ~= col2
|
||||
|
@ -166,10 +186,12 @@ function Doc:merge_cursors(idx)
|
|||
if self.selections[i] == self.selections[j] and
|
||||
self.selections[i+1] == self.selections[j+1] then
|
||||
common.splice(self.selections, i, 4)
|
||||
common.splice(self.cursor_clipboard, i, 1)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if #self.selections <= 4 then self.cursor_clipboard = {} end
|
||||
end
|
||||
|
||||
local function selection_iterator(invariant, idx)
|
||||
|
@ -300,6 +322,7 @@ end
|
|||
function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||
-- split text into lines and merge with line at insertion point
|
||||
local lines = split_lines(text)
|
||||
local len = #lines[#lines]
|
||||
local before = self.lines[line]:sub(1, col - 1)
|
||||
local after = self.lines[line]:sub(col)
|
||||
for i = 1, #lines - 1 do
|
||||
|
@ -310,6 +333,14 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
|
|||
|
||||
-- splice lines into line array
|
||||
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
|
||||
local line2, col2 = self:position_offset(line, col, #text)
|
||||
|
@ -334,6 +365,14 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
|||
|
||||
-- splice line into line array
|
||||
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
|
||||
self.highlighter:invalidate(line1)
|
||||
|
@ -370,7 +409,7 @@ end
|
|||
|
||||
|
||||
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
|
||||
self:delete_to_cursor(sidx)
|
||||
end
|
||||
|
@ -379,12 +418,7 @@ function Doc:text_input(text, idx)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
|
||||
local old_text = self:get_text(line1, col1, line2, col2)
|
||||
local new_text, n = fn(old_text)
|
||||
if old_text ~= new_text then
|
||||
|
@ -392,12 +426,27 @@ function Doc:replace(fn)
|
|||
self:remove(line1, col1, line2, col2)
|
||||
if line1 == line2 and col1 == col2 then
|
||||
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
|
||||
return n
|
||||
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, ...)
|
||||
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
|
||||
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)
|
||||
return line, math.huge
|
||||
|
|
|
@ -9,6 +9,7 @@ local View = require "core.view"
|
|||
|
||||
local DocView = View:extend()
|
||||
|
||||
DocView.context = "session"
|
||||
|
||||
local function move_to_line_offset(dv, line, col, offset)
|
||||
local xo = dv.last_x_offset
|
||||
|
@ -112,7 +113,8 @@ end
|
|||
|
||||
|
||||
function DocView:get_gutter_width()
|
||||
return self:get_font():get_width(#self.doc.lines) + style.padding.x * 2
|
||||
local padding = style.padding.x * 2
|
||||
return self:get_font():get_width(#self.doc.lines) + padding, padding
|
||||
end
|
||||
|
||||
|
||||
|
@ -365,23 +367,27 @@ function DocView:draw_line_body(idx, x, y)
|
|||
local x1 = x + self:get_col_x_offset(idx, col1)
|
||||
local x2 = x + self:get_col_x_offset(idx, col2)
|
||||
local lh = self:get_line_height()
|
||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||
if x1 ~= x2 then
|
||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||
end
|
||||
end
|
||||
end
|
||||
local draw_highlight = nil
|
||||
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)
|
||||
if draw_highlight ~= false and config.highlight_current_line
|
||||
and line1 == idx and core.active_view == self then
|
||||
self:draw_line_highlight(x + self.scroll.x, y)
|
||||
draw_highlight = (line1 == line2 and col1 == col2)
|
||||
end
|
||||
end
|
||||
if draw_highlight then self:draw_line_highlight(x + self.scroll.x, y) end
|
||||
|
||||
-- draw line's text
|
||||
self:draw_line_text(idx, x, y)
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw_line_gutter(idx, x, y)
|
||||
function DocView:draw_line_gutter(idx, x, y, width)
|
||||
local color = style.line_number
|
||||
for _, line1, _, line2 in self.doc:get_selections(true) do
|
||||
if idx >= line1 and idx <= line2 then
|
||||
|
@ -391,7 +397,7 @@ function DocView:draw_line_gutter(idx, x, y)
|
|||
end
|
||||
local yoffset = self:get_line_text_y_offset()
|
||||
x = x + style.padding.x
|
||||
renderer.draw_text(self:get_font(), idx, x, y + yoffset, color)
|
||||
common.draw_text(self:get_font(), color, idx, "right", x, y + yoffset, width, self:get_line_height())
|
||||
end
|
||||
|
||||
|
||||
|
@ -420,12 +426,12 @@ function DocView:draw()
|
|||
local lh = self:get_line_height()
|
||||
|
||||
local x, y = self:get_line_screen_position(minline)
|
||||
local gw, gpad = self:get_gutter_width()
|
||||
for i = minline, maxline do
|
||||
self:draw_line_gutter(i, self.position.x, y)
|
||||
self:draw_line_gutter(i, self.position.x, y, gpad and gw - gpad or gw)
|
||||
y = y + lh
|
||||
end
|
||||
|
||||
local gw = self:get_gutter_width()
|
||||
local pos = self.position
|
||||
x, y = self:get_line_screen_position(minline)
|
||||
core.push_clip_rect(pos.x + gw, pos.y, self.size.x, self.size.y)
|
||||
|
|
|
@ -17,10 +17,7 @@ local core = {}
|
|||
|
||||
local function load_session()
|
||||
local ok, t = pcall(dofile, USERDIR .. "/session.lua")
|
||||
if ok then
|
||||
return t.recents, t.window, t.window_mode
|
||||
end
|
||||
return {}
|
||||
return ok and t or {}
|
||||
end
|
||||
|
||||
|
||||
|
@ -30,20 +27,16 @@ local function save_session()
|
|||
fp:write("return {recents=", common.serialize(core.recent_projects),
|
||||
", window=", common.serialize(table.pack(system.get_window_size())),
|
||||
", window_mode=", common.serialize(system.get_window_mode()),
|
||||
", previous_find=", common.serialize(core.previous_find),
|
||||
", previous_replace=", common.serialize(core.previous_replace),
|
||||
"}\n")
|
||||
fp:close()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function normalize_path(s)
|
||||
local drive, path = s:match("^([a-z]):([/\\].*)")
|
||||
return drive and drive:upper() .. ":" .. path or s
|
||||
end
|
||||
|
||||
|
||||
local function update_recents_project(action, dir_path_abs)
|
||||
local dirname = normalize_path(dir_path_abs)
|
||||
local dirname = common.normalize_path(dir_path_abs)
|
||||
if not dirname then return end
|
||||
local recents = core.recent_projects
|
||||
local n = #recents
|
||||
|
@ -63,7 +56,7 @@ function core.set_project_dir(new_dir, change_project_fn)
|
|||
local chdir_ok = pcall(system.chdir, new_dir)
|
||||
if chdir_ok then
|
||||
if change_project_fn then change_project_fn() end
|
||||
core.project_dir = normalize_path(new_dir)
|
||||
core.project_dir = common.normalize_path(new_dir)
|
||||
core.project_directories = {}
|
||||
core.add_project_directory(new_dir)
|
||||
return true
|
||||
|
@ -153,17 +146,14 @@ end
|
|||
|
||||
function core.project_subdir_set_show(dir, filename, show)
|
||||
dir.shown_subdir[filename] = show
|
||||
if dir.files_limit then
|
||||
if dir.files_limit and PLATFORM == "Linux" then
|
||||
local fullpath = dir.name .. PATHSEP .. filename
|
||||
if PLATFORM == "Linux" then
|
||||
if show then
|
||||
local success = system.watch_dir_add(dir.watch_id, fullpath)
|
||||
print("DEBUG: watch_dir_add", fullpath, "success:", success)
|
||||
else
|
||||
print("DEBUG dir", dir.name, "filename", filename, "watch_id:", dir.watch_id)
|
||||
local success = system.watch_dir_rm(dir.watch_id, fullpath)
|
||||
print("DEBUG: watch_dir_rm", fullpath, "success:", success)
|
||||
end
|
||||
local watch_fn = show and system.watch_dir_add or system.watch_dir_rm
|
||||
print("DEBUG dir", dir.name, "filename", filename, "watch_id:", dir.watch_id, "show:", show)
|
||||
local success = watch_fn(dir.watch_id, fullpath)
|
||||
print("DEBUG: ", show and "watch_dir_add" or "watch_dir_rm", fullpath, "success:", success)
|
||||
if not success then
|
||||
core.log("Internal warning: error calling system.watch_dir_%s", show and "add" or "rm")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -208,14 +198,12 @@ function core.add_project_directory(path)
|
|||
-- top directories has a file-like "item" but the item.filename
|
||||
-- will be simply the name of the directory, without its path.
|
||||
-- The field item.topdir will identify it as a top level directory.
|
||||
path = normalize_path(path)
|
||||
-- local watch_id = system.watch_dir(path)
|
||||
path = common.normalize_path(path)
|
||||
local dir = {
|
||||
name = path,
|
||||
item = {filename = common.basename(path), type = "dir", topdir = true},
|
||||
files_limit = false,
|
||||
is_dirty = true,
|
||||
-- watch_id = watch_id,
|
||||
shown_subdir = {},
|
||||
}
|
||||
table.insert(core.project_directories, dir)
|
||||
|
@ -490,8 +478,8 @@ local style = require "core.style"
|
|||
------------------------------- Fonts ----------------------------------------
|
||||
|
||||
-- customize fonts:
|
||||
-- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
|
||||
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-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", 14 * SCALE)
|
||||
--
|
||||
-- font names used by lite:
|
||||
-- style.font : user interface
|
||||
|
@ -512,11 +500,11 @@ local style = require "core.style"
|
|||
|
||||
-- enable or disable plugin loading setting config entries:
|
||||
|
||||
-- enable trimwhitespace, otherwise it is disable by default:
|
||||
-- config.trimwhitespace = true
|
||||
-- enable plugins.trimwhitespace, otherwise it is disable by default:
|
||||
-- config.plugins.trimwhitespace = true
|
||||
--
|
||||
-- disable detectindent, otherwise it is enabled by default
|
||||
-- config.detectindent = false
|
||||
-- config.plugins.detectindent = false
|
||||
]])
|
||||
init_file:close()
|
||||
end
|
||||
|
@ -592,13 +580,15 @@ function core.init()
|
|||
end
|
||||
|
||||
do
|
||||
local recent_projects, window_position, window_mode = load_session()
|
||||
if window_mode == "normal" then
|
||||
system.set_window_size(table.unpack(window_position))
|
||||
elseif window_mode == "maximized" then
|
||||
local session = load_session()
|
||||
if session.window_mode == "normal" then
|
||||
system.set_window_size(table.unpack(session.window))
|
||||
elseif session.window_mode == "maximized" then
|
||||
system.set_window_mode("maximized")
|
||||
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
|
||||
|
||||
local project_dir = core.recent_projects[1] or "."
|
||||
|
@ -618,7 +608,10 @@ function core.init()
|
|||
project_dir = arg_filename
|
||||
project_dir_explicit = true
|
||||
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
|
||||
|
||||
|
@ -651,6 +644,7 @@ function core.init()
|
|||
core.redraw = true
|
||||
core.visited_files = {}
|
||||
core.restart_request = false
|
||||
core.quit_request = false
|
||||
core.replacements = whitespace_replacements()
|
||||
|
||||
core.root_view = RootView()
|
||||
|
@ -726,10 +720,10 @@ function core.init()
|
|||
end
|
||||
|
||||
|
||||
function core.confirm_close_all(close_fn, ...)
|
||||
function core.confirm_close_docs(docs, close_fn, ...)
|
||||
local dirty_count = 0
|
||||
local dirty_name
|
||||
for _, doc in ipairs(core.docs) do
|
||||
for _, doc in ipairs(docs or core.docs) do
|
||||
if doc:is_dirty() then
|
||||
dirty_count = dirty_count + 1
|
||||
dirty_name = doc:get_name()
|
||||
|
@ -782,24 +776,6 @@ do
|
|||
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)
|
||||
if force then
|
||||
delete_temp_files()
|
||||
|
@ -807,12 +783,12 @@ local function quit_with_function(quit_fn, force)
|
|||
save_session()
|
||||
quit_fn()
|
||||
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
|
||||
|
||||
function core.quit(force)
|
||||
quit_with_function(os.exit, force)
|
||||
quit_with_function(function() core.quit_request = true end, force)
|
||||
end
|
||||
|
||||
|
||||
|
@ -826,8 +802,8 @@ local function check_plugin_version(filename)
|
|||
if info ~= nil and info.type == "dir" then
|
||||
filename = filename .. "/init.lua"
|
||||
info = system.get_file_info(filename)
|
||||
if not info then return true end
|
||||
end
|
||||
if not info or not filename:match("%.lua$") then return false end
|
||||
local f = io.open(filename, "r")
|
||||
if not f then return false end
|
||||
local version_match = false
|
||||
|
@ -841,13 +817,13 @@ local function check_plugin_version(filename)
|
|||
-- Future versions will look only at the mod-version tag.
|
||||
local version = line:match('%-%-%s*lite%-xl%s*(%d+%.%d+)$')
|
||||
if version then
|
||||
-- we consider the version tag 1.16 equivalent to mod-version:1
|
||||
version_match = (version == '1.16' and MOD_VERSION == "1")
|
||||
-- we consider the version tag 2.0 equivalent to mod-version:2
|
||||
version_match = (version == '2.0' and MOD_VERSION == "2")
|
||||
break
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
return version_match
|
||||
return true, version_match
|
||||
end
|
||||
|
||||
|
||||
|
@ -857,20 +833,25 @@ function core.load_plugins()
|
|||
userdir = {dir = USERDIR, plugins = {}},
|
||||
datadir = {dir = DATADIR, plugins = {}},
|
||||
}
|
||||
for _, root_dir in ipairs {USERDIR, DATADIR} do
|
||||
local files = {}
|
||||
for _, root_dir in ipairs {DATADIR, USERDIR} do
|
||||
local plugin_dir = root_dir .. "/plugins"
|
||||
local files = system.list_dir(plugin_dir)
|
||||
for _, filename in ipairs(files or {}) do
|
||||
local basename = filename:match("(.-)%.lua$") or filename
|
||||
local version_match = check_plugin_version(plugin_dir .. '/' .. filename)
|
||||
for _, filename in ipairs(system.list_dir(plugin_dir) or {}) do
|
||||
files[filename] = plugin_dir -- user plugins will always replace system plugins
|
||||
end
|
||||
end
|
||||
|
||||
for filename, plugin_dir in pairs(files) do
|
||||
local basename = filename:match("(.-)%.lua$") or filename
|
||||
local is_lua_file, version_match = check_plugin_version(plugin_dir .. '/' .. filename)
|
||||
if is_lua_file then
|
||||
if not version_match then
|
||||
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
|
||||
ls[#ls + 1] = filename
|
||||
local list = refused_list[plugin_dir:find(USERDIR, 1, true) == 1 and 'userdir' or 'datadir'].plugins
|
||||
table.insert(list, filename)
|
||||
end
|
||||
if version_match and config[basename] ~= false then
|
||||
local modname = "plugins." .. basename
|
||||
local ok = core.try(require, modname)
|
||||
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
|
||||
|
@ -920,8 +901,12 @@ end
|
|||
|
||||
function core.set_active_view(view)
|
||||
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 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
|
||||
core.set_visited(view.doc.filename)
|
||||
end
|
||||
|
@ -971,10 +956,30 @@ function core.normalize_to_project_dir(filename)
|
|||
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)
|
||||
local new_file = not filename or not system.get_file_info(filename)
|
||||
local abs_filename
|
||||
if filename then
|
||||
-- normalize filename and set absolute filename then
|
||||
-- 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
|
||||
if doc.abs_filename and abs_filename == doc.abs_filename then
|
||||
return doc
|
||||
|
@ -982,8 +987,7 @@ function core.open_doc(filename)
|
|||
end
|
||||
end
|
||||
-- no existing doc for filename; create new
|
||||
filename = core.normalize_to_project_dir(filename)
|
||||
local doc = Doc(filename)
|
||||
local doc = Doc(filename, abs_filename, new_file)
|
||||
table.insert(core.docs, doc)
|
||||
core.log_quiet(filename and "Opened doc \"%s\"" or "Opened new doc", filename)
|
||||
return doc
|
||||
|
@ -1032,6 +1036,23 @@ function core.error(...)
|
|||
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, ...)
|
||||
local err
|
||||
local ok, res = xpcall(fn, function(msg)
|
||||
|
@ -1061,9 +1082,12 @@ function core.dir_rescan_add_job(dir, filepath)
|
|||
if dirpath then
|
||||
-- check if the directory is in the project files list, if not exit
|
||||
local dir_index, dir_match = file_search(dir.files, {filename = dirpath, type = "dir"})
|
||||
local dir_filename = dir.files[dir_index].filename
|
||||
if not dir_match or not core.project_subdir_is_shown(dir, dir_filename) then
|
||||
print("DEBUG do not start a rescan job for", abs_dirpath); return end
|
||||
-- Note that is dir_match is false dir_index greaten than the last valid index.
|
||||
-- We use dir_index to index dir.files below only if dir_match is true.
|
||||
if not dir_match or not core.project_subdir_is_shown(dir, dir.files[dir_index].filename) then
|
||||
print("DEBUG do not start a rescan job for", abs_dirpath)
|
||||
return
|
||||
end
|
||||
end
|
||||
local new_time = system.get_time() + 1
|
||||
|
||||
|
@ -1263,9 +1287,9 @@ function core.run()
|
|||
while true do
|
||||
core.frame_start = system.get_time()
|
||||
local did_redraw = core.step()
|
||||
local need_more_work = run_threads()
|
||||
if core.restart_request then break end
|
||||
if not did_redraw and not need_more_work and not core.has_pending_rescan() then
|
||||
local need_more_work = run_threads() or core.has_pending_rescan()
|
||||
if core.restart_request or core.quit_request then break end
|
||||
if not did_redraw and not need_more_work then
|
||||
idle_iterations = idle_iterations + 1
|
||||
-- do not wait of events at idle_iterations = 1 to give a chance at core.step to run
|
||||
-- and set "redraw" flag.
|
||||
|
|
|
@ -52,23 +52,27 @@ local function keymap_macos(keymap)
|
|||
["shift+tab"] = "doc:unindent",
|
||||
["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+backspace"] = "doc:delete-to-start-of-indentation",
|
||||
["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+delete"] = "doc:delete-to-end-of-line",
|
||||
["return"] = { "command:submit", "doc:newline", "dialog:select" },
|
||||
["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" },
|
||||
["cmd+return"] = "doc:newline-below",
|
||||
["cmd+shift+return"] = "doc:newline-above",
|
||||
["cmd+j"] = "doc:join-lines",
|
||||
["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+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||
["cmd+/"] = "doc:toggle-line-comments",
|
||||
["cmd+up"] = "doc:move-lines-up",
|
||||
["cmd+down"] = "doc:move-lines-down",
|
||||
["option+up"] = "doc:move-lines-up",
|
||||
["option+down"] = "doc:move-lines-down",
|
||||
["cmd+shift+d"] = "doc:duplicate-lines",
|
||||
["cmd+shift+k"] = "doc:delete-lines",
|
||||
|
||||
|
@ -76,14 +80,16 @@ local function keymap_macos(keymap)
|
|||
["right"] = { "doc:move-to-next-char", "dialog:next-entry"},
|
||||
["up"] = { "command:select-previous", "doc:move-to-previous-line" },
|
||||
["down"] = { "command:select-next", "doc:move-to-next-line" },
|
||||
["cmd+left"] = "doc:move-to-previous-word-start",
|
||||
["cmd+right"] = "doc:move-to-next-word-end",
|
||||
["option+left"] = "doc:move-to-previous-word-start",
|
||||
["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-next-block-end",
|
||||
["home"] = "doc:move-to-start-of-line",
|
||||
["home"] = "doc:move-to-start-of-indentation",
|
||||
["end"] = "doc:move-to-end-of-line",
|
||||
["cmd+home"] = "doc:move-to-start-of-doc",
|
||||
["cmd+end"] = "doc:move-to-end-of-doc",
|
||||
["cmd+up"] = "doc:move-to-start-of-doc",
|
||||
["cmd+down"] = "doc:move-to-end-of-doc",
|
||||
["pageup"] = "doc:move-to-previous-page",
|
||||
["pagedown"] = "doc:move-to-next-page",
|
||||
|
||||
|
@ -91,18 +97,20 @@ local function keymap_macos(keymap)
|
|||
["shift+right"] = "doc:select-to-next-char",
|
||||
["shift+up"] = "doc:select-to-previous-line",
|
||||
["shift+down"] = "doc:select-to-next-line",
|
||||
["cmd+shift+left"] = "doc:select-to-previous-word-start",
|
||||
["cmd+shift+right"] = "doc:select-to-next-word-end",
|
||||
["option+shift+left"] = "doc:select-to-previous-word-start",
|
||||
["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-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",
|
||||
["cmd+shift+home"] = "doc:select-to-start-of-doc",
|
||||
["cmd+shift+end"] = "doc:select-to-end-of-doc",
|
||||
["cmd+shift+up"] = "doc:select-to-start-of-doc",
|
||||
["cmd+shift+down"] = "doc:select-to-end-of-doc",
|
||||
["shift+pageup"] = "doc:select-to-previous-page",
|
||||
["shift+pagedown"] = "doc:select-to-next-page",
|
||||
["cmd+shift+up"] = "doc:create-cursor-previous-line",
|
||||
["cmd+shift+down"] = "doc:create-cursor-next-line"
|
||||
["cmd+option+up"] = "doc:create-cursor-previous-line",
|
||||
["cmd+option+down"] = "doc:create-cursor-next-line"
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -108,6 +108,7 @@ keymap.add_direct {
|
|||
["ctrl+shift+c"] = "core:change-project-folder",
|
||||
["ctrl+shift+o"] = "core:open-project-folder",
|
||||
["alt+return"] = "core:toggle-fullscreen",
|
||||
["f11"] = "core:toggle-fullscreen",
|
||||
|
||||
["alt+shift+j"] = "root:split-left",
|
||||
["alt+shift+l"] = "root:split-right",
|
||||
|
@ -137,8 +138,8 @@ keymap.add_direct {
|
|||
["ctrl+r"] = "find-replace:replace",
|
||||
["f3"] = "find-replace:repeat-find",
|
||||
["shift+f3"] = "find-replace:previous-find",
|
||||
["ctrl+i"] = "find-replace:toggle-insensitivity",
|
||||
["ctrl+shift+i"] = "find-replace:toggle-plain",
|
||||
["ctrl+i"] = "find-replace:toggle-sensitivity",
|
||||
["ctrl+shift+i"] = "find-replace:toggle-regex",
|
||||
["ctrl+g"] = "doc:go-to-line",
|
||||
["ctrl+s"] = "doc:save",
|
||||
["ctrl+shift+s"] = "doc:save-as",
|
||||
|
@ -167,8 +168,10 @@ keymap.add_direct {
|
|||
["ctrl+shift+return"] = "doc:newline-above",
|
||||
["ctrl+j"] = "doc:join-lines",
|
||||
["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+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||
["ctrl+/"] = "doc:toggle-line-comments",
|
||||
["ctrl+up"] = "doc:move-lines-up",
|
||||
["ctrl+down"] = "doc:move-lines-down",
|
||||
|
@ -183,7 +186,7 @@ keymap.add_direct {
|
|||
["ctrl+right"] = "doc:move-to-next-word-end",
|
||||
["ctrl+["] = "doc:move-to-previous-block-start",
|
||||
["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",
|
||||
["ctrl+home"] = "doc:move-to-start-of-doc",
|
||||
["ctrl+end"] = "doc:move-to-end-of-doc",
|
||||
|
@ -198,7 +201,7 @@ keymap.add_direct {
|
|||
["ctrl+shift+right"] = "doc:select-to-next-word-end",
|
||||
["ctrl+shift+["] = "doc:select-to-previous-block-start",
|
||||
["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",
|
||||
["ctrl+shift+home"] = "doc:select-to-start-of-doc",
|
||||
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
|
||||
|
|
|
@ -1,14 +1,45 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local style = require "core.style"
|
||||
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()
|
||||
|
||||
LogView.context = "session"
|
||||
|
||||
function LogView:new()
|
||||
LogView.super.new(self)
|
||||
self.last_item = core.log_items[#core.log_items]
|
||||
self.expanding = {}
|
||||
self.scrollable = true
|
||||
self.yoffset = 0
|
||||
end
|
||||
|
@ -19,6 +50,55 @@ function LogView:get_name()
|
|||
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()
|
||||
local item = core.log_items[#core.log_items]
|
||||
if self.last_item ~= item then
|
||||
|
@ -27,6 +107,14 @@ function LogView:update()
|
|||
self.yoffset = -(style.font:get_height() + style.padding.y)
|
||||
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)
|
||||
|
||||
LogView.super.update(self)
|
||||
|
@ -35,38 +123,48 @@ end
|
|||
|
||||
local function draw_text_multiline(font, text, x, y, color)
|
||||
local th = font:get_height()
|
||||
local resx, resy = x, y
|
||||
local resx = x
|
||||
for line in text:gmatch("[^\n]+") do
|
||||
resy = y
|
||||
resx = renderer.draw_text(style.font, line, x, y, color)
|
||||
y = y + th
|
||||
end
|
||||
return resx, resy
|
||||
return resx, y
|
||||
end
|
||||
|
||||
|
||||
function LogView:draw()
|
||||
self:draw_background(style.background)
|
||||
|
||||
local ox, oy = self:get_content_offset()
|
||||
local th = style.font:get_height()
|
||||
local y = oy + style.padding.y + self.yoffset
|
||||
|
||||
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)
|
||||
local lh = th + style.padding.y -- for one line
|
||||
for _, item, x, y, w in self:each_item() do
|
||||
x = x + style.padding.x
|
||||
local subx = x
|
||||
x, y = draw_text_multiline(style.font, item.text, x, y, style.text)
|
||||
renderer.draw_text(style.font, " at " .. item.at, x, y, style.dim)
|
||||
y = y + th
|
||||
if item.info then
|
||||
subx, y = draw_text_multiline(style.font, item.info, subx, y, style.dim)
|
||||
y = y + th
|
||||
|
||||
local time = os.date(nil, item.time)
|
||||
x = common.draw_text(style.font, style.dim, time, "left", x, y, w, lh)
|
||||
x = x + style.padding.x
|
||||
|
||||
x = common.draw_text(style.code_font, style.dim, is_expanded(item) and "-" or "+", "left", x, y, w, lh)
|
||||
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
|
||||
y = y + style.padding.y
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -193,7 +193,8 @@ function NagView:next()
|
|||
self:change_hovered(common.find_index(self.options, "default_yes"))
|
||||
end
|
||||
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
|
||||
|
||||
function NagView:show(title, message, options, on_select)
|
||||
|
|
|
@ -20,17 +20,6 @@ function Object:extend()
|
|||
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)
|
||||
local mt = getmetatable(self)
|
||||
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).
|
||||
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)
|
||||
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.
|
||||
local function previous_character(str, index)
|
||||
local byte
|
||||
|
@ -23,7 +23,7 @@ end
|
|||
-- Moves to the end of the identified character.
|
||||
local function end_character(str, index)
|
||||
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
|
||||
byte = string.byte(str, index + 1)
|
||||
end
|
||||
|
@ -32,7 +32,7 @@ end
|
|||
|
||||
-- Build off matching. For now, only support basic replacements, but capture
|
||||
-- groupings should be doable. We can even have custom group replacements and
|
||||
-- transformations and stuff in lua. Currently, this takes group replacements
|
||||
-- transformations and stuff in lua. Currently, this takes group replacements
|
||||
-- as \1 - \9.
|
||||
-- Should work on UTF-8 text.
|
||||
regex.gsub = function(pattern_string, str, replacement)
|
||||
|
@ -48,8 +48,8 @@ regex.gsub = function(pattern_string, str, replacement)
|
|||
if #indices > 2 then
|
||||
for i = 1, (#indices/2 - 1) do
|
||||
currentReplacement = string.gsub(
|
||||
currentReplacement,
|
||||
"\\" .. i,
|
||||
currentReplacement,
|
||||
"\\" .. i,
|
||||
str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))
|
||||
)
|
||||
end
|
||||
|
@ -57,10 +57,10 @@ regex.gsub = function(pattern_string, str, replacement)
|
|||
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
|
||||
table.insert(replacements, { indices[1], #currentReplacement+indices[1] })
|
||||
if indices[1] > 1 then
|
||||
result = result ..
|
||||
result = result ..
|
||||
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
|
||||
else
|
||||
result = result .. currentReplacement
|
||||
result = result .. currentReplacement
|
||||
end
|
||||
str = str:sub(indices[2])
|
||||
end
|
||||
|
|
|
@ -5,7 +5,6 @@ local style = require "core.style"
|
|||
local keymap = require "core.keymap"
|
||||
local Object = require "core.object"
|
||||
local View = require "core.view"
|
||||
local CommandView = require "core.commandview"
|
||||
local NagView = require "core.nagview"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
|
@ -240,12 +239,15 @@ end
|
|||
|
||||
function Node:get_divider_overlapping_point(px, py)
|
||||
if self.type ~= "leaf" then
|
||||
local p = 6
|
||||
local x, y, w, h = self:get_divider_rect()
|
||||
x, y = x - p, y - p
|
||||
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
|
||||
local axis = self.type == "hsplit" and "x" or "y"
|
||||
if self.a:is_resizable(axis) and self.b:is_resizable(axis) then
|
||||
local p = 6
|
||||
local x, y, w, h = self:get_divider_rect()
|
||||
x, y = x - p, y - p
|
||||
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
|
||||
return self.a:get_divider_overlapping_point(px, py)
|
||||
or self.b:get_divider_overlapping_point(px, py)
|
||||
|
@ -259,7 +261,7 @@ end
|
|||
|
||||
|
||||
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 x1, y1, w, h = self:get_tab_rect(self.tab_offset)
|
||||
local x2, y2 = self:get_tab_rect(self.tab_offset + tabs_number)
|
||||
|
@ -269,6 +271,16 @@ function Node:get_tab_overlapping_point(px, py)
|
|||
end
|
||||
|
||||
|
||||
function Node:should_show_tabs()
|
||||
if self.locked then return false end
|
||||
if #self.views > 1 then 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 cw = style.icon_font:get_width("C")
|
||||
local pad = style.padding.y
|
||||
|
@ -412,7 +424,7 @@ end
|
|||
function Node:update_layout()
|
||||
if self.type == "leaf" then
|
||||
local av = self.active_view
|
||||
if #self.views > 1 then
|
||||
if self:should_show_tabs() then
|
||||
local _, _, _, th = self:get_tab_rect(1)
|
||||
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
|
||||
|
@ -567,7 +579,7 @@ end
|
|||
|
||||
function Node:draw()
|
||||
if self.type == "leaf" then
|
||||
if #self.views > 1 then
|
||||
if self:should_show_tabs() then
|
||||
self:draw_tabs()
|
||||
end
|
||||
local pos, size = self.active_view.position, self.active_view.size
|
||||
|
@ -591,23 +603,37 @@ function Node:is_empty()
|
|||
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
|
||||
local i = 1
|
||||
while i <= #self.views do
|
||||
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)
|
||||
if view == node_active_view then
|
||||
lost_active_view = true
|
||||
end
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
self.tab_offset = 1
|
||||
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())
|
||||
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
|
||||
else
|
||||
self.a:close_all_docviews()
|
||||
self.b:close_all_docviews()
|
||||
self.a:close_all_docviews(keep_active)
|
||||
self.b:close_all_docviews(keep_active)
|
||||
if self.a:is_empty() and not self.a.is_primary_node then
|
||||
self:consume(self.b)
|
||||
elseif self.b:is_empty() and not self.b.is_primary_node then
|
||||
|
@ -733,8 +759,8 @@ function RootView:open_doc(doc)
|
|||
end
|
||||
|
||||
|
||||
function RootView:close_all_docviews()
|
||||
self.root_node:close_all_docviews()
|
||||
function RootView:close_all_docviews(keep_active)
|
||||
self.root_node:close_all_docviews(keep_active)
|
||||
end
|
||||
|
||||
|
||||
|
@ -823,10 +849,7 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
|||
if node and node:get_scroll_button_index(x, y) then
|
||||
core.request_cursor("arrow")
|
||||
elseif div then
|
||||
local axis = (div.type == "hsplit" and "x" or "y")
|
||||
if div.a:is_resizable(axis) and div.b:is_resizable(axis) then
|
||||
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||
end
|
||||
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||
elseif tab_index then
|
||||
core.request_cursor("arrow")
|
||||
elseif node then
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
-- this file is used by lite-xl to setup the Lua environment when starting
|
||||
VERSION = "1.16.11"
|
||||
MOD_VERSION = "1"
|
||||
VERSION = "@PROJECT_VERSION@"
|
||||
MOD_VERSION = "2"
|
||||
|
||||
SCALE = tonumber(os.getenv("LITE_SCALE")) or SCALE
|
||||
PATHSEP = package.config:sub(1, 1)
|
||||
|
@ -19,4 +19,3 @@ package.path = DATADIR .. '/?.lua;' .. package.path
|
|||
package.path = DATADIR .. '/?/init.lua;' .. package.path
|
||||
package.path = USERDIR .. '/?.lua;' .. package.path
|
||||
package.path = USERDIR .. '/?/init.lua;' .. package.path
|
||||
|
||||
|
|
|
@ -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.
|
||||
-- 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", 14 * SCALE)
|
||||
style.big_font = style.font:copy(40 * SCALE)
|
||||
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 14 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||
style.icon_big_font = style.icon_font:copy(20 * SCALE)
|
||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
|
||||
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 16 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||
style.icon_big_font = style.icon_font:copy(24 * SCALE)
|
||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 14 * SCALE)
|
||||
|
||||
style.background = { common.color "#2e2e32" }
|
||||
style.background2 = { common.color "#252529" }
|
||||
|
|
|
@ -7,6 +7,10 @@ local Object = require "core.object"
|
|||
|
||||
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()
|
||||
self.position = { x = 0, y = 0 }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
|
@ -8,25 +8,65 @@ local keymap = require "core.keymap"
|
|||
local translate = require "core.doc.translate"
|
||||
local RootView = require "core.rootview"
|
||||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
config.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 = {}
|
||||
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 }
|
||||
|
||||
function autocomplete.add(t)
|
||||
function autocomplete.add(t, triggered_manually)
|
||||
local items = {}
|
||||
for text, info in pairs(t.items) do
|
||||
info = (type(info) == "string") and info
|
||||
table.insert(items, setmetatable({ text = text, info = info }, mt))
|
||||
if type(info) == "table" then
|
||||
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
|
||||
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
||||
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()
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
|
@ -109,16 +149,39 @@ local last_line, last_col
|
|||
local function reset_suggestions()
|
||||
suggestions_idx = 1
|
||||
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
|
||||
|
||||
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 doc = core.active_view.doc
|
||||
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
|
||||
local items = {}
|
||||
for _, v in pairs(autocomplete.map) do
|
||||
for _, v in pairs(map) do
|
||||
if common.match_pattern(filename, v.files) then
|
||||
for _, item in pairs(v.items) do
|
||||
table.insert(items, item)
|
||||
|
@ -129,7 +192,7 @@ local function update_suggestions()
|
|||
-- fuzzy match, remove duplicates and store
|
||||
items = common.fuzzy_match(items, partial)
|
||||
local j = 1
|
||||
for i = 1, config.autocomplete_max_suggestions do
|
||||
for i = 1, config.plugins.autocomplete.max_suggestions do
|
||||
suggestions[i] = items[j]
|
||||
while items[j] and items[i].text == items[j].text do
|
||||
items[i].info = items[i].info or items[j].info
|
||||
|
@ -138,7 +201,6 @@ local function update_suggestions()
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_partial_symbol()
|
||||
local doc = core.active_view.doc
|
||||
local line2, col2 = doc:get_selection()
|
||||
|
@ -146,14 +208,12 @@ local function get_partial_symbol()
|
|||
return doc:get_text(line1, col1, line2, col2)
|
||||
end
|
||||
|
||||
|
||||
local function get_active_view()
|
||||
if getmetatable(core.active_view) == DocView then
|
||||
return core.active_view
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_suggestions_rect(av)
|
||||
if #suggestions == 0 then
|
||||
return 0, 0, 0, 0
|
||||
|
@ -175,15 +235,67 @@ local function get_suggestions_rect(av)
|
|||
max_width = math.max(max_width, w)
|
||||
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
|
||||
x - style.padding.x,
|
||||
y - style.padding.y,
|
||||
max_width + style.padding.x * 2,
|
||||
#suggestions * (th + style.padding.y) + style.padding.y
|
||||
max_items * (th + style.padding.y) + style.padding.y
|
||||
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)
|
||||
if #suggestions <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local ah = config.plugins.autocomplete.max_height
|
||||
|
||||
-- draw background rect
|
||||
local rx, ry, rw, rh = get_suggestions_rect(av)
|
||||
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 lh = font:get_height() + style.padding.y
|
||||
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
|
||||
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
||||
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)
|
||||
end
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
-- 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 function show_autocomplete()
|
||||
local av = get_active_view()
|
||||
if av then
|
||||
-- update partial symbol and suggestions
|
||||
partial = get_partial_symbol()
|
||||
if #partial >= 3 then
|
||||
|
||||
if #partial >= config.plugins.autocomplete.min_len or triggered_manually then
|
||||
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
|
||||
reset_suggestions()
|
||||
end
|
||||
|
@ -233,6 +381,30 @@ RootView.on_text_input = function(...)
|
|||
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(...)
|
||||
update(...)
|
||||
|
@ -241,13 +413,19 @@ RootView.update = function(...)
|
|||
if av then
|
||||
-- reset suggestions if caret was moved
|
||||
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
|
||||
|
||||
|
||||
RootView.draw = function(...)
|
||||
draw(...)
|
||||
|
||||
|
@ -258,12 +436,53 @@ RootView.draw = function(...)
|
|||
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()
|
||||
return get_active_view() and #suggestions > 0
|
||||
end
|
||||
|
||||
|
||||
command.add(predicate, {
|
||||
["autocomplete:complete"] = function()
|
||||
local doc = core.active_view.doc
|
||||
|
@ -288,7 +507,9 @@ command.add(predicate, {
|
|||
end,
|
||||
})
|
||||
|
||||
|
||||
--
|
||||
-- Keymaps
|
||||
--
|
||||
keymap.add {
|
||||
["tab"] = "autocomplete:complete",
|
||||
["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 config = require "core.config"
|
||||
local Doc = require "core.doc"
|
||||
|
|
|
@ -1,223 +1,10 @@
|
|||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local Object = require "core.object"
|
||||
local ContextMenu = require "core.contextmenu"
|
||||
local RootView = require "core.rootview"
|
||||
|
||||
local border_width = 1
|
||||
local divider_width = 1
|
||||
local DIVIDER = {}
|
||||
|
||||
local ContextMenu = Object:extend()
|
||||
|
||||
ContextMenu.DIVIDER = DIVIDER
|
||||
|
||||
function ContextMenu:new()
|
||||
self.itemset = {}
|
||||
self.show_context_menu = false
|
||||
self.selected = -1
|
||||
self.height = 0
|
||||
self.position = { x = 0, y = 0 }
|
||||
end
|
||||
|
||||
local function get_item_size(item)
|
||||
local lw, lh
|
||||
if item == DIVIDER then
|
||||
lw = 0
|
||||
lh = divider_width
|
||||
else
|
||||
lw = style.font:get_width(item.text)
|
||||
if item.info then
|
||||
lw = lw + style.padding.x + style.font:get_width(item.info)
|
||||
end
|
||||
lh = style.font:get_height() + style.padding.y
|
||||
end
|
||||
return lw, lh
|
||||
end
|
||||
|
||||
function ContextMenu:register(predicate, items)
|
||||
if type(predicate) == "string" then
|
||||
predicate = require(predicate)
|
||||
end
|
||||
if type(predicate) == "table" then
|
||||
local class = predicate
|
||||
predicate = function() return core.active_view:is(class) end
|
||||
end
|
||||
|
||||
local width, height = 0, 0 --precalculate the size of context menu
|
||||
for i, item in ipairs(items) do
|
||||
if item ~= DIVIDER then
|
||||
item.info = keymap.reverse_map[item.command]
|
||||
end
|
||||
local lw, lh = get_item_size(item)
|
||||
width = math.max(width, lw)
|
||||
height = height + lh
|
||||
end
|
||||
width = width + style.padding.x * 2
|
||||
items.width, items.height = width, height
|
||||
table.insert(self.itemset, { predicate = predicate, items = items })
|
||||
end
|
||||
|
||||
function ContextMenu:show(x, y)
|
||||
self.items = nil
|
||||
local items_list = { width = 0, height = 0 }
|
||||
for _, items in ipairs(self.itemset) do
|
||||
if items.predicate(x, y) then
|
||||
items_list.width = math.max(items_list.width, items.items.width)
|
||||
items_list.height = items_list.height + items.items.height
|
||||
for _, subitems in ipairs(items.items) do
|
||||
table.insert(items_list, subitems)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #items_list > 0 then
|
||||
self.items = items_list
|
||||
local w, h = self.items.width, self.items.height
|
||||
|
||||
-- by default the box is opened on the right and below
|
||||
if x + w >= core.root_view.size.x then
|
||||
x = x - w
|
||||
end
|
||||
if y + h >= core.root_view.size.y then
|
||||
y = y - h
|
||||
end
|
||||
|
||||
self.position.x, self.position.y = x, y
|
||||
self.show_context_menu = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ContextMenu:hide()
|
||||
self.show_context_menu = false
|
||||
self.items = nil
|
||||
self.selected = -1
|
||||
self.height = 0
|
||||
end
|
||||
|
||||
function ContextMenu:each_item()
|
||||
local x, y, w = self.position.x, self.position.y, self.items.width
|
||||
local oy = y
|
||||
return coroutine.wrap(function()
|
||||
for i, item in ipairs(self.items) do
|
||||
local _, lh = get_item_size(item)
|
||||
if y - oy > self.height then break end
|
||||
coroutine.yield(i, item, x, y, w, lh)
|
||||
y = y + lh
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function ContextMenu:on_mouse_moved(px, py)
|
||||
if not self.show_context_menu then return end
|
||||
|
||||
self.selected = -1
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
if px > x and px <= x + w and py > y and py <= y + h then
|
||||
self.selected = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if self.selected >= 0 then
|
||||
core.request_cursor("arrow")
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function ContextMenu:on_selected(item)
|
||||
if type(item.command) == "string" then
|
||||
command.perform(item.command)
|
||||
else
|
||||
item.command()
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:on_mouse_pressed(button, x, y, clicks)
|
||||
local selected = (self.items or {})[self.selected]
|
||||
local caught = false
|
||||
|
||||
self:hide()
|
||||
if button == "left" then
|
||||
if selected then
|
||||
self:on_selected(selected)
|
||||
caught = true
|
||||
end
|
||||
end
|
||||
|
||||
if button == "right" then
|
||||
caught = self:show(x, y)
|
||||
end
|
||||
return caught
|
||||
end
|
||||
|
||||
-- copied from core.docview
|
||||
function ContextMenu:move_towards(t, k, dest, rate)
|
||||
if type(t) ~= "table" then
|
||||
return self:move_towards(self, t, k, dest, rate)
|
||||
end
|
||||
local val = t[k]
|
||||
if not config.transitions or math.abs(val - dest) < 0.5 then
|
||||
t[k] = dest
|
||||
else
|
||||
rate = rate or 0.5
|
||||
if config.fps ~= 60 or config.animation_rate ~= 1 then
|
||||
local dt = 60 / config.fps
|
||||
rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt)
|
||||
end
|
||||
t[k] = common.lerp(val, dest, rate)
|
||||
end
|
||||
if val ~= dest then
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:update()
|
||||
if self.show_context_menu then
|
||||
self:move_towards("height", self.items.height)
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenu:draw()
|
||||
if not self.show_context_menu then return end
|
||||
core.root_view:defer_draw(self.draw_context_menu, self)
|
||||
end
|
||||
|
||||
function ContextMenu:draw_context_menu()
|
||||
if not self.items then return end
|
||||
local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height
|
||||
|
||||
renderer.draw_rect(
|
||||
bx - border_width,
|
||||
by - border_width,
|
||||
bw + (border_width * 2),
|
||||
bh + (border_width * 2),
|
||||
style.divider
|
||||
)
|
||||
renderer.draw_rect(bx, by, bw, bh, style.background3)
|
||||
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
if item == DIVIDER then
|
||||
renderer.draw_rect(x, y, w, h, style.caret)
|
||||
else
|
||||
if i == self.selected then
|
||||
renderer.draw_rect(x, y, w, h, style.selection)
|
||||
end
|
||||
|
||||
common.draw_text(style.font, style.text, item.text, "left", x + style.padding.x, y, w, h)
|
||||
if item.info then
|
||||
common.draw_text(style.font, style.dim, item.info, "right", x, y, w - style.padding.x, h)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local menu = ContextMenu()
|
||||
local on_view_mouse_pressed = RootView.on_view_mouse_pressed
|
||||
local on_mouse_moved = RootView.on_mouse_moved
|
||||
|
@ -255,15 +42,33 @@ keymap.add {
|
|||
["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
|
||||
menu:register("core.docview", {
|
||||
{ text = "Font +", command = "scale:increase" },
|
||||
{ text = "Font -", command = "scale:decrease" },
|
||||
{ text = "Font Reset", command = "scale:reset" },
|
||||
DIVIDER,
|
||||
ContextMenu.DIVIDER,
|
||||
{ text = "Find", command = "find-replace:find" },
|
||||
{ text = "Replace", command = "find-replace:replace" },
|
||||
DIVIDER,
|
||||
ContextMenu.DIVIDER,
|
||||
{ text = "Find Pattern", command = "find-replace:find-pattern" },
|
||||
{ text = "Replace Pattern", command = "find-replace:replace-pattern" },
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.c$", "%.h$", "%.inl$", "%.cpp$", "%.hpp$" },
|
||||
files = { "%.c$", "%.h$", "%.inl$" },
|
||||
comment = "//",
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
|
@ -55,6 +55,17 @@ syntax.add {
|
|||
["true"] = "literal",
|
||||
["false"] = "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",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
pcall(require, "plugins.language_c")
|
||||
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = {
|
||||
"%.h$", "%.inl$", "%.cpp$", "%.cc$", "%.C$", "%.cxx$",
|
||||
"%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$"
|
||||
},
|
||||
comment = "//",
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "0x%x+", type = "number" },
|
||||
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
|
||||
{ pattern = "%.?%d+f?", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "[%a_][%w_]*::", type = "symbol" },
|
||||
{ pattern = "::", type = "symbol" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
|
||||
{ pattern = "#[%a_][%w_]*", type = "keyword" },
|
||||
},
|
||||
symbols = {
|
||||
["alignof"] = "keyword",
|
||||
["alignas"] = "keyword",
|
||||
["private"] = "keyword",
|
||||
["protected"] = "keyword",
|
||||
["public"] = "keyword",
|
||||
["register"] = "keyword",
|
||||
["nullptr"] = "keyword",
|
||||
["operator"] = "keyword",
|
||||
["asm"] = "keyword",
|
||||
["catch"] = "keyword",
|
||||
["throw"] = "keyword",
|
||||
["try"] = "keyword",
|
||||
["compl"] = "keyword",
|
||||
["explicit"] = "keyword",
|
||||
["export"] = "keyword",
|
||||
["concept"] = "keyword",
|
||||
["consteval"] = "keyword",
|
||||
["constexpr"] = "keyword",
|
||||
["constinit"] = "keyword",
|
||||
["const_cast"] = "keyword",
|
||||
["dynamic_cast"] = "keyword",
|
||||
["reinterpret_cast"] = "keyword",
|
||||
["static_cast"] = "keyword",
|
||||
["static_assert"] = "keyword",
|
||||
["template"] = "keyword",
|
||||
["this"] = "keyword",
|
||||
["thread_local"] = "keyword",
|
||||
["requires"] = "keyword",
|
||||
["co_wait"] = "keyword",
|
||||
["co_return"] = "keyword",
|
||||
["co_yield"] = "keyword",
|
||||
["decltype"] = "keyword",
|
||||
["delete"] = "keyword",
|
||||
["export"] = "keyword",
|
||||
["friend"] = "keyword",
|
||||
["typeid"] = "keyword",
|
||||
["typename"] = "keyword",
|
||||
["mutable"] = "keyword",
|
||||
["override"] = "keyword",
|
||||
["virtual"] = "keyword",
|
||||
["using"] = "keyword",
|
||||
["new"] = "keyword",
|
||||
["noexcept"] = "keyword",
|
||||
["if"] = "keyword",
|
||||
["then"] = "keyword",
|
||||
["else"] = "keyword",
|
||||
["elseif"] = "keyword",
|
||||
["do"] = "keyword",
|
||||
["while"] = "keyword",
|
||||
["for"] = "keyword",
|
||||
["break"] = "keyword",
|
||||
["continue"] = "keyword",
|
||||
["return"] = "keyword",
|
||||
["goto"] = "keyword",
|
||||
["typedef"] = "keyword",
|
||||
["enum"] = "keyword",
|
||||
["extern"] = "keyword",
|
||||
["static"] = "keyword",
|
||||
["volatile"] = "keyword",
|
||||
["const"] = "keyword",
|
||||
["inline"] = "keyword",
|
||||
["switch"] = "keyword",
|
||||
["case"] = "keyword",
|
||||
["default"] = "keyword",
|
||||
["auto"] = "keyword",
|
||||
["const"] = "keyword",
|
||||
["void"] = "keyword",
|
||||
["int"] = "keyword2",
|
||||
["short"] = "keyword2",
|
||||
["long"] = "keyword2",
|
||||
["float"] = "keyword2",
|
||||
["double"] = "keyword2",
|
||||
["char"] = "keyword2",
|
||||
["unsigned"] = "keyword2",
|
||||
["bool"] = "keyword2",
|
||||
["true"] = "keyword2",
|
||||
["false"] = "keyword2",
|
||||
["#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
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
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"
|
||||
|
||||
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"
|
||||
|
||||
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"
|
||||
|
||||
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"
|
||||
|
||||
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"
|
||||
|
||||
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"
|
||||
|
||||
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 style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
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 common = require "core.common"
|
||||
local keymap = require "core.keymap"
|
||||
|
@ -9,6 +9,7 @@ local View = require "core.view"
|
|||
|
||||
local ResultsView = View:extend()
|
||||
|
||||
ResultsView.context = "session"
|
||||
|
||||
function ResultsView:new(text, fn)
|
||||
ResultsView.super.new(self)
|
||||
|
@ -170,7 +171,7 @@ function ResultsView:draw()
|
|||
local ox, oy = self:get_content_offset()
|
||||
local x, y = ox + style.padding.x, oy + style.padding.y
|
||||
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
|
||||
if self.searching 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 command = require "core.command"
|
||||
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 config = require "core.config"
|
||||
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 common = require "core.common"
|
||||
local command = require "core.command"
|
||||
|
@ -8,8 +8,12 @@ local style = require "core.style"
|
|||
local RootView = require "core.rootview"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
config.scale_mode = "code"
|
||||
config.scale_use_mousewheel = true
|
||||
config.plugins.scale = {
|
||||
mode = "code",
|
||||
use_mousewheel = true
|
||||
}
|
||||
|
||||
local MINIMUM_SCALE = 0.25;
|
||||
|
||||
local scale_level = 0
|
||||
local scale_steps = 0.05
|
||||
|
@ -35,7 +39,7 @@ local function set_scale(scale)
|
|||
-- we set scale_level in case this was called by user
|
||||
scale_level = (scale - default_scale) / scale_steps
|
||||
|
||||
if config.scale_mode == "ui" then
|
||||
if config.plugins.scale.mode == "ui" then
|
||||
SCALE = scale
|
||||
|
||||
style.padding.x = style.padding.x * s
|
||||
|
@ -68,7 +72,7 @@ end
|
|||
local on_mouse_wheel = RootView.on_mouse_wheel
|
||||
|
||||
function RootView:on_mouse_wheel(d, ...)
|
||||
if keymap.modkeys["ctrl"] and config.scale_use_mousewheel then
|
||||
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
|
||||
|
@ -77,18 +81,18 @@ function RootView:on_mouse_wheel(d, ...)
|
|||
end
|
||||
|
||||
local function res_scale()
|
||||
scale_level = 0
|
||||
set_scale(default_scale)
|
||||
scale_level = 0
|
||||
set_scale(default_scale)
|
||||
end
|
||||
|
||||
local function inc_scale()
|
||||
scale_level = scale_level + 1
|
||||
set_scale(default_scale + scale_level * scale_steps)
|
||||
scale_level = scale_level + 1
|
||||
set_scale(default_scale + scale_level * scale_steps)
|
||||
end
|
||||
|
||||
local function dec_scale()
|
||||
scale_level = scale_level - 1
|
||||
set_scale(default_scale + scale_level * scale_steps)
|
||||
local function dec_scale()
|
||||
scale_level = scale_level - 1
|
||||
set_scale(math.max(default_scale + scale_level * scale_steps), MINIMUM_SCALE)
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
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 common = require "core.common"
|
||||
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 common = require "core.common"
|
||||
local command = require "core.command"
|
||||
|
@ -6,9 +6,12 @@ local config = require "core.config"
|
|||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
local ContextMenu = require "core.contextmenu"
|
||||
local RootView = require "core.rootview"
|
||||
|
||||
|
||||
local default_treeview_size = 200 * SCALE
|
||||
local tooltip_offset = style.font:get_height("A")
|
||||
local tooltip_offset = style.font:get_height()
|
||||
local tooltip_border = 1
|
||||
local tooltip_delay = 0.5
|
||||
local tooltip_alpha = 255
|
||||
|
@ -168,13 +171,13 @@ end
|
|||
function TreeView:on_mouse_moved(px, py, ...)
|
||||
TreeView.super.on_mouse_moved(self, px, py, ...)
|
||||
if self.dragging_scrollbar then return end
|
||||
|
||||
|
||||
local item_changed, tooltip_changed
|
||||
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
|
||||
item_changed = true
|
||||
self.hovered_item = item
|
||||
|
||||
|
||||
x,y,w,h = self:get_text_bounding_box(item, x,y,w,h)
|
||||
if px > x and py > y and px <= x + w and py <= y + h then
|
||||
tooltip_changed = true
|
||||
|
@ -204,7 +207,7 @@ end
|
|||
|
||||
function TreeView:on_mouse_pressed(button, x, y, clicks)
|
||||
local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||
if caught then
|
||||
if caught or button ~= "left" then
|
||||
return
|
||||
end
|
||||
local hovered_item = self.hovered_item
|
||||
|
@ -239,7 +242,7 @@ function TreeView:update()
|
|||
else
|
||||
self:move_towards(self.size, "x", dest)
|
||||
end
|
||||
|
||||
|
||||
local duration = system.get_time() - self.tooltip.begin
|
||||
if self.hovered_item and self.tooltip.x and duration > tooltip_delay then
|
||||
self:move_towards(self.tooltip, "alpha", tooltip_alpha, tooltip_alpha_rate)
|
||||
|
@ -336,9 +339,10 @@ local treeview_node = node:split("left", view, {x = true}, true)
|
|||
-- plugin to be independent of each other. In addition it is not the
|
||||
-- plugin module that plug itself in the active node but it is plugged here
|
||||
-- in the treeview node.
|
||||
local toolbar_view = nil
|
||||
local toolbar_plugin, ToolbarView = core.try(require, "plugins.toolbarview")
|
||||
if config.toolbarview ~= false and toolbar_plugin then
|
||||
local toolbar_view = ToolbarView()
|
||||
if config.plugins.toolbarview ~= false and toolbar_plugin then
|
||||
toolbar_view = ToolbarView()
|
||||
treeview_node:split("down", toolbar_view, {y = true})
|
||||
local min_toolbar_width = toolbar_view:get_min_width()
|
||||
view:set_target_size("x", math.max(default_treeview_size, min_toolbar_width))
|
||||
|
@ -349,12 +353,182 @@ if config.toolbarview ~= false and toolbar_plugin then
|
|||
})
|
||||
end
|
||||
|
||||
-- Add a context menu to the treeview
|
||||
local menu = ContextMenu()
|
||||
|
||||
-- register commands and keymap
|
||||
local on_view_mouse_pressed = RootView.on_view_mouse_pressed
|
||||
local on_mouse_moved = RootView.on_mouse_moved
|
||||
local root_view_update = RootView.update
|
||||
local root_view_draw = RootView.draw
|
||||
|
||||
function RootView:on_mouse_moved(...)
|
||||
if menu:on_mouse_moved(...) then return end
|
||||
on_mouse_moved(self, ...)
|
||||
end
|
||||
|
||||
function RootView.on_view_mouse_pressed(button, x, y, clicks)
|
||||
-- We give the priority to the menu to process mouse pressed events.
|
||||
if button == "right" then
|
||||
view.tooltip.alpha = 0
|
||||
view.tooltip.x, view.tooltip.y = nil, nil
|
||||
end
|
||||
local handled = menu:on_mouse_pressed(button, x, y, clicks)
|
||||
return handled or on_view_mouse_pressed(button, x, y, clicks)
|
||||
end
|
||||
|
||||
function RootView:update(...)
|
||||
root_view_update(self, ...)
|
||||
menu:update()
|
||||
end
|
||||
|
||||
function RootView:draw(...)
|
||||
root_view_draw(self, ...)
|
||||
menu:draw()
|
||||
end
|
||||
|
||||
local function is_project_folder(path)
|
||||
return common.basename(core.project_dir) == path
|
||||
end
|
||||
|
||||
menu:register(function() return view.hovered_item end, {
|
||||
{ text = "Open in System", command = "treeview:open-in-system" },
|
||||
ContextMenu.DIVIDER
|
||||
})
|
||||
|
||||
menu:register(
|
||||
function()
|
||||
return view.hovered_item
|
||||
and not is_project_folder(view.hovered_item.filename)
|
||||
end,
|
||||
{
|
||||
{ text = "Rename", command = "treeview:rename" },
|
||||
{ text = "Delete", command = "treeview:delete" },
|
||||
}
|
||||
)
|
||||
|
||||
menu:register(
|
||||
function()
|
||||
return view.hovered_item and view.hovered_item.type == "dir"
|
||||
end,
|
||||
{
|
||||
{ text = "New File", command = "treeview:new-file" },
|
||||
{ text = "New Folder", command = "treeview:new-folder" },
|
||||
}
|
||||
)
|
||||
|
||||
-- Register the TreeView commands and keymap
|
||||
command.add(nil, {
|
||||
["treeview:toggle"] = function()
|
||||
view.visible = not view.visible
|
||||
end})
|
||||
|
||||
|
||||
command.add(function() return view.hovered_item ~= nil end, {
|
||||
["treeview:rename"] = function()
|
||||
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:enter("Rename", function(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
|
||||
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
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["treeview:new-file"] = function()
|
||||
local dir_name = view.hovered_item.filename
|
||||
if not is_project_folder(dir_name) then
|
||||
core.command_view:set_text(dir_name .. "/")
|
||||
end
|
||||
core.command_view:enter("Filename", function(filename)
|
||||
local doc_filename = core.project_dir .. PATHSEP .. filename
|
||||
local file = io.open(doc_filename, "a+")
|
||||
file:write("")
|
||||
file:close()
|
||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||
core.log("Created %s", doc_filename)
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["treeview:new-folder"] = function()
|
||||
local dir_name = view.hovered_item.filename
|
||||
if not is_project_folder(dir_name) then
|
||||
core.command_view:set_text(dir_name .. "/")
|
||||
end
|
||||
core.command_view:enter("Folder Name", function(filename)
|
||||
local dir_path = core.project_dir .. PATHSEP .. filename
|
||||
common.mkdirp(dir_path)
|
||||
core.log("Created %s", dir_path)
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["treeview:delete"] = function()
|
||||
local filename = view.hovered_item.abs_filename
|
||||
local relfilename = view.hovered_item.filename
|
||||
local file_info = system.get_file_info(filename)
|
||||
local file_type = file_info.type == "dir" and "Directory" or "File"
|
||||
-- Ask before deleting
|
||||
local opt = {
|
||||
{ font = style.font, text = "Yes", default_yes = true },
|
||||
{ font = style.font, text = "No" , default_no = true }
|
||||
}
|
||||
core.nag_view:show(
|
||||
string.format("Delete %s", file_type),
|
||||
string.format(
|
||||
"Are you sure you want to delete the %s?\n%s: %s",
|
||||
file_type:lower(), file_type, relfilename
|
||||
),
|
||||
opt,
|
||||
function(item)
|
||||
if item.text == "Yes" then
|
||||
if file_info.type == "dir" then
|
||||
local deleted, error, path = common.rm(filename, true)
|
||||
if not deleted then
|
||||
core.error("Error: %s - \"%s\" ", error, path)
|
||||
return
|
||||
end
|
||||
else
|
||||
local removed, error = os.remove(filename)
|
||||
if not removed then
|
||||
core.error("Error: %s - \"%s\"", error, filename)
|
||||
return
|
||||
end
|
||||
end
|
||||
core.log("Deleted \"%s\"", filename)
|
||||
end
|
||||
end
|
||||
)
|
||||
end,
|
||||
|
||||
["treeview:open-in-system"] = function()
|
||||
local hovered_item = view.hovered_item
|
||||
|
||||
if PLATFORM == "Windows" then
|
||||
system.exec(string.format("start \"\" %q", hovered_item.abs_filename))
|
||||
elseif string.find(PLATFORM, "Mac") then
|
||||
system.exec(string.format("open %q", hovered_item.abs_filename))
|
||||
elseif PLATFORM == "Linux" then
|
||||
system.exec(string.format("xdg-open %q", hovered_item.abs_filename))
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add { ["ctrl+\\"] = "treeview:toggle" }
|
||||
|
||||
-- Return the treeview with toolbar and contextmenu to allow
|
||||
-- user or plugin modifications
|
||||
view.toolbar = toolbar_view
|
||||
view.contextmenu = menu
|
||||
|
||||
return view
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
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 common = require "core.common"
|
||||
local DocView = require "core.docview"
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# Interface Files
|
||||
|
||||
This directory holds the documentation for the Lua C API that
|
||||
is hidden in the C source files of Lite. The idea of these files
|
||||
is to serve you as a quick reference about the functionality
|
||||
that is not written in Lua it self. Please note that they
|
||||
don't have any real code, just metadata or annotations.
|
||||
|
||||
Also, these interfaces are using
|
||||
[EmmyLua annotation syntax](https://emmylua.github.io/annotation.html)
|
||||
which is supported by LSP servers like the
|
||||
[Sumneko Lua LSP](https://github.com/sumneko/lua-language-server).
|
||||
This means that you can get nice code autocompletion and descriptions
|
||||
of Lite core libraries and symbols when developing plugins or adding
|
||||
any options to your **User Module File** (init.lua).
|
||||
|
||||
## The Base Core
|
||||
|
||||
Most of the code that is written in Lua for Lite is powered by the exposed
|
||||
C API in the four namespaces that follow:
|
||||
|
||||
* [system](api/system.lua)
|
||||
* [renderer](api/renderer.lua)
|
||||
* [regex](api/regex.lua)
|
||||
* [process](api/process.lua)
|
||||
|
||||
Finally, all global variables are documented in the file named
|
||||
[globals.lua](api/globals.lua).
|
|
@ -0,0 +1,21 @@
|
|||
---@meta
|
||||
|
||||
---The command line arguments given to lite.
|
||||
---@type table<integer, string>
|
||||
ARGS = {}
|
||||
|
||||
---The current operating system.
|
||||
---@type string | "'Windows'" | "'Mac OS X'" | "'Linux'" | "'iOS'" | "'Android'"
|
||||
PLATFORM = "Operating System"
|
||||
|
||||
---The current text or ui scale.
|
||||
---@type number
|
||||
SCALE = 1.0
|
||||
|
||||
---Full path of lite executable.
|
||||
---@type string
|
||||
EXEFILE = "/path/to/lite"
|
||||
|
||||
---Path to the users home directory.
|
||||
---@type string
|
||||
HOME = "/path/to/user/dir"
|
|
@ -0,0 +1,232 @@
|
|||
---@meta
|
||||
|
||||
---
|
||||
---Functionality that allows you to launch subprocesses and read
|
||||
---or write to them in a non-blocking fashion.
|
||||
---@class process
|
||||
process = {}
|
||||
|
||||
---Error triggered when the stdout, stderr or stdin fails while reading
|
||||
---or writing, its value is platform dependent, so the value declared on this
|
||||
---interface does not represents the real one.
|
||||
---@type integer
|
||||
process.ERROR_PIPE = -1
|
||||
|
||||
---Error triggered when a read or write action is blocking,
|
||||
---its value is platform dependent, so the value declared on this
|
||||
---interface does not represents the real one.
|
||||
---@type integer
|
||||
process.ERROR_WOULDBLOCK = -2
|
||||
|
||||
---Error triggered when a process takes more time than that specified
|
||||
---by the deadline parameter given on process:start(),
|
||||
---its value is platform dependent, so the value declared on this
|
||||
---interface does not represents the real one.
|
||||
---@type integer
|
||||
process.ERROR_TIMEDOUT = -3
|
||||
|
||||
---Error triggered when trying to terminate or kill a non running process,
|
||||
---its value is platform dependent, so the value declared on this
|
||||
---interface does not represents the real one.
|
||||
---@type integer
|
||||
process.ERROR_INVAL = -4
|
||||
|
||||
---Error triggered when no memory is available to allocate the process,
|
||||
---its value is platform dependent, so the value declared on this
|
||||
---interface does not represents the real one.
|
||||
---@type integer
|
||||
process.ERROR_NOMEM = -5
|
||||
|
||||
---Used for the process:close_stream() method to close stdin.
|
||||
---@type integer
|
||||
process.STREAM_STDIN = 0
|
||||
|
||||
---Used for the process:close_stream() method to close stdout.
|
||||
---@type integer
|
||||
process.STREAM_STDOUT = 1
|
||||
|
||||
---Used for the process:close_stream() method to close stderr.
|
||||
---@type integer
|
||||
process.STREAM_STDERR = 2
|
||||
|
||||
---Instruct process:wait() to wait until the process ends.
|
||||
---@type integer
|
||||
process.WAIT_INFINITE = -1
|
||||
|
||||
---Instruct process:wait() to wait until the deadline given on process:start()
|
||||
---@type integer
|
||||
process.WAIT_DEADLINE = -2
|
||||
|
||||
---Used for the process.options stdin, stdout and stderr fields.
|
||||
---@type integer
|
||||
process.REDIRECT_DEFAULT = 0
|
||||
|
||||
---Used for the process.options stdin, stdout and stderr fields.
|
||||
---@type integer
|
||||
process.REDIRECT_PIPE = 1
|
||||
|
||||
---Used for the process.options stdin, stdout and stderr fields.
|
||||
---@type integer
|
||||
process.REDIRECT_PARENT = 2
|
||||
|
||||
---Used for the process.options stdin, stdout and stderr fields.
|
||||
---@type integer
|
||||
process.REDIRECT_DISCARD = 3
|
||||
|
||||
---Used for the process.options stdin, stdout and stderr fields.
|
||||
---@type integer
|
||||
process.REDIRECT_STDOUT = 4
|
||||
|
||||
---@alias process.errortype
|
||||
---|>'process.ERROR_PIPE'
|
||||
---| 'process.ERROR_WOULDBLOCK'
|
||||
---| 'process.ERROR_TIMEDOUT'
|
||||
---| 'process.ERROR_INVAL'
|
||||
---| 'process.ERROR_NOMEM'
|
||||
|
||||
---@alias process.streamtype
|
||||
---|>'process.STREAM_STDIN'
|
||||
---| 'process.STREAM_STDOUT'
|
||||
---| 'process.STREAM_STDERR'
|
||||
|
||||
---@alias process.waittype
|
||||
---|>'process.WAIT_INFINITE'
|
||||
---| 'process.WAIT_DEADLINE'
|
||||
|
||||
---@alias process.redirecttype
|
||||
---|>'process.REDIRECT_DEFAULT'
|
||||
---| 'process.REDIRECT_PIPE'
|
||||
---| 'process.REDIRECT_PARENT'
|
||||
---| 'process.REDIRECT_DISCARD'
|
||||
---| 'process.REDIRECT_STDOUT'
|
||||
|
||||
---
|
||||
--- Options that can be passed to process.start()
|
||||
---@class process.options
|
||||
---@field public timeout number
|
||||
---@field public cwd string
|
||||
---@field public stdin process.redirecttype
|
||||
---@field public stdout process.redirecttype
|
||||
---@field public stderr process.redirecttype
|
||||
---@field public env table<string, string>
|
||||
process.options = {}
|
||||
|
||||
---
|
||||
---Create and start a new process
|
||||
---
|
||||
---@param command_and_params table First index is the command to execute
|
||||
---and subsequente elements are parameters for the command.
|
||||
---@param options process.options
|
||||
---
|
||||
---@return process | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:start(command_and_params, options) end
|
||||
|
||||
---
|
||||
---Translates an error code into a useful text message
|
||||
---
|
||||
---@param code integer
|
||||
---
|
||||
---@return string | nil
|
||||
function process.strerror(code) end
|
||||
|
||||
---
|
||||
---Get the process id.
|
||||
---
|
||||
---@return integer id Process id or 0 if not running.
|
||||
function process:pid() end
|
||||
|
||||
---
|
||||
---Read from the given stream type, if the process fails with a ERROR_PIPE it is
|
||||
---automatically destroyed returning nil along error message and code.
|
||||
---
|
||||
---@param stream process.streamtype
|
||||
---@param len? integer Amount of bytes to read, defaults to 2048.
|
||||
---
|
||||
---@return string | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:read(stream, len) end
|
||||
|
||||
---
|
||||
---Read from stdout, if the process fails with a ERROR_PIPE it is
|
||||
---automatically destroyed returning nil along error message and code.
|
||||
---
|
||||
---@param len? integer Amount of bytes to read, defaults to 2048.
|
||||
---
|
||||
---@return string | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:read_stdout(len) end
|
||||
|
||||
---
|
||||
---Read from stderr, if the process fails with a ERROR_PIPE it is
|
||||
---automatically destroyed returning nil along error message and code.
|
||||
---
|
||||
---@param len? integer Amount of bytes to read, defaults to 2048.
|
||||
---
|
||||
---@return string | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:read_stderr(len) end
|
||||
|
||||
---
|
||||
---Write to the stdin, if the process fails with a ERROR_PIPE it is
|
||||
---automatically destroyed returning nil along error message and code.
|
||||
---
|
||||
---@param data string
|
||||
---
|
||||
---@return integer | nil bytes The amount of bytes written or nil if error
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:write(data) end
|
||||
|
||||
---
|
||||
---Allows you to close a stream pipe that you will not be using.
|
||||
---
|
||||
---@param stream process.streamtype
|
||||
---
|
||||
---@return integer | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:close_stream(stream) end
|
||||
|
||||
---
|
||||
---Wait the specified amount of time for the process to exit.
|
||||
---
|
||||
---@param timeout integer | process.waittype Time to wait in milliseconds,
|
||||
---if 0, the function will only check if process is running without waiting.
|
||||
---
|
||||
---@return integer | nil exit_status The process exit status or nil on error
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:wait(timeout) end
|
||||
|
||||
---
|
||||
---Sends SIGTERM to the process
|
||||
---
|
||||
---@return boolean | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:terminate() end
|
||||
|
||||
---
|
||||
---Sends SIGKILL to the process
|
||||
---
|
||||
---@return boolean | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process:kill() end
|
||||
|
||||
---
|
||||
---Get the exit code of the process or nil if still running.
|
||||
---
|
||||
---@return number | nil
|
||||
function process:returncode() end
|
||||
|
||||
---
|
||||
---Check if the process is running
|
||||
---
|
||||
---@return boolean
|
||||
function process:running() end
|
|
@ -0,0 +1,57 @@
|
|||
---@meta
|
||||
|
||||
---
|
||||
---Provides the base functionality for regular expressions matching.
|
||||
---@class regex
|
||||
regex = {}
|
||||
|
||||
---Instruct regex:cmatch() to match only at the first position.
|
||||
---@type integer
|
||||
regex.ANCHORED = 0x80000000
|
||||
|
||||
---Tell regex:cmatch() that the pattern can match only at end of subject.
|
||||
---@type integer
|
||||
regex.ENDANCHORED = 0x20000000
|
||||
|
||||
---Tell regex:cmatch() that subject string is not the beginning of a line.
|
||||
---@type integer
|
||||
regex.NOTBOL = 0x00000001
|
||||
|
||||
---Tell regex:cmatch() that subject string is not the end of a line.
|
||||
---@type integer
|
||||
regex.NOTEOL = 0x00000002
|
||||
|
||||
---Tell regex:cmatch() that an empty string is not a valid match.
|
||||
---@type integer
|
||||
regex.NOTEMPTY = 0x00000004
|
||||
|
||||
---Tell regex:cmatch() that an empty string at the start of the
|
||||
---subject is not a valid match.
|
||||
---@type integer
|
||||
regex.NOTEMPTY_ATSTART = 0x00000008
|
||||
|
||||
---@alias regex.modifiers
|
||||
---|>'"i"' # Case insesitive matching
|
||||
---| '"m"' # Multiline matching
|
||||
---| '"s"' # Match all characters with dot (.) metacharacter even new lines
|
||||
|
||||
---
|
||||
---Compiles a regular expression pattern that can be used to search in strings.
|
||||
---
|
||||
---@param pattern string
|
||||
---@param options? regex.modifiers A string of one or more pattern modifiers.
|
||||
---
|
||||
---@return regex|string regex Ready to use regular expression object or error
|
||||
---message if compiling the pattern failed.
|
||||
function regex.compile(pattern, options) end
|
||||
|
||||
---
|
||||
---Search a string for valid matches and returns a list of matching offsets.
|
||||
---
|
||||
---@param subject string The string to search for valid matches.
|
||||
---@param offset? integer The position on the subject to start searching.
|
||||
---@param options? integer A bit field of matching options, eg:
|
||||
---regex.NOTBOL | regex.NOTEMPTY
|
||||
---
|
||||
---@return table<integer, integer> list List of offsets where a match was found.
|
||||
function regex:cmatch(subject, offset, options) end
|
|
@ -0,0 +1,181 @@
|
|||
---@meta
|
||||
|
||||
---
|
||||
---Core functionality to render or draw elements into the screen.
|
||||
---@class renderer
|
||||
renderer = {}
|
||||
|
||||
---
|
||||
---Represents a color used by the rendering functions.
|
||||
---@class renderer.color
|
||||
---@field public r number Red
|
||||
---@field public g number Green
|
||||
---@field public b number Blue
|
||||
---@field public a number Alpha
|
||||
renderer.color = {}
|
||||
|
||||
---
|
||||
---Represent options that affect a font's rendering.
|
||||
---@class renderer.fontoptions
|
||||
---@field public antialiasing "'grayscale'" | "'subpixel'"
|
||||
---@field public hinting "'slight'" | "'none'" | '"full"'
|
||||
renderer.fontoptions = {}
|
||||
|
||||
---
|
||||
---@class renderer.font
|
||||
renderer.font = {}
|
||||
|
||||
---
|
||||
---Create a new font object.
|
||||
---
|
||||
---@param path string
|
||||
---@param size number
|
||||
---@param options renderer.fontoptions
|
||||
---
|
||||
---@return renderer.font
|
||||
function renderer.font.load(path, size, options) end
|
||||
|
||||
---
|
||||
---Clones a font object into a new one.
|
||||
---
|
||||
---@param size? number Optional new size for cloned font.
|
||||
---
|
||||
---@return renderer.font
|
||||
function renderer.font:copy(size) end
|
||||
|
||||
---
|
||||
---Set the amount of characters that represent a tab.
|
||||
---
|
||||
---@param chars integer Also known as tab width.
|
||||
function renderer.font:set_tab_size(chars) end
|
||||
|
||||
---
|
||||
---Get the width in pixels of the given text when
|
||||
---rendered with this font.
|
||||
---
|
||||
---@param text string
|
||||
---
|
||||
---@return number
|
||||
function renderer.font:get_width(text) end
|
||||
|
||||
---
|
||||
---Get the width in subpixels of the given text when
|
||||
---rendered with this font.
|
||||
---
|
||||
---@param text string
|
||||
---
|
||||
---@return number
|
||||
function renderer.font:get_width_subpixel(text) end
|
||||
|
||||
---
|
||||
---Get the height in pixels that occupies a single character
|
||||
---when rendered with this font.
|
||||
---
|
||||
---@return number
|
||||
function renderer.font:get_height() end
|
||||
|
||||
---
|
||||
---Gets the font subpixel scale.
|
||||
---
|
||||
---@return number
|
||||
function renderer.font:subpixel_scale() end
|
||||
|
||||
---
|
||||
---Get the current size of the font.
|
||||
---
|
||||
---@return number
|
||||
function renderer.font:get_size() end
|
||||
|
||||
---
|
||||
---Set a new size for the font.
|
||||
---
|
||||
---@param size number
|
||||
function renderer.font:set_size(size) end
|
||||
|
||||
---
|
||||
---Assistive functionality to replace characters in a
|
||||
---rendered text with other characters.
|
||||
---@class renderer.replacements
|
||||
renderer.replacements = {}
|
||||
|
||||
---
|
||||
---Create a new character replacements object.
|
||||
---
|
||||
---@return renderer.replacements
|
||||
function renderer.replacements.new() end
|
||||
|
||||
---
|
||||
---Add to internal map a character to character replacement.
|
||||
---
|
||||
---@param original_char string Should be a single character like '\t'
|
||||
---@param replacement_char string Should be a single character like '»'
|
||||
function renderer.replacements:add(original_char, replacement_char) end
|
||||
|
||||
---
|
||||
---Toggles drawing debugging rectangles on the currently rendered sections
|
||||
---of the window to help troubleshoot the renderer.
|
||||
---
|
||||
---@param enable boolean
|
||||
function renderer.show_debug(enable) end
|
||||
|
||||
---
|
||||
---Get the size of the screen area been rendered.
|
||||
---
|
||||
---@return number width
|
||||
---@return number height
|
||||
function renderer.get_size() end
|
||||
|
||||
---
|
||||
---Tell the rendering system that we want to build a new frame to render.
|
||||
function renderer.begin_frame() end
|
||||
|
||||
---
|
||||
---Tell the rendering system that we finished building the frame.
|
||||
function renderer.end_frame() end
|
||||
|
||||
---
|
||||
---Set the region of the screen where draw operations will take effect.
|
||||
---
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param width number
|
||||
---@param height number
|
||||
function renderer.set_clip_rect(x, y, width, height) end
|
||||
|
||||
---
|
||||
---Draw a rectangle.
|
||||
---
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param width number
|
||||
---@param height number
|
||||
---@param color renderer.color
|
||||
function renderer.draw_rect(x, y, width, height, color) end
|
||||
|
||||
---
|
||||
---Draw text.
|
||||
---
|
||||
---@param font renderer.font
|
||||
---@param text string
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param color renderer.color
|
||||
---@param replace renderer.replacements
|
||||
---@param color_replace renderer.color
|
||||
---
|
||||
---@return number x_subpixel
|
||||
function renderer.draw_text(font, text, x, y, color, replace, color_replace) end
|
||||
|
||||
---
|
||||
---Draw text at subpixel level.
|
||||
---
|
||||
---@param font renderer.font
|
||||
---@param text string
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param color renderer.color
|
||||
---@param replace renderer.replacements
|
||||
---@param color_replace renderer.color
|
||||
---
|
||||
---@return number x_subpixel
|
||||
function renderer.draw_text_subpixel(font, text, x, y, color, replace, color_replace) end
|
|
@ -0,0 +1,234 @@
|
|||
---@meta
|
||||
|
||||
---
|
||||
---Utilites for managing current window, files and more.
|
||||
---@class system
|
||||
system = {}
|
||||
|
||||
---@alias system.fileinfotype
|
||||
---|>'"file"' # It is a file.
|
||||
---| '"dir"' # It is a directory.
|
||||
|
||||
---
|
||||
---@class system.fileinfo
|
||||
---@field public modified number A timestamp in seconds.
|
||||
---@field public size number Size in bytes.
|
||||
---@field public type system.fileinfotype Type of file
|
||||
system.fileinfo = {}
|
||||
|
||||
---
|
||||
---Core function used to retrieve the current event been triggered by SDL.
|
||||
---
|
||||
---The following is a list of event types emitted by this function and
|
||||
---the arguments for each of them if applicable.
|
||||
---
|
||||
---Window events:
|
||||
--- * "quit"
|
||||
--- * "resized" -> width, height
|
||||
--- * "exposed"
|
||||
--- * "minimized"
|
||||
--- * "maximized"
|
||||
--- * "restored"
|
||||
--- * "focuslost"
|
||||
---
|
||||
---File events:
|
||||
--- * "filedropped" -> filename, x, y
|
||||
---
|
||||
---Keyboard events:
|
||||
--- * "keypressed" -> key_name
|
||||
--- * "keyreleased" -> key_name
|
||||
--- * "textinput" -> text
|
||||
---
|
||||
---Mouse events:
|
||||
--- * "mousepressed" -> button_name, x, y, amount_of_clicks
|
||||
--- * "mousereleased" -> button_name, x, y
|
||||
--- * "mousemoved" -> x, y, relative_x, relative_y
|
||||
--- * "mousewheel" -> y
|
||||
---
|
||||
---@return string type
|
||||
---@return any? arg1
|
||||
---@return any? arg2
|
||||
---@return any? arg3
|
||||
---@return any? arg4
|
||||
function system.poll_event() end
|
||||
|
||||
---
|
||||
---Wait until an event is triggered.
|
||||
---
|
||||
---@param timeout number Amount of seconds, also supports fractions
|
||||
---of a second, eg: 0.01
|
||||
---
|
||||
---@return boolean status True on success or false if there was an error.
|
||||
function system.wait_event(timeout) end
|
||||
|
||||
---
|
||||
---Change the cursor type displayed on screen.
|
||||
---
|
||||
---@param type string | "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'"
|
||||
function system.set_cursor(type) end
|
||||
|
||||
---
|
||||
---Change the window title.
|
||||
---
|
||||
---@param title string
|
||||
function system.set_window_title(title) end
|
||||
|
||||
---@alias system.windowmode
|
||||
---|>'"normal"'
|
||||
---| '"minimized"'
|
||||
---| '"maximized"'
|
||||
---| '"fullscreen"'
|
||||
|
||||
---
|
||||
---Change the window mode.
|
||||
---
|
||||
---@param mode system.windowmode
|
||||
function system.set_window_mode(mode) end
|
||||
|
||||
---
|
||||
---Retrieve the current window mode.
|
||||
---
|
||||
---@return system.windowmode mode
|
||||
function system.get_window_mode() end
|
||||
|
||||
---
|
||||
---Toggle between bordered and borderless.
|
||||
---
|
||||
---@param bordered boolean
|
||||
function system.set_window_bordered(bordered) end
|
||||
|
||||
---
|
||||
---When then window is run borderless (without system decorations), this
|
||||
---function allows to set the size of the different regions that allow
|
||||
---for custom window management.
|
||||
---
|
||||
---@param title_height number
|
||||
---@param controls_width number This is for minimize, maximize, close, etc...
|
||||
---@param resize_border number The amount of pixels reserved for resizing
|
||||
function system.set_window_hit_test(title_height, controls_width, resize_border) end
|
||||
|
||||
---
|
||||
---Get the size and coordinates of the window.
|
||||
---
|
||||
---@return number width
|
||||
---@return number height
|
||||
---@return number x
|
||||
---@return number y
|
||||
function system.get_window_size() end
|
||||
|
||||
---
|
||||
---Sets the size and coordinates of the window.
|
||||
---
|
||||
---@param width number
|
||||
---@param height number
|
||||
---@param x number
|
||||
---@param y number
|
||||
function system.set_window_size(width, height, x, y) end
|
||||
|
||||
---
|
||||
---Check if the window currently has focus.
|
||||
---
|
||||
---@return boolean
|
||||
function system.window_has_focus() end
|
||||
|
||||
---
|
||||
---Opens a message box to display an error message.
|
||||
---
|
||||
---@param title string
|
||||
---@param message string
|
||||
function system.show_fatal_error(title, message) end
|
||||
|
||||
---
|
||||
---Change the current directory path which affects relative file operations.
|
||||
---This function raises an error if the path doesn't exists.
|
||||
---
|
||||
---@param path string
|
||||
function system.chdir(path) end
|
||||
|
||||
---
|
||||
---Create a new directory, note that this function doesn't recursively
|
||||
---creates the directories on the given path.
|
||||
---
|
||||
---@param directory_path string
|
||||
---
|
||||
---@return boolean created True on success or false on failure.
|
||||
function system.mkdir(directory_path) end
|
||||
|
||||
---
|
||||
---Gets a list of files and directories for a given path.
|
||||
---
|
||||
---@param path string
|
||||
---
|
||||
---@return table|nil list List of directories or nil if empty or error.
|
||||
---@return string? message Error message in case of error.
|
||||
function system.list_dir(path) end
|
||||
|
||||
---
|
||||
---Converts a relative path from current directory to the absolute one.
|
||||
---
|
||||
---@param path string
|
||||
---
|
||||
---@return string
|
||||
function system.absolute_path(path) end
|
||||
|
||||
---
|
||||
---Get details about a given file or path.
|
||||
---
|
||||
---@param path string Can be a file or a directory path
|
||||
---
|
||||
---@return system.fileinfo|nil info Path details or nil if empty or error.
|
||||
---@return string? message Error message in case of error.
|
||||
function system.get_file_info(path) end
|
||||
|
||||
---
|
||||
---Retrieve the text currently stored on the clipboard.
|
||||
---
|
||||
---@return string
|
||||
function system.get_clipboard() end
|
||||
|
||||
---
|
||||
---Set the content of the clipboard.
|
||||
---
|
||||
---@param text string
|
||||
function system.set_clipboard(text) end
|
||||
|
||||
---
|
||||
---Get amount of iterations since the application was launched
|
||||
---also known as SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency()
|
||||
---
|
||||
---@return number
|
||||
function system.get_time() end
|
||||
|
||||
---
|
||||
---Sleep for the given amount of seconds.
|
||||
---
|
||||
---@param seconds number Also supports fractions of a second, eg: 0.01
|
||||
function system.sleep(seconds) end
|
||||
|
||||
---
|
||||
---Similar to os.execute() but does not return the exit status of the
|
||||
---executed command and executes the process in a non blocking way by
|
||||
---forking it to the background.
|
||||
---
|
||||
---@param command string The command to execute.
|
||||
function system.exec(command) end
|
||||
|
||||
---
|
||||
---Generates a matching score depending on how well the value of the
|
||||
---given needle compares to that of the value in the haystack.
|
||||
---
|
||||
---@param haystack string
|
||||
---@param needle string
|
||||
---@param file boolean Reverse the algorithm to prioritize the end
|
||||
---of the haystack, eg: with a haystack "/my/path/to/file" and a needle
|
||||
---"file", will get better score than with this option not set to true.
|
||||
---
|
||||
---@return integer score
|
||||
function system.fuzzy_match(haystack, needle, file) end
|
||||
|
||||
---
|
||||
---Change the opacity (also known as transparency) of the window.
|
||||
---
|
||||
---@param opacity number A value from 0.0 to 1.0, the lower the value
|
||||
---the less visible the window will be.
|
||||
function system.set_window_opacity(opacity) end
|
|
@ -1,27 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
cxxcompiler="g++"
|
||||
cxxflags="-Wall -O3 -g -std=c++03 -fno-exceptions -fno-rtti -Isrc -Ilib/font_renderer"
|
||||
cxxflags+=" -DFONT_RENDERER_HEIGHT_HACK"
|
||||
for package in libagg freetype2; do
|
||||
cxxflags+=" $(pkg-config --cflags $package)"
|
||||
done
|
||||
|
||||
echo "compiling font renderer library..."
|
||||
|
||||
for f in `find lib -name "*.cpp"`; do
|
||||
$cxxcompiler -c $cxxflags $f -o "${f//\//_}.o"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
got_error=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $got_error ]]; then
|
||||
rm -f *.o
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ar -rcs libfontrenderer.a *.o
|
||||
|
||||
rm *.o
|
||||
echo "font renderer library created"
|
|
@ -245,7 +245,7 @@ FR_Bitmap *FR_Bake_Font_Bitmap(FR_Renderer *font_renderer, int font_height,
|
|||
}
|
||||
|
||||
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;
|
||||
const int pixels_width = glyph_avg_width > 0 ? glyph_avg_width * 28 : 28;
|
||||
|
||||
// dry run simulating pixel position to estimate required image's height
|
||||
int x = x_start, y = 0, y_bottom = y;
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
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
|
||||
libagg_dep = dependency('libagg', fallback: ['libagg', 'libagg_dep'])
|
||||
|
||||
font_renderer_sources = [
|
||||
'agg_font_freetype.cpp',
|
||||
|
|
170
meson.build
170
meson.build
|
@ -1,68 +1,128 @@
|
|||
project('lite-xl', 'c', 'cpp', default_options : ['c_std=gnu11', 'cpp_std=c++03'])
|
||||
project('lite-xl',
|
||||
['c', 'cpp'],
|
||||
version : '2.0.2',
|
||||
license : 'MIT',
|
||||
meson_version : '>= 0.54',
|
||||
default_options : ['c_std=gnu11', 'cpp_std=c++03']
|
||||
)
|
||||
|
||||
#===============================================================================
|
||||
# Configuration
|
||||
#===============================================================================
|
||||
conf_data = configuration_data()
|
||||
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'
|
||||
add_languages('objc')
|
||||
endif
|
||||
|
||||
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')
|
||||
threads_dep = dependency('threads')
|
||||
|
||||
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, threads_dep, 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 = []
|
||||
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')
|
||||
endif
|
||||
|
||||
# On macos we need to use the SDL renderer to support retina displays
|
||||
if get_option('renderer') or host_machine.system() == 'darwin'
|
||||
lite_cargs += '-DLITE_USE_SDL_RENDERER'
|
||||
endif
|
||||
#===============================================================================
|
||||
# Linker Settings
|
||||
#===============================================================================
|
||||
lite_link_args = []
|
||||
if cc.get_id() == 'gcc' and get_option('buildtype') == 'release'
|
||||
lite_link_args += ['-static-libgcc', '-static-libstdc++']
|
||||
endif
|
||||
|
||||
subdir('lib/font_renderer')
|
||||
subdir('src')
|
||||
if host_machine.system() == 'darwin'
|
||||
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)
|
||||
threads_dep = dependency('threads')
|
||||
lua_dep = dependency('lua5.2', fallback: ['lua', 'lua_dep'],
|
||||
default_options: ['shared=false', 'use_readline=false', 'app=false']
|
||||
)
|
||||
pcre2_dep = dependency('libpcre2-8')
|
||||
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, threads_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('cpp').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('lib/font_renderer')
|
||||
subdir('src')
|
||||
subdir('scripts')
|
||||
endif
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
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('renderer', type : 'boolean', value : false, description: 'Use SDL renderer')
|
||||
|
||||
|
|
|
@ -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
|
||||
Icon=lite-xl
|
||||
Terminal=false
|
||||
StartupNotify=false
|
||||
Categories=Utility;TextEditor;Development;
|
||||
StartupWMClass=lite-xl
|
||||
Categories=Development;IDE;
|
||||
MimeType=text/plain;
|
|
@ -2,25 +2,30 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>lite-xl</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>lite-xl</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>icon</string>
|
||||
<string>icon.icns</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>lite-xl</string>
|
||||
<string>Lite XL</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>MinimumOSVersion</key><string>10.13</string>
|
||||
<key>NSDocumentsFolderUsageDescription</key><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>LSMinimumSystemVersion</key>
|
||||
<string>10.11</string>
|
||||
<key>NSDocumentsFolderUsageDescription</key>
|
||||
<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>
|
||||
<string>1.16.10</string>
|
||||
<string>@PROJECT_VERSION@</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>© 2019-2021 Francesco Abbate</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
|
@ -0,0 +1,28 @@
|
|||
# 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.
|
||||
|
||||
[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
|
|
@ -0,0 +1,88 @@
|
|||
#define MyAppName "Lite XL"
|
||||
#define MyAppVersion "@PROJECT_VERSION@"
|
||||
#define MyAppPublisher "Lite XL Team"
|
||||
#define MyAppURL "https://lite-xl.github.io"
|
||||
#define MyAppExeName "lite-xl.exe"
|
||||
#define BuildDir "@PROJECT_BUILD_DIR@"
|
||||
#define SourceDir "@PROJECT_SOURCE_DIR@"
|
||||
|
||||
; Use /dArch option to create a setup for a different architecture, e.g.:
|
||||
; iscc /dArch=x86 innosetup.iss
|
||||
#ifndef Arch
|
||||
#define Arch "x64"
|
||||
#endif
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application.
|
||||
; Do not use the same AppId value in installers for other applications.
|
||||
; To generate a new GUID, click Tools | Generate GUID inside the InnoSetup IDE.
|
||||
AppId={{06761240-D97C-43DE-B9ED-C15F765A2D65}
|
||||
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
;AppVerName={#MyAppName} {#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
|
||||
#if Arch=="x64"
|
||||
ArchitecturesAllowed=x64
|
||||
ArchitecturesInstallIn64BitMode=x64
|
||||
#define ArchInternal "x86_64"
|
||||
#else
|
||||
#define ArchInternal "i686"
|
||||
#endif
|
||||
|
||||
AllowNoIcons=yes
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
DefaultDirName={autopf}/{#MyAppName}
|
||||
DefaultGroupName={#MyAppPublisher}
|
||||
UninstallFilesDir={app}
|
||||
|
||||
; Uncomment the following line to run in non administrative install mode
|
||||
; (install for current user only.)
|
||||
;PrivilegesRequired=lowest
|
||||
PrivilegesRequiredOverridesAllowed=dialog
|
||||
|
||||
; The [Icons] "quicklaunchicon" entry uses {userappdata}
|
||||
; but its [Tasks] entry has a proper IsAdminInstallMode Check.
|
||||
UsedUserAreasWarning=no
|
||||
|
||||
OutputDir=.
|
||||
OutputBaseFilename=LiteXL-{#MyAppVersion}-{#ArchInternal}-setup
|
||||
;DisableDirPage=yes
|
||||
;DisableProgramGroupPage=yes
|
||||
|
||||
LicenseFile={#SourceDir}/LICENSE
|
||||
SetupIconFile={#SourceDir}/resources/icons/icon.ico
|
||||
WizardImageFile="{#SourceDir}/scripts/innosetup/wizard-modern-image.bmp"
|
||||
WizardSmallImageFile="{#SourceDir}/scripts/innosetup/litexl-55px.bmp"
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
|
||||
Name: "portablemode"; Description: "Portable Mode"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "{#BuildDir}/src/lite-xl.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#BuildDir}/mingwLibs{#Arch}/*"; DestDir: "{app}"; Flags: ignoreversion ; Check: DirExists(ExpandConstant('{#BuildDir}/mingwLibs{#Arch}'))
|
||||
Source: "{#SourceDir}/data/*"; DestDir: "{app}/data"; Flags: ignoreversion recursesubdirs
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||
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]
|
||||
Filename: "{app}/{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[Setup]
|
||||
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 "$@"
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
|
@ -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,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
|
||||
local dirname="$1"
|
||||
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 () {
|
||||
|
@ -23,12 +23,17 @@ lite_copy_third_party_modules () {
|
|||
rm "$build/rxi-lite-colors.zip"
|
||||
}
|
||||
|
||||
lite_branch=master
|
||||
while [ ! -z ${1+x} ]; do
|
||||
case "$1" in
|
||||
-dir)
|
||||
use_dir="$(realpath $2)"
|
||||
shift 2
|
||||
;;
|
||||
-branch)
|
||||
lite_branch="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "unknown option: $1"
|
||||
exit 1
|
||||
|
@ -73,6 +78,8 @@ for filename in $(ls -1 *.zip *.tar.*); do
|
|||
fi
|
||||
rm "$filename"
|
||||
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')"
|
||||
coredir="$(dirname $xcoredir)"
|
||||
echo "coredir: $coredir"
|
||||
|
@ -81,6 +88,7 @@ for filename in $(ls -1 *.zip *.tar.*); do
|
|||
rm -fr "$coredir/$module_name"
|
||||
(cd .. && copy_directory_from_repo --strip-components=1 "data/$module_name" "$workdir/$coredir")
|
||||
done
|
||||
sed -i "s/@PROJECT_VERSION@/$lite_version/g" "$start_file"
|
||||
for module_name in plugins colors; do
|
||||
cp -r "third/data/$module_name" "$coredir"
|
||||
done
|
||||
|
|
|
@ -47,10 +47,7 @@ if [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "mingw"* ]]; then
|
|||
fi
|
||||
|
||||
rundir=".run"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
bindir="$rundir"
|
||||
datadir="$rundir"
|
||||
elif [ "$option_portable" == on ]; then
|
||||
if [ "$option_portable" == on ]; then
|
||||
bindir="$rundir"
|
||||
datadir="$rundir/data"
|
||||
else
|
||||
|
@ -75,9 +72,14 @@ copy_lite_build () {
|
|||
else
|
||||
cp "$builddir/src/lite-xl" "$bindir"
|
||||
fi
|
||||
mkdir -p "$datadir/core"
|
||||
for module_name in core plugins colors fonts; do
|
||||
cp -r "data/$module_name" "$datadir"
|
||||
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 () {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#define API_TYPE_FONT "Font"
|
||||
#define API_TYPE_REPLACE "Replace"
|
||||
#define API_TYPE_PROCESS "Process"
|
||||
|
||||
void api_load_libs(lua_State *L);
|
||||
|
||||
|
|
|
@ -5,33 +5,171 @@
|
|||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
#include <reproc/reproc.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <reproc/reproc.h>
|
||||
#include "api.h"
|
||||
|
||||
#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 {
|
||||
reproc_t * process;
|
||||
lua_State* L;
|
||||
|
||||
bool running;
|
||||
int returncode;
|
||||
} 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(
|
||||
L, sizeof(process_t)
|
||||
int ret = reproc_wait(proc->process, timeout);
|
||||
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;
|
||||
self->L = L;
|
||||
return ret;
|
||||
}
|
||||
|
||||
luaL_getmetatable(L, "PROCESS");
|
||||
lua_setmetatable(L, -2);
|
||||
static int process_start(lua_State* L)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -39,24 +177,20 @@ static int process_strerror(lua_State* L)
|
|||
{
|
||||
int error_code = luaL_checknumber(L, 1);
|
||||
|
||||
if(error_code){
|
||||
lua_pushstring(
|
||||
L,
|
||||
reproc_strerror(error_code)
|
||||
);
|
||||
} else {
|
||||
if (error_code < 0)
|
||||
lua_pushstring(L, reproc_strerror(error_code));
|
||||
else
|
||||
lua_pushnil(L);
|
||||
}
|
||||
|
||||
|
||||
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, "PROCESS");
|
||||
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||
|
||||
if(self->process){
|
||||
reproc_kill(self->process);
|
||||
if(self->process) {
|
||||
kill_process(self);
|
||||
reproc_destroy(self->process);
|
||||
self->process = NULL;
|
||||
}
|
||||
|
@ -64,385 +198,211 @@ static int process_gc(lua_State* L)
|
|||
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);
|
||||
lua_pushliteral(L, API_TYPE_PROCESS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* path = NULL;
|
||||
size_t path_len = 0;
|
||||
static int f_pid(lua_State* L)
|
||||
{
|
||||
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||
|
||||
if(lua_type(L, 3) == LUA_TSTRING){
|
||||
path = (char*) lua_tolstring(L, 3, &path_len);
|
||||
}
|
||||
lua_pushnumber(L, reproc_pid(self->process));
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t deadline = 0;
|
||||
static int f_returncode(lua_State *L)
|
||||
{
|
||||
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||
int ret = poll_process(self, 0);
|
||||
|
||||
if(lua_type(L, 4) == LUA_TNUMBER){
|
||||
deadline = lua_tonumber(L, 4);
|
||||
}
|
||||
if (self->running)
|
||||
lua_pushnil(L);
|
||||
else
|
||||
lua_pushnumber(L, ret);
|
||||
|
||||
size_t table_len = luaL_len(L, 2);
|
||||
char* command[table_len+1];
|
||||
command[table_len] = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int i;
|
||||
for(i=1; i<=table_len; i++){
|
||||
lua_pushnumber(L, i);
|
||||
lua_gettable(L, 2);
|
||||
static int g_read(lua_State* L, int stream)
|
||||
{
|
||||
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||
unsigned long read_size = luaL_optunsigned(L, 2, READ_BUF_SIZE);
|
||||
|
||||
command[i-1] = (char*) lua_tostring(L, -1);
|
||||
luaL_Buffer b;
|
||||
uint8_t* buffer = (uint8_t*) luaL_buffinitsize(L, &b, read_size);
|
||||
|
||||
lua_remove(L, -1);
|
||||
}
|
||||
|
||||
if(self->process){
|
||||
reproc_kill(self->process);
|
||||
reproc_destroy(self->process);
|
||||
}
|
||||
|
||||
self->process = reproc_new();
|
||||
|
||||
int out = reproc_start(
|
||||
int out = reproc_read(
|
||||
self->process,
|
||||
(const char* const*) command,
|
||||
(reproc_options){
|
||||
.working_directory = path,
|
||||
.deadline = deadline,
|
||||
.nonblocking=true,
|
||||
.redirect.err.type=REPROC_REDIRECT_PIPE
|
||||
}
|
||||
stream,
|
||||
buffer,
|
||||
read_size
|
||||
);
|
||||
|
||||
if(out > 0) {
|
||||
lua_pushboolean(L, 1);
|
||||
}
|
||||
else {
|
||||
reproc_destroy(self->process);
|
||||
self->process = NULL;
|
||||
lua_pushnumber(L, out);
|
||||
if (out >= 0)
|
||||
luaL_addsize(&b, out);
|
||||
luaL_pushresult(&b);
|
||||
|
||||
if (out == REPROC_EPIPE) {
|
||||
kill_process(self);
|
||||
ASSERT_REPROC_ERRNO(L, out);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int process_pid(lua_State* L)
|
||||
static int f_read_stdout(lua_State* L)
|
||||
{
|
||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
||||
|
||||
if(self->process){
|
||||
int id = reproc_pid(self->process);
|
||||
|
||||
if(id > 0){
|
||||
lua_pushnumber(L, id);
|
||||
} else {
|
||||
lua_pushnumber(L, 0);
|
||||
}
|
||||
} else {
|
||||
lua_pushnumber(L, 0);
|
||||
}
|
||||
|
||||
return 1;
|
||||
return g_read(L, REPROC_STREAM_OUT);
|
||||
}
|
||||
|
||||
static int process_read(lua_State* L)
|
||||
static int f_read_stderr(lua_State* L)
|
||||
{
|
||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
||||
|
||||
if(self->process){
|
||||
int read_size = 4096;
|
||||
if (lua_type(L, 2) == LUA_TNUMBER){
|
||||
read_size = (int) lua_tonumber(L, 2);
|
||||
}
|
||||
|
||||
int tries = 1;
|
||||
if (lua_type(L, 3) == LUA_TNUMBER){
|
||||
tries = (int) lua_tonumber(L, 3);
|
||||
}
|
||||
|
||||
int out = 0;
|
||||
uint8_t buffer[read_size];
|
||||
|
||||
int runs;
|
||||
for (runs=0; runs<tries; runs++){
|
||||
out = reproc_read(
|
||||
self->process,
|
||||
REPROC_STREAM_OUT,
|
||||
buffer,
|
||||
read_size
|
||||
);
|
||||
|
||||
if (out >= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// if request for tries was set and nothing
|
||||
// read kill the process
|
||||
if(tries > 1 && out < 0)
|
||||
out = REPROC_EPIPE;
|
||||
|
||||
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 g_read(L, REPROC_STREAM_ERR);
|
||||
}
|
||||
|
||||
static int process_read_errors(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){
|
||||
int read_size = 4096;
|
||||
if (lua_type(L, 2) == LUA_TNUMBER){
|
||||
read_size = (int) lua_tonumber(L, 2);
|
||||
}
|
||||
|
||||
int tries = 1;
|
||||
if (lua_type(L, 3) == LUA_TNUMBER){
|
||||
tries = (int) lua_tonumber(L, 3);
|
||||
}
|
||||
|
||||
int out = 0;
|
||||
uint8_t buffer[read_size];
|
||||
|
||||
int runs;
|
||||
for (runs=0; runs<tries; runs++){
|
||||
out = reproc_read(
|
||||
self->process,
|
||||
REPROC_STREAM_ERR,
|
||||
buffer,
|
||||
read_size
|
||||
);
|
||||
|
||||
if (out >= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// if request for tries was set and nothing
|
||||
// read kill the process
|
||||
if(tries > 1 && out < 0)
|
||||
out = REPROC_EPIPE;
|
||||
|
||||
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 g_read(L, stream);
|
||||
}
|
||||
|
||||
static int process_write(lua_State* L)
|
||||
static int f_write(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){
|
||||
size_t data_size = 0;
|
||||
const char* data = luaL_checklstring(L, 2, &data_size);
|
||||
size_t data_size = 0;
|
||||
const char* data = luaL_checklstring(L, 2, &data_size);
|
||||
|
||||
int out = 0;
|
||||
|
||||
out = reproc_write(
|
||||
self->process,
|
||||
(uint8_t*) data,
|
||||
data_size
|
||||
);
|
||||
|
||||
if(out == REPROC_EPIPE){
|
||||
reproc_kill(self->process);
|
||||
reproc_destroy(self->process);
|
||||
self->process = NULL;
|
||||
}
|
||||
|
||||
lua_pushnumber(L, out);
|
||||
} else {
|
||||
lua_pushnumber(L, REPROC_EPIPE);
|
||||
int out = reproc_write(
|
||||
self->process,
|
||||
(uint8_t*) data,
|
||||
data_size
|
||||
);
|
||||
if (out == REPROC_EPIPE) {
|
||||
kill_process(self);
|
||||
L_RETURN_REPROC_ERROR(L, out);
|
||||
}
|
||||
|
||||
lua_pushnumber(L, out);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int process_close_stream(lua_State* L)
|
||||
static int f_close_stream(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){
|
||||
size_t stream = luaL_checknumber(L, 2);
|
||||
|
||||
int out = reproc_close(self->process, stream);
|
||||
|
||||
lua_pushnumber(L, out);
|
||||
} else {
|
||||
lua_pushnumber(L, REPROC_EINVAL);
|
||||
}
|
||||
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 process_wait(lua_State* L)
|
||||
static int f_wait(lua_State* L)
|
||||
{
|
||||
process_t* self = (process_t*) lua_touserdata(L, 1);
|
||||
|
||||
if(self->process){
|
||||
size_t timeout = luaL_checknumber(L, 2);
|
||||
|
||||
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);
|
||||
}
|
||||
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 process_terminate(lua_State* L)
|
||||
static int f_terminate(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_terminate(self->process);
|
||||
int out = reproc_terminate(self->process);
|
||||
ASSERT_REPROC_ERRNO(L, out);
|
||||
|
||||
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);
|
||||
}
|
||||
poll_process(self, 0);
|
||||
lua_pushboolean(L, 1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int process_kill(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);
|
||||
int out = reproc_kill(self->process);
|
||||
ASSERT_REPROC_ERRNO(L, out);
|
||||
|
||||
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);
|
||||
}
|
||||
poll_process(self, 0);
|
||||
lua_pushboolean(L, 1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int process_running(lua_State* L)
|
||||
static int f_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);
|
||||
}
|
||||
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
|
||||
|
||||
poll_process(self, 0);
|
||||
lua_pushboolean(L, self->running);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct luaL_Reg process_methods[] = {
|
||||
{ "__gc", process_gc},
|
||||
static const struct luaL_Reg lib[] = {
|
||||
{"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},
|
||||
{"ERROR_PIPE", NULL},
|
||||
{"ERROR_WOULDBLOCK", NULL},
|
||||
{"ERROR_TIMEDOUT", NULL},
|
||||
{"ERROR_INVALID", NULL},
|
||||
{"STREAM_STDIN", NULL},
|
||||
{"STREAM_STDOUT", NULL},
|
||||
{"STREAM_STDERR", NULL},
|
||||
{"WAIT_INFINITE", NULL},
|
||||
{"WAIT_DEADLINE", NULL},
|
||||
{"__gc", f_gc},
|
||||
{"__tostring", f_tostring},
|
||||
{"pid", f_pid},
|
||||
{"returncode", f_returncode},
|
||||
{"read", f_read},
|
||||
{"read_stdout", f_read_stdout},
|
||||
{"read_stderr", f_read_stderr},
|
||||
{"write", f_write},
|
||||
{"close_stream", f_close_stream},
|
||||
{"wait", f_wait},
|
||||
{"terminate", f_terminate},
|
||||
{"kill", f_kill},
|
||||
{"running", f_running},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int luaopen_process(lua_State *L)
|
||||
{
|
||||
luaL_newmetatable(L, "PROCESS");
|
||||
luaL_setfuncs(L, process_methods, 0);
|
||||
luaL_newmetatable(L, API_TYPE_PROCESS);
|
||||
luaL_setfuncs(L, lib, 0);
|
||||
lua_pushvalue(L, -1);
|
||||
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);
|
||||
lua_setfield(L, -2, "ERROR_PIPE");
|
||||
L_SETNUM(L, -1, "WAIT_INFINITE", REPROC_INFINITE);
|
||||
L_SETNUM(L, -1, "WAIT_DEADLINE", REPROC_DEADLINE);
|
||||
|
||||
lua_pushnumber(L, REPROC_EWOULDBLOCK);
|
||||
lua_setfield(L, -2, "ERROR_WOULDBLOCK");
|
||||
L_SETNUM(L, -1, "STREAM_STDIN", REPROC_STREAM_IN);
|
||||
L_SETNUM(L, -1, "STREAM_STDOUT", REPROC_STREAM_OUT);
|
||||
L_SETNUM(L, -1, "STREAM_STDERR", REPROC_STREAM_ERR);
|
||||
|
||||
lua_pushnumber(L, REPROC_ETIMEDOUT);
|
||||
lua_setfield(L, -2, "ERROR_TIMEDOUT");
|
||||
|
||||
lua_pushnumber(L, REPROC_EINVAL);
|
||||
lua_setfield(L, -2, "ERROR_INVALID");
|
||||
|
||||
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");
|
||||
|
||||
lua_pushnumber(L, REPROC_INFINITE);
|
||||
lua_setfield(L, -2, "WAIT_INFINITE");
|
||||
|
||||
lua_pushnumber(L, REPROC_DEADLINE);
|
||||
lua_setfield(L, -2, "WAIT_DEADLINE");
|
||||
L_SETNUM(L, -1, "REDIRECT_DEFAULT", REPROC_REDIRECT_DEFAULT);
|
||||
L_SETNUM(L, -1, "REDIRECT_PIPE", REPROC_REDIRECT_PIPE);
|
||||
L_SETNUM(L, -1, "REDIRECT_PARENT", REPROC_REDIRECT_PARENT);
|
||||
L_SETNUM(L, -1, "REDIRECT_DISCARD", REPROC_REDIRECT_DISCARD);
|
||||
L_SETNUM(L, -1, "REDIRECT_STDOUT", REPROC_REDIRECT_STDOUT);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -74,11 +74,11 @@ static int f_pcre_match(lua_State *L) {
|
|||
}
|
||||
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(md);
|
||||
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,
|
||||
we just detect this case and give up. */
|
||||
luaL_error(L, "regex matching error: \\K was used in an assertion to "
|
||||
" set the match start after its end");
|
||||
" set the match start after its end");
|
||||
pcre2_match_data_free(md);
|
||||
return 0;
|
||||
}
|
||||
|
@ -103,8 +103,8 @@ int luaopen_regex(lua_State *L) {
|
|||
lua_setfield(L, LUA_REGISTRYINDEX, "regex");
|
||||
lua_pushnumber(L, PCRE2_ANCHORED);
|
||||
lua_setfield(L, -2, "ANCHORED");
|
||||
lua_pushnumber(L, PCRE2_ANCHORED) ;
|
||||
lua_setfield(L, -2, "ENDANCHORED");
|
||||
lua_pushnumber(L, PCRE2_ANCHORED) ;
|
||||
lua_setfield(L, -2, "ENDANCHORED");
|
||||
lua_pushnumber(L, PCRE2_NOTBOL);
|
||||
lua_setfield(L, -2, "NOTBOL");
|
||||
lua_pushnumber(L, PCRE2_NOTEOL);
|
||||
|
|
|
@ -1,31 +1,15 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#include "lua.h"
|
||||
|
||||
#ifdef MACOS_USE_BUNDLE
|
||||
void set_macos_bundle_resources(lua_State *L)
|
||||
{ @autoreleasepool
|
||||
{
|
||||
/* Use resolved executablePath instead of resourcePath to allow lanching
|
||||
the lite-xl binary via a symlink, like typically done by Homebrew:
|
||||
|
||||
/usr/local/bin/lite-xl -> /Applications/lite-xl.app/Contents/MacOS/lite-xl
|
||||
|
||||
The resourcePath returns /usr/local in this case instead of
|
||||
/Applications/lite-xl.app/Contents/Resources, which makes later
|
||||
access to the resource files fail. Resolving the symlink to the
|
||||
executable and then the relative path to the expected directory
|
||||
Resources is a workaround for starting the application from both
|
||||
the launcher directly and the command line via the symlink.
|
||||
*/
|
||||
NSString* executable_path = [[NSBundle mainBundle] executablePath];
|
||||
char resolved_path[PATH_MAX + 16 + 1];
|
||||
realpath([executable_path UTF8String], resolved_path);
|
||||
strcat(resolved_path, "/../../Resources");
|
||||
char resource_path[PATH_MAX + 1];
|
||||
realpath(resolved_path, resource_path);
|
||||
lua_pushstring(L, resource_path);
|
||||
NSString* resource_path = [[NSBundle mainBundle] resourcePath];
|
||||
lua_pushstring(L, [resource_path UTF8String]);
|
||||
lua_setglobal(L, "MACOS_RESOURCES");
|
||||
}}
|
||||
|
||||
#endif
|
||||
|
||||
/* Thanks to mathewmariani, taken from his lite-macos github repository. */
|
||||
void enable_momentum_scroll() {
|
||||
|
|
|
@ -1578,8 +1578,11 @@ _DMON_PRIVATE void dmon__fsevent_callback(ConstFSEventStreamRef stream_ref, void
|
|||
dmon__unixpath(abs_filepath, sizeof(abs_filepath), abs_filepath));
|
||||
|
||||
// strip the root dir
|
||||
DMON_ASSERT(strstr(abs_filepath, watch->rootdir) == abs_filepath);
|
||||
dmon__strcpy(ev.filepath, sizeof(ev.filepath), abs_filepath + strlen(watch->rootdir));
|
||||
size_t len = strlen(watch->rootdir);
|
||||
// FIXME: filesystems on macOS can be case sensitive or not. The check below
|
||||
// ignore case but we should check if the filesystem is case sensitive.
|
||||
DMON_ASSERT(strncasecmp(abs_filepath, watch->rootdir, len) == 0);
|
||||
dmon__strcpy(ev.filepath, sizeof(ev.filepath), abs_filepath + len);
|
||||
|
||||
ev.event_flags = flags;
|
||||
ev.event_id = event_id;
|
||||
|
|
48
src/main.c
48
src/main.c
|
@ -9,10 +9,6 @@
|
|||
#include <windows.h>
|
||||
#elif __linux__
|
||||
#include <unistd.h>
|
||||
#include <SDL_syswm.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xresource.h>
|
||||
#include <signal.h>
|
||||
#elif __APPLE__
|
||||
#include <mach-o/dyld.h>
|
||||
|
@ -24,34 +20,12 @@
|
|||
SDL_Window *window;
|
||||
|
||||
static double get_scale(void) {
|
||||
#ifdef _WIN32
|
||||
float dpi;
|
||||
#ifdef __APPLE__
|
||||
return 1.0;
|
||||
#else
|
||||
float dpi = 96.0;
|
||||
SDL_GetDisplayDPI(0, NULL, &dpi, NULL);
|
||||
return dpi / 96.0;
|
||||
#elif __linux__
|
||||
SDL_SysWMinfo info;
|
||||
XrmDatabase db;
|
||||
XrmValue value;
|
||||
char *type = NULL;
|
||||
|
||||
SDL_VERSION(&info.version);
|
||||
if (!SDL_GetWindowWMInfo(window, &info)
|
||||
|| info.subsystem != SDL_SYSWM_X11)
|
||||
return 1.0;
|
||||
|
||||
char *resource = XResourceManagerString(info.info.x11.display);
|
||||
if (resource == NULL)
|
||||
return 1.0;
|
||||
|
||||
XrmInitialize();
|
||||
db = XrmGetStringDatabase(resource);
|
||||
if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == False
|
||||
|| value.addr == NULL)
|
||||
return 1.0;
|
||||
|
||||
return atof(value.addr) / 96.0;
|
||||
#else
|
||||
return 1.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -106,8 +80,10 @@ static void init_window_icon(void) {
|
|||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
void set_macos_bundle_resources(lua_State *L);
|
||||
void enable_momentum_scroll();
|
||||
#ifdef MACOS_USE_BUNDLE
|
||||
void set_macos_bundle_resources(lua_State *L);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
@ -168,16 +144,20 @@ init_lua:
|
|||
lua_setglobal(L, "EXEFILE");
|
||||
|
||||
#ifdef __APPLE__
|
||||
set_macos_bundle_resources(L);
|
||||
lua_pushboolean(L, true);
|
||||
lua_setglobal(L, "MACOS");
|
||||
enable_momentum_scroll();
|
||||
#ifdef MACOS_USE_BUNDLE
|
||||
set_macos_bundle_resources(L);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const char *init_lite_code = \
|
||||
"local core\n"
|
||||
"xpcall(function()\n"
|
||||
" HOME = os.getenv('" LITE_OS_HOME "')\n"
|
||||
" local exedir = EXEFILE:match(\"^(.*)" LITE_PATHSEP_PATTERN LITE_NONPATHSEP_PATTERN "$\")\n"
|
||||
" local prefix = exedir:match(\"^(.*)" LITE_PATHSEP_PATTERN "bin$\")\n"
|
||||
" local exedir = EXEFILE:match('^(.*)" LITE_PATHSEP_PATTERN LITE_NONPATHSEP_PATTERN "$')\n"
|
||||
" local prefix = exedir:match('^(.*)" LITE_PATHSEP_PATTERN "bin$')\n"
|
||||
" dofile((MACOS_RESOURCES or (prefix and prefix .. '/share/lite-xl' or exedir .. '/data')) .. '/core/start.lua')\n"
|
||||
" core = require('core')\n"
|
||||
" core.init()\n"
|
||||
|
|
|
@ -14,17 +14,25 @@ lite_sources = [
|
|||
'main.c',
|
||||
]
|
||||
|
||||
if host_machine.system() == 'darwin'
|
||||
lite_rc = []
|
||||
if host_machine.system() == 'windows'
|
||||
windows = import('windows')
|
||||
lite_rc += windows.compile_resources('../resources/icons/icon.rc')
|
||||
elif host_machine.system() == 'darwin'
|
||||
lite_sources += 'bundle_open.m'
|
||||
endif
|
||||
|
||||
lite_include = include_directories('.')
|
||||
|
||||
executable('lite-xl',
|
||||
lite_sources + lite_rc,
|
||||
include_directories: [lite_include, font_renderer_include],
|
||||
dependencies: lite_deps,
|
||||
c_args: lite_cargs,
|
||||
objc_args: lite_cargs,
|
||||
link_with: libfontrenderer,
|
||||
link_args: lite_link_args,
|
||||
install_dir: lite_bindir,
|
||||
install: true,
|
||||
gui_app: true,
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[wrap-git]
|
||||
directory = reproc
|
||||
url = https://github.com/franko/reproc
|
||||
revision = v14.2.2-meson-1
|
||||
revision = v14.2.3-meson-1
|
||||
|
|
Loading…
Reference in New Issue