Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
George Sokianos | cb30b591d2 | |
George Sokianos | 9d13525af0 |
|
@ -33,6 +33,3 @@
|
|||
|
||||
"Category: C Core":
|
||||
- src/**/*
|
||||
|
||||
"Category: Libraries":
|
||||
- lib/**/*
|
||||
|
|
|
@ -1,20 +1,45 @@
|
|||
name: CI
|
||||
|
||||
# All builds use lhelper only for releases,
|
||||
# otherwise for normal builds dependencies are dynamically linked.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
- '*'
|
||||
# tags:
|
||||
# - 'v[0-9]*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
workflow_dispatch:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
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-22.04
|
||||
runs-on: ubuntu-18.04
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
|
@ -24,204 +49,204 @@ jobs:
|
|||
CC: ${{ matrix.config.cc }}
|
||||
CXX: ${{ matrix.config.cxx }}
|
||||
steps:
|
||||
- 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)-portable" >> "$GITHUB_ENV"
|
||||
- uses: actions/checkout@v3
|
||||
- name: Python Setup
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Update Packages
|
||||
run: sudo apt-get update
|
||||
- name: Install Dependencies
|
||||
run: bash scripts/install-dependencies.sh --debug
|
||||
- name: Build
|
||||
run: |
|
||||
bash --version
|
||||
bash scripts/build.sh --debug --forcefallback --portable
|
||||
- name: Package
|
||||
if: ${{ matrix.config.cc == 'gcc' }}
|
||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --binary
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ matrix.config.cc == 'gcc' }}
|
||||
with:
|
||||
name: Linux Artifacts
|
||||
path: ${{ env.INSTALL_NAME }}.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_macos:
|
||||
name: macOS
|
||||
runs-on: macos-11
|
||||
name: macOS (x86_64)
|
||||
runs-on: macos-10.15
|
||||
env:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ['x86_64', 'arm64']
|
||||
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-${{ matrix.arch }}" >> "$GITHUB_ENV"
|
||||
if [[ $(uname -m) != ${{ matrix.arch }} ]]; then echo "ARCH=--cross-arch ${{ matrix.arch }}" >> "$GITHUB_ENV"; fi
|
||||
- uses: actions/checkout@v3
|
||||
- name: Python Setup
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install Dependencies
|
||||
# --lhelper will eliminate a warning with arm64 and libusb
|
||||
run: bash scripts/install-dependencies.sh --debug --lhelper
|
||||
- name: Build
|
||||
run: |
|
||||
bash --version
|
||||
bash scripts/build.sh --bundle --debug --forcefallback $ARCH
|
||||
- name: Create DMG Image
|
||||
run: bash scripts/package.sh --version ${INSTALL_REF} $ARCH --debug --dmg
|
||||
- name: Upload DMG Image
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macOS DMG Images
|
||||
path: ${{ env.INSTALL_NAME }}.dmg
|
||||
|
||||
build_macos_universal:
|
||||
name: macOS (Universal)
|
||||
runs-on: macos-11
|
||||
needs: build_macos
|
||||
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_NAME=lite-xl-${GITHUB_REF##*/}-macos-universal" >> "$GITHUB_ENV"
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Install dmgbuild
|
||||
run: pip install dmgbuild
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
id: download
|
||||
with:
|
||||
name: macOS DMG Images
|
||||
path: dmgs-original
|
||||
- name: Make universal bundles
|
||||
run: |
|
||||
bash --version
|
||||
bash scripts/make-universal-binaries.sh ${{ steps.download.outputs.download-path }} "${INSTALL_NAME}"
|
||||
- name: Upload DMG Image
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macOS Universal DMG Images
|
||||
path: ${{ env.INSTALL_NAME }}.dmg
|
||||
- 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:
|
||||
- {msystem: MINGW32, arch: i686}
|
||||
- {msystem: MINGW64, arch: x86_64}
|
||||
msystem: [MINGW32, MINGW64]
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.config.msystem }}
|
||||
#msystem: MINGW64
|
||||
msystem: ${{ matrix.msystem }}
|
||||
update: true
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
zip
|
||||
mingw-w64-${{ matrix.config.arch }}-gcc
|
||||
mingw-w64-${{ matrix.config.arch }}-meson
|
||||
mingw-w64-${{ matrix.config.arch }}-ninja
|
||||
mingw-w64-${{ matrix.config.arch }}-ca-certificates
|
||||
mingw-w64-${{ matrix.config.arch }}-ntldd
|
||||
- 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"
|
||||
if [[ "${MSYSTEM}" == "MINGW64" ]]; then
|
||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-windows-x86_64" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-windows-i686" >> "$GITHUB_ENV"
|
||||
fi
|
||||
- name: Install Dependencies
|
||||
if: false
|
||||
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 -U --debug --forcefallback
|
||||
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 --binary
|
||||
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@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Windows Artifacts
|
||||
path: ${{ env.INSTALL_NAME }}.zip
|
||||
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
|
||||
|
||||
build_windows_msvc:
|
||||
name: Windows (MSVC)
|
||||
runs-on: windows-2019
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- { target: x86, name: i686 }
|
||||
- { target: x64, name: x86_64 }
|
||||
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:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: ${{ matrix.arch.target }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install meson and ninja
|
||||
run: pip install meson ninja
|
||||
- name: Set up environment variables
|
||||
run: |
|
||||
"INSTALL_NAME=lite-xl-$($env:GITHUB_REF -replace ".*/")-windows-msvc-${{ matrix.arch.name }}" >> $env:GITHUB_ENV
|
||||
"INSTALL_REF=$($env:GITHUB_REF -replace ".*/")" >> $env:GITHUB_ENV
|
||||
"LUA_SUBPROJECT_PATH=subprojects/$(awk -F ' *= *' '/directory/ { printf $2 }' subprojects/lua.wrap)" >> $env:GITHUB_ENV
|
||||
- name: Download and patch subprojects
|
||||
shell: bash
|
||||
run: |
|
||||
meson subprojects download
|
||||
cat resources/windows/001-lua-unicode.diff | patch -Np1 -d "$LUA_SUBPROJECT_PATH"
|
||||
- name: Configure
|
||||
run: |
|
||||
meson setup --wrap-mode=forcefallback build
|
||||
- name: Build
|
||||
run: |
|
||||
meson install -C build --destdir="../lite-xl"
|
||||
- name: Package
|
||||
run: |
|
||||
Remove-Item -Recurse -Force -Path "lite-xl/lib","lite-xl/include"
|
||||
Compress-Archive -Path lite-xl -DestinationPath "$env:INSTALL_NAME.zip"
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Windows Artifacts (MSVC)
|
||||
path: ${{ env.INSTALL_NAME }}.zip
|
||||
- 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,267 +0,0 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.*
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Release Version
|
||||
default: v2.1.4
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
version: ${{ steps.tag.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Fetch Version
|
||||
id: tag
|
||||
run: |
|
||||
if [[ "${{ github.event.inputs.version }}" != "" ]]; then
|
||||
echo ::set-output name=version::${{ github.event.inputs.version }}
|
||||
else
|
||||
echo ::set-output name=version::${GITHUB_REF/refs\/tags\//}
|
||||
fi
|
||||
- name: Update Tag
|
||||
uses: richardsimko/update-tag@v1
|
||||
with:
|
||||
tag_name: ${{ steps.tag.outputs.version }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ steps.tag.outputs.version }}
|
||||
name: Lite XL ${{ steps.tag.outputs.version }}
|
||||
draft: true
|
||||
body_path: changelog.md
|
||||
generate_release_notes: true
|
||||
|
||||
build_linux:
|
||||
name: Linux
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/lite-xl/lite-xl-build-box:latest
|
||||
env:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
steps:
|
||||
- name: Set Environment Variables
|
||||
run: |
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# disabled because this will break our own Python install
|
||||
- name: Python Setup
|
||||
if: false
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
# disabled because the container has up-to-date packages
|
||||
- name: Update Packages
|
||||
if: false
|
||||
run: sudo apt-get update
|
||||
|
||||
# disabled as the dependencies are already installed
|
||||
- name: Install Dependencies
|
||||
if: false
|
||||
run: |
|
||||
bash scripts/install-dependencies.sh --debug
|
||||
sudo apt-get install -y ccache
|
||||
|
||||
- name: Build Portable
|
||||
run: |
|
||||
bash --version
|
||||
bash scripts/build.sh --debug --forcefallback --portable --release
|
||||
- name: Package Portables
|
||||
run: |
|
||||
bash scripts/package.sh --version ${INSTALL_REF} --debug --binary --release
|
||||
bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --binary --release
|
||||
- name: Build AppImages
|
||||
run: |
|
||||
bash scripts/appimage.sh --debug --static --version ${INSTALL_REF} --release
|
||||
bash scripts/appimage.sh --debug --nobuild --addons --version ${INSTALL_REF}
|
||||
- name: Upload Files
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ needs.release.outputs.version }}
|
||||
draft: true
|
||||
files: |
|
||||
lite-xl-${{ env.INSTALL_REF }}-linux-x86_64-portable.tar.gz
|
||||
lite-xl-${{ env.INSTALL_REF }}-addons-linux-x86_64-portable.tar.gz
|
||||
LiteXL-${{ env.INSTALL_REF }}-x86_64.AppImage
|
||||
LiteXL-${{ env.INSTALL_REF }}-addons-x86_64.AppImage
|
||||
|
||||
build_macos:
|
||||
name: macOS
|
||||
needs: release
|
||||
runs-on: macos-11
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86_64, arm64]
|
||||
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=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_NAME=lite-xl-${{ needs.release.outputs.version }}-macos-${{ matrix.arch }}" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_NAME_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-macos-${{ matrix.arch }}" >> "$GITHUB_ENV"
|
||||
if [[ $(uname -m) != ${{ matrix.arch }} ]]; then echo "ARCH=--cross-arch ${{ matrix.arch }}" >> "$GITHUB_ENV"; fi
|
||||
- uses: actions/checkout@v3
|
||||
- name: Python Setup
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install Dependencies
|
||||
run: bash scripts/install-dependencies.sh --debug
|
||||
- name: Build
|
||||
run: |
|
||||
bash --version
|
||||
bash scripts/build.sh --bundle --debug --forcefallback --release $ARCH
|
||||
- name: Create DMG Image
|
||||
run: |
|
||||
bash scripts/package.sh --version ${INSTALL_REF} $ARCH --debug --dmg --release
|
||||
bash scripts/package.sh --version ${INSTALL_REF} $ARCH --debug --addons --dmg --release
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macOS DMG Images
|
||||
path: |
|
||||
${{ env.INSTALL_NAME }}.dmg
|
||||
${{ env.INSTALL_NAME_ADDONS }}.dmg
|
||||
- name: Upload Files
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ needs.release.outputs.version }}
|
||||
draft: true
|
||||
files: |
|
||||
${{ env.INSTALL_NAME }}.dmg
|
||||
${{ env.INSTALL_NAME_ADDONS }}.dmg
|
||||
|
||||
build_macos_universal:
|
||||
name: macOS (Universal)
|
||||
needs: [release, build_macos]
|
||||
runs-on: macos-11
|
||||
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_BASE=lite-xl-${{ needs.release.outputs.version }}-macos" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_BASE_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-macos" >> "$GITHUB_ENV"
|
||||
- uses: actions/checkout@v2
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
id: download
|
||||
with:
|
||||
name: macOS DMG Images
|
||||
path: dmgs-original
|
||||
- name: Python Setup
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install dmgbuild
|
||||
run: pip install dmgbuild
|
||||
- name: Prepare DMG Images
|
||||
run: |
|
||||
mkdir -p dmgs-addons dmgs-normal
|
||||
mv -v "${{ steps.download.outputs.download-path }}/$INSTALL_BASE-"{x86_64,arm64}.dmg dmgs-normal
|
||||
mv -v "${{ steps.download.outputs.download-path }}/$INSTALL_BASE_ADDONS-"{x86_64,arm64}.dmg dmgs-addons
|
||||
- name: Create Universal DMGs
|
||||
run: |
|
||||
bash --version
|
||||
bash scripts/make-universal-binaries.sh dmgs-normal "$INSTALL_BASE-universal"
|
||||
bash scripts/make-universal-binaries.sh dmgs-addons "$INSTALL_BASE_ADDONS-universal"
|
||||
- name: Upload Files
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ needs.release.outputs.version }}
|
||||
draft: true
|
||||
files: |
|
||||
${{ env.INSTALL_BASE }}-universal.dmg
|
||||
${{ env.INSTALL_BASE_ADDONS }}-universal.dmg
|
||||
|
||||
build_windows_msys2:
|
||||
name: Windows
|
||||
needs: release
|
||||
runs-on: windows-2019
|
||||
strategy:
|
||||
matrix:
|
||||
msystem: [MINGW32, MINGW64]
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.msystem }}
|
||||
update: true
|
||||
install: >-
|
||||
base-devel
|
||||
git
|
||||
zip
|
||||
- name: Set Environment Variables
|
||||
run: |
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
|
||||
if [[ "${MSYSTEM}" == "MINGW64" ]]; then
|
||||
echo "BUILD_ARCH=x86_64" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_NAME=lite-xl-${{ needs.release.outputs.version }}-windows-x86_64" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_NAME_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-windows-x86_64" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "BUILD_ARCH=i686" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_NAME=lite-xl-${{ needs.release.outputs.version }}-windows-i686" >> "$GITHUB_ENV"
|
||||
echo "INSTALL_NAME_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-windows-i686" >> "$GITHUB_ENV"
|
||||
fi
|
||||
- name: Install Dependencies
|
||||
run: bash scripts/install-dependencies.sh --debug
|
||||
- name: Build
|
||||
run: |
|
||||
bash --version
|
||||
bash scripts/build.sh -U --debug --forcefallback --release
|
||||
- name: Package
|
||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --binary --release
|
||||
- name: Build Installer
|
||||
run: bash scripts/innosetup/innosetup.sh --debug --version ${INSTALL_REF}
|
||||
- name: Package With Addons
|
||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --binary --release
|
||||
- name: Build Installer With Addons
|
||||
run: bash scripts/innosetup/innosetup.sh --debug --version ${INSTALL_REF} --addons
|
||||
- name: Upload Files
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ needs.release.outputs.version }}
|
||||
draft: true
|
||||
files: |
|
||||
${{ env.INSTALL_NAME }}.zip
|
||||
${{ env.INSTALL_NAME_ADDONS }}.zip
|
||||
LiteXL-${{ env.INSTALL_REF }}-${{ env.BUILD_ARCH }}-setup.exe
|
||||
LiteXL-${{ env.INSTALL_REF }}-addons-${{ env.BUILD_ARCH }}-setup.exe
|
|
@ -2,10 +2,9 @@ build*/
|
|||
.build*/
|
||||
lhelper/
|
||||
submodules/
|
||||
subprojects/*/
|
||||
subprojects/lua/
|
||||
subprojects/reproc/
|
||||
/appimage*
|
||||
.vscode
|
||||
.cache
|
||||
.ccls-cache
|
||||
.lite-debug.log
|
||||
.run*
|
||||
|
@ -22,10 +21,6 @@ LiteXL*
|
|||
lite
|
||||
.config/
|
||||
*.lha
|
||||
release_files
|
||||
*.o
|
||||
*.snalyzerinfo
|
||||
|
||||
|
||||
!resources/windows/*.diff
|
||||
!resources/windows/*.exe.manifest.in
|
||||
!resources/macos/*.py
|
||||
|
|
4
LICENSE
4
LICENSE
|
@ -1,6 +1,4 @@
|
|||
Copyright (c) 2020 rxi
|
||||
Copyright (c) 2020-2022 Francesco Abbate
|
||||
Copyright (c) 2022-present Lite XL Team
|
||||
Copyright (c) 2020-2021 Francesco Abbate
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
|
89
Makefile.mos
89
Makefile.mos
|
@ -1,89 +0,0 @@
|
|||
#
|
||||
# Project: Lite XL
|
||||
#
|
||||
# Created on: 26-12-2021
|
||||
#
|
||||
|
||||
LiteXL_OBJ := \
|
||||
src/main.o src/rencache.o src/renderer.o src/renwindow.o \
|
||||
src/api/api.o src/api/dirmonitor.o \
|
||||
src/api/regex.o src/api/renderer.o src/api/system.o \
|
||||
src/api/utf8.o src/platform/morphos.o \
|
||||
src/api/dirmonitor/mos.o src/platform/codesets.o
|
||||
|
||||
outfile := lite-xl
|
||||
compiler := ppc-morphos-gcc-11
|
||||
cxxcompiler := ppc-morphos-g++-11
|
||||
|
||||
INCPATH := -Isrc -Ilib/dmon -I/sdk/gg/usr/local/include/SDL2 \
|
||||
-I/sdk/gg/usr/include/freetype -I/sdk/gg/usr/include/lua5.4
|
||||
DFLAGS ?= -D__USE_INLINE__
|
||||
CFLAGS ?= -Wall -Wwrite-strings -O2 -noixemul -g -std=gnu11 -fno-strict-aliasing
|
||||
LFLAGS ?= -noixemul -lpcre2-8 -lSDL2 -llua54 -lagg -lfreetype -lm -lc -L/usr/local/lib
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
CFLAGS += -g -gstabs
|
||||
LFLAGS += -gstabs
|
||||
endif
|
||||
|
||||
.PHONY: LiteXL clean release
|
||||
|
||||
default: LiteXL
|
||||
|
||||
clean:
|
||||
@echo "Cleaning compiler objects..."
|
||||
@rm -f $(LiteXL_OBJ)
|
||||
|
||||
LiteXL: $(LiteXL_OBJ)
|
||||
@echo "Linking LiteXL"
|
||||
$(compiler) -o $(outfile) $(LiteXL_OBJ) $(LFLAGS)
|
||||
|
||||
.c.o:
|
||||
@echo "Compiling $<"
|
||||
$(compiler) -c $< -o $*.o $(CFLAGS) $(INCPATH) $(DFLAGS)
|
||||
|
||||
src/main.o: src/main.c src/api/api.h src/rencache.h \
|
||||
src/renderer.h src/platform/morphos.h src/platform/codesets.h
|
||||
|
||||
src/rencache.o: src/rencache.c
|
||||
|
||||
src/renderer.o: src/renderer.c
|
||||
|
||||
src/renwindow.o: src/renwindow.c
|
||||
|
||||
src/api/api.o: src/api/api.c
|
||||
|
||||
src/api/regex.o: src/api/regex.c
|
||||
|
||||
src/api/renderer.o: src/api/renderer.c
|
||||
|
||||
src/api/system.o: src/api/system.c src/platform/morphos.h
|
||||
|
||||
src/platform/morphos.o: src/platform/morphos.c
|
||||
|
||||
src/api/dirmonitor.o: src/api/dirmonitor.c src/api/dirmonitor/mos.c
|
||||
|
||||
src/api/utf8.o: src/api/utf8.c
|
||||
|
||||
src/api/dirmonitor/mos.o: src/api/dirmonitor/mos.c
|
||||
|
||||
src/platform/codesets.o: src/platform/codesets.c
|
||||
|
||||
release: clean LiteXL
|
||||
@echo "Creating release files..."
|
||||
@mkdir -p release/LiteXL2
|
||||
@cp -r resources/amiga/* release/LiteXL2/
|
||||
@mv release/LiteXL2/LiteXL2.info release/
|
||||
@rm release/LiteXL2/AutoInstall
|
||||
@cp -r data release/LiteXL2/
|
||||
@cp changelog.md release/LiteXL2/
|
||||
@cp $(outfile) release/LiteXL2/
|
||||
@strip release/LiteXL2/$(outfile)
|
||||
@cp README.md release/LiteXL2/
|
||||
@cp README_Amiga.md release/LiteXL2/
|
||||
@cp LICENSE release/LiteXL2/
|
||||
@cp -r licenses release/LiteXL2/
|
||||
@echo "Creating release archive..."
|
||||
@lha -aeqr3 a LiteXL2_MOS.lha release/
|
||||
@echo "Clean release files..."
|
||||
@delete release ALL QUIET FORCE
|
82
Makefile.os4
82
Makefile.os4
|
@ -1,38 +1,28 @@
|
|||
#
|
||||
# Project: Lite XL
|
||||
#
|
||||
# Created on: 26-12-2021
|
||||
# Created on: 26-12-2021
|
||||
#
|
||||
|
||||
LiteXL_OBJ := \
|
||||
src/main.o src/rencache.o src/renderer.o src/renwindow.o \
|
||||
src/api/api.o src/api/dirmonitor.o \
|
||||
src/api/regex.o src/api/renderer.o src/api/system.o \
|
||||
src/api/utf8.o src/platform/amigaos4.o \
|
||||
src/api/dirmonitor/os4.o src/platform/codesets.o
|
||||
src/api/dirmonitor.o src/main.o src/rencache.o src/renderer.o \
|
||||
src/renwindow.o src/api/api.o src/api/regex.o \
|
||||
src/api/renderer.o src/api/system.o src/platform/amigaos4.o
|
||||
|
||||
outfile := lite-xl
|
||||
|
||||
outfile := lite
|
||||
compiler := gcc
|
||||
cxxcompiler := g++
|
||||
|
||||
INCPATH := -Isrc -I/sdk/local/newlib/include/SDL2 \
|
||||
-I/sdk/local/common/include/lua54 -I/sdk/local/common/include/freetype2
|
||||
INCPATH := -Isrc -Ilib/dmon -I/sdk/local/newlib/include/SDL2 -I/sdk/local/common/include/freetype2
|
||||
DFLAGS := -D__USE_INLINE__ -DLITE_XL_DATA_USE_EXEDIR
|
||||
# -DLITE_USE_SDL_RENDERER
|
||||
# -Wextra -Wall
|
||||
CFLAGS := -Werror -Wwrite-strings -O3 -g -std=gnu11 -fno-strict-aliasing
|
||||
# "-gstabs -finstrument-functions -fno-inline -DPROFILING"
|
||||
LFLAGS := -mcrt=newlib -static-libgcc -static-libstdc++ -lauto -lpcre2 -lSDL2 -llua -lagg -lfreetype -lm -lunix -lpthread -athread=native
|
||||
# " -lprofyle"
|
||||
|
||||
DFLAGS += -D__USE_INLINE__ -DLITE_XL_DATA_USE_EXEDIR
|
||||
CFLAGS ?= -Werror -Wwrite-strings -O3 -std=gnu11 -fno-strict-aliasing
|
||||
LFLAGS ?= -mcrt=newlib -lpcre2-8 -lSDL2 -llua54 -lfreetype -lpng -lz \
|
||||
-lpthread -athread=native
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
CFLAGS += -g -gstabs
|
||||
LFLAGS += -gstabs
|
||||
|
||||
ifeq ($(PROFYLER),1)
|
||||
CFLAGS += -finstrument-functions -fno-inline -DPROFILING
|
||||
LFLAGS += -lprofyle
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
|
||||
.PHONY: LiteXL clean release
|
||||
|
@ -45,16 +35,18 @@ clean:
|
|||
|
||||
LiteXL: $(LiteXL_OBJ)
|
||||
@echo "Linking LiteXL"
|
||||
@$(compiler) -o $(outfile) $(LiteXL_OBJ) $(LFLAGS)
|
||||
@$(cxxcompiler) -o $(outfile) $(LiteXL_OBJ) $(LFLAGS)
|
||||
|
||||
|
||||
|
||||
.c.o:
|
||||
@echo "Compiling $<"
|
||||
@$(compiler) -c $< -o $*.o $(CFLAGS) $(INCPATH) $(DFLAGS)
|
||||
|
||||
src/dirmonitor.o: src/api/dirmonitor.c
|
||||
|
||||
src/main.o: src/main.c src/api/api.h src/rencache.h \
|
||||
src/renderer.h src/platform/amigaos4.h src/platform/codesets.h
|
||||
src/renderer.h src/platform/amigaos4.h
|
||||
|
||||
src/rencache.o: src/rencache.c
|
||||
|
||||
|
@ -68,35 +60,23 @@ src/api/regex.o: src/api/regex.c
|
|||
|
||||
src/api/renderer.o: src/api/renderer.c
|
||||
|
||||
src/api/system.o: src/api/system.c src/platform/amigaos4.h
|
||||
src/api/system.o: src/api/system.c
|
||||
|
||||
src/platform/amigaos4.o: src/platform/amigaos4.c
|
||||
|
||||
src/platform/codesets.o: src/platform/codesets.c
|
||||
|
||||
src/api/dirmonitor.o: src/api/dirmonitor.c src/api/dirmonitor/os4.c
|
||||
|
||||
src/api/utf8.o: src/api/utf8.c src/platform/amigaos4.h
|
||||
|
||||
src/api/dirmonitor/os4.o: src/api/dirmonitor/os4.c
|
||||
release:
|
||||
mkdir -p release/LiteXL
|
||||
cp release_files/* release/LiteXL/ -r
|
||||
mv release/LiteXL/LiteXL.info release/
|
||||
cp data release/LiteXL/ -r
|
||||
cp changelog.md release/LiteXL/
|
||||
cp lite release/LiteXL/
|
||||
strip release/LiteXL/lite
|
||||
cp README.md release/LiteXL/
|
||||
cp README_OS4.md release/LiteXL/
|
||||
cp LICENSE release/LiteXL/
|
||||
lha -aeqr3 a LiteXL.lha release/
|
||||
|
||||
src/api/process.o: src/api/process.c
|
||||
|
||||
release: clean LiteXL
|
||||
@echo "Creating release files..."
|
||||
@mkdir -p release/LiteXL2
|
||||
@cp -r resources/amiga/* release/LiteXL2/
|
||||
@mv release/LiteXL2/LiteXL2.info release/
|
||||
@mv release/LiteXL2/AutoInstall release/
|
||||
@cp -r data release/LiteXL2/
|
||||
@cp changelog.md release/LiteXL2/
|
||||
@cp $(outfile) release/LiteXL2/
|
||||
@strip release/LiteXL2/$(outfile)
|
||||
@cp README.md release/LiteXL2/
|
||||
@cp README_Amiga.md release/LiteXL2/
|
||||
@cp LICENSE release/LiteXL2/
|
||||
@cp -r licenses release/LiteXL2/
|
||||
@echo "Creating release archive..."
|
||||
@lha -aeqr3 a LiteXL2_OS4.lha release/
|
||||
@echo "Clean release files..."
|
||||
@delete release ALL QUIET FORCE
|
||||
|
|
44
README.md
44
README.md
|
@ -27,7 +27,7 @@ The changes and differences between Lite XL and rxi/lite are listed in the
|
|||
|
||||
## Overview
|
||||
|
||||
Lite XL is derived from [lite].
|
||||
Lite XL is derived from lite.
|
||||
It is a lightweight text editor written mostly in Lua — it aims to provide
|
||||
something practical, pretty, *small* and fast easy to modify and extend,
|
||||
or to use without doing either.
|
||||
|
@ -81,39 +81,6 @@ affects only the place where the application is actually installed.
|
|||
|
||||
Head over to [releases](https://github.com/lite-xl/lite-xl/releases) and download the version for your operating system.
|
||||
|
||||
### Windows
|
||||
|
||||
Lite XL comes with installers on Windows for typical installations.
|
||||
Alternatively, we provide ZIP archives that you can download and extract anywhere and run directly.
|
||||
|
||||
To make Lite XL portable (e.g. running Lite XL from a thumb drive),
|
||||
simply create a `user` folder where `lite-xl.exe` is located.
|
||||
Lite XL will load and store all your configurations and plugins in the folder.
|
||||
|
||||
### macOS
|
||||
|
||||
We provide DMG files for macOS. Simply drag the program into your Applications folder.
|
||||
|
||||
> **Important**
|
||||
> Newer versions of Lite XL are signed with a self-signed certificate,
|
||||
> so you'll have to follow these steps when running Lite XL for the first time.
|
||||
>
|
||||
> 1. Find Lite XL in Finder (do not open it in Launchpad).
|
||||
> 2. Control-click Lite XL, then choose `Open` from the shortcut menu.
|
||||
> 3. Click `Open` in the popup menu.
|
||||
>
|
||||
> The correct steps may vary between macOS versions, so you should refer to
|
||||
> the [macOS User Guide](https://support.apple.com/en-my/guide/mac-help/mh40616/mac).
|
||||
>
|
||||
> On an older version of Lite XL, you will need to run these commands instead:
|
||||
>
|
||||
> ```sh
|
||||
> # clears attributes from the directory
|
||||
> xattr -cr /Applications/Lite\ XL.app
|
||||
> ```
|
||||
>
|
||||
> Otherwise, macOS will display a **very misleading error** saying that the application is damaged.
|
||||
|
||||
### Linux
|
||||
|
||||
Unzip the file and `cd` into the `lite-xl` directory:
|
||||
|
@ -125,15 +92,15 @@ cd lite-xl
|
|||
|
||||
To run lite-xl without installing:
|
||||
```sh
|
||||
cd bin
|
||||
./lite-xl
|
||||
```
|
||||
|
||||
To install lite-xl copy files over into appropriate directories:
|
||||
|
||||
```sh
|
||||
rm -rf $HOME/.local/share/lite-xl $HOME/.local/bin/lite-xl
|
||||
mkdir -p $HOME/.local/bin && cp lite-xl $HOME/.local/bin/
|
||||
mkdir -p $HOME/.local/share/lite-xl && cp -r data/* $HOME/.local/share/lite-xl/
|
||||
mkdir -p $HOME/.local/bin && cp bin/lite-xl $HOME/.local/bin
|
||||
cp -r share $HOME/.local
|
||||
```
|
||||
|
||||
If `$HOME/.local/bin` is not in PATH:
|
||||
|
@ -181,13 +148,12 @@ See the [licenses] file for details on licenses used by the required dependencie
|
|||
[screenshot-dark]: https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png
|
||||
[lite]: https://github.com/rxi/lite
|
||||
[website]: https://lite-xl.com
|
||||
[build]: https://lite-xl.com/en/documentation/build
|
||||
[build]: https://lite-xl.com/en/documentation/build/
|
||||
[Get Lite XL]: https://github.com/lite-xl/lite-xl/releases/latest
|
||||
[Get plugins]: https://github.com/lite-xl/lite-xl-plugins
|
||||
[Get color themes]: https://github.com/lite-xl/lite-xl-colors
|
||||
[changelog]: https://github.com/lite-xl/lite-xl/blob/master/changelog.md
|
||||
[Lite XL plugins repository]: https://github.com/lite-xl/lite-xl-plugins
|
||||
[plugins repository]: https://github.com/rxi/lite-plugins
|
||||
[colors repository]: https://github.com/lite-xl/lite-xl-colors
|
||||
[LICENSE]: LICENSE
|
||||
[licenses]: licenses/licenses.md
|
||||
|
|
368
README_Amiga.md
368
README_Amiga.md
|
@ -1,368 +0,0 @@
|
|||
# Lite XL v2 for AmigaOS 4.1 FE & MorphOS 3
|
||||
|
||||
Lite XL is a lightweight text editor written in Lua and SDL2.
|
||||
|
||||
The port is not perfect and it might have issues here and there. It might
|
||||
crash from time to time, if there is a path problem, but overall it works
|
||||
pretty well. This is my daily editor for any kind of development.
|
||||
If it crashes on your system, try to delete to `.config` folder.
|
||||
|
||||
## Installation
|
||||
You can extract the Lite XL archive wherever you want and run the *lite*
|
||||
editor.
|
||||
|
||||
## Configuration folder
|
||||
This editor creates a `.config` folder where the configuration is saved, as
|
||||
well as plugins, themes etc.. By default this version uses the installation
|
||||
folder, but if you want to override it, you can create an ENV variable
|
||||
named `HOME` and set there your prefferable path.
|
||||
|
||||
You can check if there is one already set by executing the following command
|
||||
in a shell
|
||||
```
|
||||
GetEnv HOME
|
||||
```
|
||||
If there is one set, then you will see the path at the output.
|
||||
|
||||
Otherwise, you can set your home path be executing the following command.
|
||||
Change the path to the one of your preference.
|
||||
```
|
||||
SetEnv SAVE HOME "Sys:home/"
|
||||
```
|
||||
|
||||
## Addons
|
||||
### Colors
|
||||
Colors are lua files that set the color scheme of the editor. There are
|
||||
light and dark themes for you to choose.
|
||||
|
||||
To install and use them you have to copy the ones you would like from
|
||||
`addons/colors/light` or `addons/colors/dark` into the folder
|
||||
`.config/lite-xl/colors/`. Don't add light or dark folders. Just copy the
|
||||
.lua files in there.
|
||||
|
||||
Then you have to start Lite XL and open your configuration by clicking
|
||||
at the cog icon at the toolbar (bottom left sixth icon). Go at the line
|
||||
that looks like below
|
||||
```
|
||||
-- core.reload_module("colors.summer")
|
||||
```
|
||||
and change the `summer` with the name of your color theme. Also, remove
|
||||
the two dashes `--` at the start of the line and save the file. If you
|
||||
did everything right, the color schema should change instantly.
|
||||
|
||||
The themes can also be found at
|
||||
https://github.com/lite-xl/lite-xl-colors
|
||||
|
||||
### Plugins
|
||||
LiteXL is able to use plugins to extend its features. Those can be found
|
||||
at https://github.com/lite-xl/lite-xl-plugins and other websites. Not all
|
||||
of them will work fine on AmigaOS 4 or MorphOS, because of missing
|
||||
dependencies or filesystem issues.
|
||||
|
||||
To make it easier for you, I gathered some of the plugins that are working
|
||||
well, and I included them under `addons/plugins`. For you to install the
|
||||
ones you would like to use, you have to copy the `.lua` files into the
|
||||
folder `.config/lite-xl/plugins/` and restart the editor.
|
||||
|
||||
Please, choose wisely, because adding all the plugins might make the editor
|
||||
slower on your system. I would recommend you add only those that you really
|
||||
need.
|
||||
|
||||
The included plugins are the following:
|
||||
|
||||
**autoinsert**
|
||||
Automatically inserts closing brackets and quotes. Also allows selected
|
||||
text to be wrapped with brackets or quotes.
|
||||
|
||||
**autosaveonfocuslost**
|
||||
Automatically saves files that were changed when the main window loses
|
||||
focus by switching to another application
|
||||
|
||||
**autowrap**
|
||||
Automatically hardwraps lines when typing
|
||||
|
||||
**bigclock**
|
||||
Shows the current time and date in a view with large text
|
||||
|
||||
**bracketmatch**
|
||||
Underlines matching pair for bracket under the caret
|
||||
|
||||
**codesets**
|
||||
This plugin uses the codesets.library on AmigaOS 4 and the
|
||||
charsets.library on MorphOS to translate ISO encoded files to unicode
|
||||
and vice-versa. When this is enabled new menu items are added to
|
||||
load/save the code with a different encoding. Also there is a new
|
||||
section at the status bar that show the file encoding.
|
||||
This plugin is **EXPERIMENTAL** and heavily inspired from the encoding
|
||||
plugin at https://github.com/jgmdev/lite-xl-encoding
|
||||
|
||||
**colorpreview**
|
||||
Underlays color values (eg. `#ff00ff` or `rgb(255, 0, 255)`) with their
|
||||
resultant color.
|
||||
|
||||
**custom_caret**
|
||||
Customize the caret in the editor setting it to *underline*, *block* or
|
||||
*line* at the init.lua file in your config folder.
|
||||
For example add:
|
||||
`config.plugins.custom_caret.shape = "block"`
|
||||
|
||||
**EditorConfig**
|
||||
EditorConfig (https://editorconfig.org/) implementation for Lite XL
|
||||
|
||||
**ephemeral_tabs**
|
||||
Preview tabs. Opening a doc will replace the contents of the preview tab.
|
||||
Marks tabs as non-preview on any change or tab double clicking.
|
||||
|
||||
**ghmarkdown**
|
||||
Opens a preview of the current markdown file in a browser window.
|
||||
On AmigaOS 4 it uses *urlopen* and on MorphOS it uses *openurl* to load
|
||||
the generated html in the browser. It requires a GitHub application token
|
||||
because it uses its Rest API. Add it at the init.lua file in your config
|
||||
folder like below:
|
||||
`config.plugins.ghmarkdown.github_token = "<token here>"`
|
||||
|
||||
**indentguide**
|
||||
Adds indent guides
|
||||
|
||||
**language_guide**
|
||||
Syntax for the AmigaGuide scripting language
|
||||
|
||||
**language_hws**
|
||||
Syntax for the Hollywood language
|
||||
|
||||
**language_make**
|
||||
Syntax for the Make build system language
|
||||
|
||||
**language_sh**
|
||||
Syntax for shell scripting language
|
||||
|
||||
**lfautoinsert**
|
||||
Automatically inserts indentation and closing bracket/text after newline
|
||||
|
||||
**markers**
|
||||
Add markers to docs and jump between them quickly
|
||||
|
||||
**minimap**
|
||||
Shows a minimap on the right-hand side of the docview. Please note that
|
||||
this plugin will make the editor slower on file loading and scrolling.
|
||||
|
||||
**navigate**
|
||||
Allows moving back and forward between document positions, reducing the
|
||||
amount of scrolling
|
||||
|
||||
**nonicons**
|
||||
File icons set for TreeView. Download TTF font to your config/fonts
|
||||
folder from https://github.com/yamatsum/nonicons/tree/master/dist
|
||||
|
||||
**opacity**
|
||||
Change the opaqueness/transparency of lite-xl using LAmiga+mousewheel
|
||||
or a command.
|
||||
|
||||
**openfilelocation**
|
||||
Opens the parent directory of the current file in the file manager
|
||||
|
||||
**rainbowparen**
|
||||
Show nesting of parentheses with rainbow colours
|
||||
|
||||
**restoretabs**
|
||||
Keep a list of recently closed tabs, and restore the tab in order on
|
||||
cntrl+shift+t.
|
||||
|
||||
**select_colorscheme**
|
||||
Select a color theme, like VScode, Sublime Text.
|
||||
(plugin saves changes)
|
||||
|
||||
**selectionhighlight**
|
||||
Highlights regions of code that match the current selection
|
||||
|
||||
**smallclock**
|
||||
It adds a small clock at the bottom right corner.
|
||||
|
||||
**tetris**
|
||||
Play Tetris inside Lite XL.
|
||||
|
||||
## Tips and tricks
|
||||
### Transitions
|
||||
|
||||
If you want to disable the transitions and make the editor faster,
|
||||
open your configuration file by clicking at the cog icon at the toolbar
|
||||
(bottom left, 6th icon) and add the following line at the end of the file,
|
||||
and then save it. You might need to restart your editor.
|
||||
|
||||
```
|
||||
config.transitions = false
|
||||
```
|
||||
|
||||
### Hide files from the file list
|
||||
|
||||
If you would like to hide files or whole folder from the left side bar list,
|
||||
open your configuration by clicking at the cog icon at the toolbar
|
||||
(bottom left sixth icon) and add the followline at the end of the file and
|
||||
save it. This hides all the files that start with a dot, and all the `.info`
|
||||
files. You might need to restart your editor.
|
||||
|
||||
```
|
||||
config.ignore_files = {"^%.", "%.info$"}
|
||||
```
|
||||
|
||||
You can add as many rules as you want in there, to hide files or
|
||||
folders, as you like.
|
||||
|
||||
## I would like to thank
|
||||
|
||||
- IconDesigner for the proper glow icons that are included in the release
|
||||
- Capehill for his tireless work on SDL port for AmigaOS 4.1 FE
|
||||
- Michael Trebilcock for his port on liblua
|
||||
- Bruno "BeWorld" Peloille for his great work on porting SDL to MorphOS
|
||||
and for his valuable help
|
||||
- Lite XL original team for being helpful and providing info
|
||||
|
||||
Without all the above Lite XL would not be possible
|
||||
|
||||
## Support
|
||||
If you enjoy what I am doing and would like to keep me up during the night,
|
||||
please consider to buy me a coffee at:
|
||||
https://ko-fi.com/walkero
|
||||
|
||||
## Known issues
|
||||
You can find the known issues at
|
||||
https://git.walkero.gr/walkero/lite-xl/issues
|
||||
|
||||
# Changelog
|
||||
|
||||
## [2.1.4r1] - 2024-05-23
|
||||
### Added
|
||||
- Added the ability to open files and folders by drag 'n drop them on the
|
||||
LiteXL icon when this is on the AmiDock (AmigaOS4) / Panel (MorphOS)
|
||||
|
||||
### Updated
|
||||
- Updated the code to the upstream 2.1.4 release
|
||||
|
||||
### Fixed
|
||||
- Fix opening files from the root of a device
|
||||
|
||||
## [2.1.3r1] - 2024-03-09
|
||||
### Added
|
||||
- Added AmiUpdate support
|
||||
- Added the Tetris plugin
|
||||
|
||||
### Updated
|
||||
- Updated the code to the upstream 2.1.3 release
|
||||
- Compiled with SDL 2.30.0 that supports editing with ISO encodings, other
|
||||
than English. Now the editor should be able to support any language
|
||||
and in conjuction with the codesets plugin be able to make text
|
||||
encodings conversions
|
||||
- Now the codesets plugin supports MorphOS 3.18 and above
|
||||
|
||||
### Changed
|
||||
- Changed the way the "Open in system" option executes the WBRun command
|
||||
in AmigaOS 4, with a more secure way
|
||||
- Did a lot of code cleanup in sync with the upstream, and parts of code
|
||||
that were left over
|
||||
- Compiled with pcre2 10.42 (MorphOS version only)
|
||||
|
||||
### Fixed
|
||||
- I did a lot of changes on path manipulation and usage, fixing scanning
|
||||
the root of a partition or an assign path
|
||||
- Fixed an error with the codesets plugin, where an empty file could
|
||||
not be opened
|
||||
- Improved the folder suggestions when opening projects or changing paths.
|
||||
Now even the root folders of a partition are presented
|
||||
- Fixed ghmarkdown plugin, but now requires a GitHub token to be provided
|
||||
|
||||
## [2.1.2r1] - 2023-12-19
|
||||
### Added
|
||||
- Added the new experimental codesets plugin (AmigaOS4 version only).
|
||||
MorphOS version is in WIP
|
||||
|
||||
### Changed
|
||||
- Synced with the latest upstream v2.1.2 code
|
||||
- Compiled with gcc 11.3.0
|
||||
- Compiled with SDL 2.28.4
|
||||
- Compiled with libfreetype 2.13.x
|
||||
- Compiled with lua 5.4.6
|
||||
- Compiled with linpng 1.6.40 (AmigaOS4 version only)
|
||||
- Compiled with libz 1.2.13 (AmigaOS4 version only)
|
||||
|
||||
## [2.1.1r2] - 2022-05-14
|
||||
### Changed
|
||||
- Compiled with latest SDL v2.26.5-rc2
|
||||
|
||||
## [2.1.1r1] - 2022-01-29
|
||||
### Changed
|
||||
- Binary name changed to lite-xl
|
||||
- Updated the colour themes and the plugins that are included in the release
|
||||
- Compiled with latest SDL 2.26
|
||||
- Compiled with gcc 11
|
||||
- Synced the code with the upstream master branch at 8th January 2023
|
||||
|
||||
### Fixed
|
||||
- Set the default locale on AmigaOS 4, so as to fix some issues with decimal
|
||||
numbers
|
||||
|
||||
## [2.1.0r1] - 2022-10-10
|
||||
### Added
|
||||
- This version of LiteXL recognises changes that are done outside the editor
|
||||
in files and folders, and updates the items when it gets focus again.
|
||||
|
||||
### Changed
|
||||
- Synced the code with the latest upstream master branch, which means that
|
||||
this version is based on the latest available source
|
||||
- Now the plugins need to be version 3. The older versions will not work.
|
||||
All the included plugins are updated to the latest available version.
|
||||
- Compiled with SDL 2.24
|
||||
- Compiled with Lua 5.4
|
||||
|
||||
### Fixed
|
||||
- Fixed regex issues with some plugins
|
||||
- Fixed "Open in System" on AmigaOS 4 and MorphOS. When you right click
|
||||
at a file or a folder at the treeview at the left side, then depending
|
||||
the type of the item opens on Workbench. A folder opens in a list view
|
||||
and a file opens with its default tool
|
||||
- Fixed markdown preview on MorphOS. Now, it calls openurl with the html
|
||||
file (#20)
|
||||
- Fixed locale issues on MorphOS (again), since the previous fix didn't
|
||||
actually fixed the problem
|
||||
|
||||
## [2.0.3r3] - 2022-09-26
|
||||
### Added
|
||||
- Added plugin for AmigaGuide files
|
||||
- Added plugin for Hollywood files
|
||||
|
||||
### Fixed
|
||||
- Fixed non existing path crashes on OS4 and MorphOS
|
||||
- Fixed editor refresh whenever init.lua is changed, no matter the working
|
||||
folder
|
||||
- Fixed an issue when the user added a directory in the project that
|
||||
already existed
|
||||
- Fixed locale issue on start for MorphOS. Now it should start just fine
|
||||
no matter what locale the user has on his system.
|
||||
- Fixed "Find" on MorphOS that was not working (shortcut CTRL+F)
|
||||
- If the user selects to change the project folder and inserts Sys: or any
|
||||
partition name, the included folders will be listed as suggestions
|
||||
|
||||
### Changed
|
||||
- Removed linking with unix on OS4 build
|
||||
- Makefiles updated
|
||||
|
||||
## [2.0.3r2] - 2022-06-18
|
||||
### Added
|
||||
- First public MorphOS version released
|
||||
|
||||
### Changed
|
||||
- Merged source code for both AmigaOS 4 and MorphOS
|
||||
- Moved the declaration of the $VER and $STACK for the AmigaOS 4 version,
|
||||
so to happen only once (reported by capehill)
|
||||
|
||||
### Fixed
|
||||
- Fixed the usage of NumPad (reported by root)
|
||||
|
||||
## [2.0.3r1] - 2022-03-30
|
||||
### Changed
|
||||
- Applied all the necessary changes to make it run under AmigaOS 4.1 FE
|
||||
- Fixes and changes
|
||||
|
||||
# Disclaimer
|
||||
YOU MAY USE IT AT YOUR OWN RISK!
|
||||
I will not be held responsible for any data loss or problems you might get
|
||||
by using this software.
|
Binary file not shown.
|
@ -0,0 +1,159 @@
|
|||
# Lite XL v2 for AmigaOS 4.1 FE
|
||||
|
||||
Lite XL is a lightweight text editor written in Lua.
|
||||
|
||||
## Installation
|
||||
You can extract the Lite XL archive wherever you want and run the *lite*
|
||||
editor.
|
||||
|
||||
## Configuration folder
|
||||
This editor creates a `.config` folder where the configuration is saved, as
|
||||
well as plugins, themes etc.. By default this AmigaOS 4.1 FE version uses the
|
||||
executable folder, but if you want to ovveride it, create an ENV variable
|
||||
named `HOME` and set there your path.
|
||||
|
||||
You can check if there is one already set by executing the following command
|
||||
in a shell
|
||||
```
|
||||
GetEnv HOME
|
||||
```
|
||||
If there is one set, then you will see the path at the output.
|
||||
|
||||
Otherwise, you can set your home path be executing the following command.
|
||||
Change the path to the one of your preference.
|
||||
```
|
||||
SetEnv SAVE HOME "Sys:home/"
|
||||
```
|
||||
|
||||
## Addons
|
||||
### Colors
|
||||
Colors are lua files that set the color scheme of the editor. There are
|
||||
light and dark themes for you to choose.
|
||||
|
||||
To install and use them you have to copy the ones you would like from
|
||||
`addons/colors/light` or `addons/colors/dark` into the folder
|
||||
`.config/lite-xl/colors/`. Don't add light or dark folders. Just copy the
|
||||
.lua files in there.
|
||||
|
||||
Then you have to start Lite XL and open your configuration by clicking
|
||||
at the cog icon at the toolbar (bottom left sixth icon). Go at the line
|
||||
that looks like below
|
||||
```
|
||||
-- core.reload_module("colors.summer")
|
||||
```
|
||||
and change the `summer` with the name of your color theme. Also, remove
|
||||
the two dashes `--` at the start of the line and save the file. If you
|
||||
did everything right, the color schema should change instantly.
|
||||
|
||||
The themes can also be found at
|
||||
https://github.com/lite-xl/lite-xl-colors
|
||||
|
||||
### Plugins
|
||||
The Lite XL that you are using on AmigaOS 4 is based on version 1.16.12
|
||||
and not the latest version that is available by the development team.
|
||||
This means that the latest plugins are not working at all or need some
|
||||
modifications to work.
|
||||
|
||||
To make it easier for you, I gathered some of the plugins that are working
|
||||
well, and I included them at the `addons/plugins`. For you to install the
|
||||
ones you would like to use, you have to copy the `.lua` files into the
|
||||
folder `.config/lite-xl/plugins/` and restart the editor.
|
||||
|
||||
The included plugins are the following:
|
||||
|
||||
**autoinsert**
|
||||
Automatically inserts closing brackets and quotes. Also allows selected
|
||||
text to be wrapped with brackets or quotes.
|
||||
|
||||
**autowrap**
|
||||
Automatically hardwraps lines when typing
|
||||
|
||||
**bigclock**
|
||||
Shows the current time and date in a view with large text
|
||||
|
||||
**bracketmatch**
|
||||
Underlines matching pair for bracket under the caret
|
||||
|
||||
**colorpreview**
|
||||
Underlays color values (eg. `#ff00ff` or `rgb(255, 0, 255)`) with their
|
||||
resultant color.
|
||||
|
||||
**eofnewline-xl**
|
||||
Make sure the file ends with one blank line.
|
||||
|
||||
**ephemeral_tabs**
|
||||
Preview tabs. Opening a doc will replace the contents of the preview tab.
|
||||
Marks tabs as non-preview on any change or tab double clicking.
|
||||
|
||||
**ghmarkdown**
|
||||
Opens a preview of the current markdown file in a browser window
|
||||
|
||||
**indentguide**
|
||||
Adds indent guides
|
||||
|
||||
**language_make**
|
||||
Syntax for the Make build system language
|
||||
|
||||
**language_sh**
|
||||
Syntax for shell scripting language
|
||||
|
||||
**lfautoinsert**
|
||||
Automatically inserts indentation and closing bracket/text after newline
|
||||
|
||||
**markers**
|
||||
Add markers to docs and jump between them quickly
|
||||
|
||||
**navigate**
|
||||
Allows moving back and forward between document positions, reducing the
|
||||
amount of scrolling
|
||||
|
||||
**rainbowparen**
|
||||
Show nesting of parentheses with rainbow colours
|
||||
|
||||
**restoretabs**
|
||||
Keep a list of recently closed tabs, and restore the tab in order on
|
||||
cntrl+shift+t.
|
||||
|
||||
**selectionhighlight**
|
||||
Highlights regions of code that match the current selection
|
||||
|
||||
## Tips and tricks
|
||||
|
||||
### Transitions
|
||||
|
||||
If you want to disable the transitions and make the scrolling a little faster,
|
||||
open your configuration by clicking at the cog icon at the toolbar
|
||||
(bottom left sixth icon) and add the followline at the end of the file and
|
||||
save it.
|
||||
|
||||
```
|
||||
config.transitions = false
|
||||
```
|
||||
|
||||
### Hide files from the file list
|
||||
|
||||
If you would like to hide files or whole folder from the left side bar list,
|
||||
open your configuration by clicking at the cog icon at the toolbar
|
||||
(bottom left sixth icon) and add the followline at the end of the file and
|
||||
save it. This hides all the files that start with a dot, and all the `.info`
|
||||
files.
|
||||
|
||||
```
|
||||
config.ignore_files = {"^%.", "%.info$"}
|
||||
```
|
||||
|
||||
You can add as many rules as you want in there, to hide fore files or
|
||||
folders, as you like.
|
||||
|
||||
|
||||
## Know issues
|
||||
You can find the known issues at
|
||||
https://git.walkero.gr/walkero/lite-xl/issues
|
||||
|
||||
|
||||
# Changelog
|
||||
|
||||
## [2.0.3.1] - 2022-01-18
|
||||
### Changed
|
||||
- Applied all the necessary changes to make it run under AmigaOS 4.1 FE
|
||||
|
Binary file not shown.
|
@ -13,36 +13,32 @@ show_help() {
|
|||
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 " --cross-platform PLATFORM The platform to cross compile for."
|
||||
echo " --cross-arch ARCH The architecture to cross compile for."
|
||||
echo " --debug Debug this script."
|
||||
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 " --cross-file CROSS_FILE The cross file used for compiling."
|
||||
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 dmgbuild."
|
||||
echo "-I --innosetup Create an InnoSetup installer (Windows only)."
|
||||
echo "-r --release Compile in release mode."
|
||||
echo "-S --source Create a source code package,"
|
||||
echo " including subprojects dependencies."
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -62,13 +58,6 @@ main() {
|
|||
local innosetup
|
||||
local portable
|
||||
local pgo
|
||||
local release
|
||||
local cross_platform
|
||||
local cross_platform_option=()
|
||||
local cross_arch
|
||||
local cross_arch_option=()
|
||||
local cross_file
|
||||
local cross_file_option=()
|
||||
|
||||
for i in "$@"; do
|
||||
case $i in
|
||||
|
@ -120,10 +109,6 @@ main() {
|
|||
portable="--portable"
|
||||
shift
|
||||
;;
|
||||
-r|--release)
|
||||
release="--release"
|
||||
shift
|
||||
;;
|
||||
-S|--source)
|
||||
source="--source"
|
||||
shift
|
||||
|
@ -132,21 +117,6 @@ main() {
|
|||
pgo="--pgo"
|
||||
shift
|
||||
;;
|
||||
--cross-platform)
|
||||
cross_platform="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
--cross-arch)
|
||||
cross_arch="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
--cross-file)
|
||||
cross_file="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
--debug)
|
||||
debug="--debug"
|
||||
set -x
|
||||
|
@ -167,23 +137,14 @@ main() {
|
|||
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
|
||||
if [[ -n $cross_platform ]]; then cross_platform_option=("--cross-platform" "${cross_platform}"); fi
|
||||
if [[ -n $cross_arch ]]; then cross_arch_option=("--cross-arch" "${cross_arch}"); fi
|
||||
if [[ -n $cross_file ]]; then cross_file_option=("--cross-file" "${cross_file}"); fi
|
||||
|
||||
|
||||
|
||||
source scripts/build.sh \
|
||||
${build_dir_option[@]} \
|
||||
${prefix_option[@]} \
|
||||
${cross_platform_option[@]} \
|
||||
${cross_arch_option[@]} \
|
||||
${cross_file_option[@]} \
|
||||
$debug \
|
||||
$force_fallback \
|
||||
$bundle \
|
||||
$portable \
|
||||
$release \
|
||||
$pgo
|
||||
|
||||
source scripts/package.sh \
|
||||
|
@ -191,15 +152,12 @@ main() {
|
|||
${dest_dir_option[@]} \
|
||||
${prefix_option[@]} \
|
||||
${version_option[@]} \
|
||||
${cross_platform_option[@]} \
|
||||
${cross_arch_option[@]} \
|
||||
--binary \
|
||||
--addons \
|
||||
$debug \
|
||||
$appimage \
|
||||
$dmg \
|
||||
$innosetup \
|
||||
$release \
|
||||
$source
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
CC="${CC:-gcc}"
|
||||
CXX="${CXX:-g++}"
|
||||
CFLAGS=
|
||||
CXXFLAGS=
|
||||
LDFLAGS=
|
||||
BUILD_TYPE=Release
|
||||
|
||||
packages=(pcre2 freetype2 sdl2 lua)
|
1363
changelog.md
1363
changelog.md
File diff suppressed because it is too large
Load Diff
|
@ -1,46 +0,0 @@
|
|||
local style = require "core.style"
|
||||
local common = require "core.common"
|
||||
|
||||
style.background = { common.color "#2e2e32" } -- Docview
|
||||
style.background2 = { common.color "#252529" } -- Treeview
|
||||
style.background3 = { common.color "#252529" } -- Command view
|
||||
style.text = { common.color "#97979c" }
|
||||
style.caret = { common.color "#93DDFA" }
|
||||
style.accent = { common.color "#e1e1e6" }
|
||||
-- style.dim - text color for nonactive tabs, tabs divider, prefix in log and
|
||||
-- search result, hotkeys for context menu and command view
|
||||
style.dim = { common.color "#525257" }
|
||||
style.divider = { common.color "#202024" } -- Line between nodes
|
||||
style.selection = { common.color "#48484f" }
|
||||
style.line_number = { common.color "#525259" }
|
||||
style.line_number2 = { common.color "#83838f" } -- With cursor
|
||||
style.line_highlight = { common.color "#343438" }
|
||||
style.scrollbar = { common.color "#414146" }
|
||||
style.scrollbar2 = { common.color "#4b4b52" } -- Hovered
|
||||
style.scrollbar_track = { common.color "#252529" }
|
||||
style.nagbar = { common.color "#FF0000" }
|
||||
style.nagbar_text = { common.color "#FFFFFF" }
|
||||
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
|
||||
style.drag_overlay = { common.color "rgba(255,255,255,0.1)" }
|
||||
style.drag_overlay_tab = { common.color "#93DDFA" }
|
||||
style.good = { common.color "#72b886" }
|
||||
style.warn = { common.color "#FFA94D" }
|
||||
style.error = { common.color "#FF3333" }
|
||||
style.modified = { common.color "#1c7c9c" }
|
||||
|
||||
style.syntax["normal"] = { common.color "#e1e1e6" }
|
||||
style.syntax["symbol"] = { common.color "#e1e1e6" }
|
||||
style.syntax["comment"] = { common.color "#676b6f" }
|
||||
style.syntax["keyword"] = { common.color "#E58AC9" } -- local function end if case
|
||||
style.syntax["keyword2"] = { common.color "#F77483" } -- self int float
|
||||
style.syntax["number"] = { common.color "#FFA94D" }
|
||||
style.syntax["literal"] = { common.color "#FFA94D" } -- true false nil
|
||||
style.syntax["string"] = { common.color "#f7c95c" }
|
||||
style.syntax["operator"] = { common.color "#93DDFA" } -- = + - / < >
|
||||
style.syntax["function"] = { common.color "#93DDFA" }
|
||||
|
||||
style.log["INFO"] = { icon = "i", color = style.text }
|
||||
style.log["WARN"] = { icon = "!", color = style.warn }
|
||||
style.log["ERROR"] = { icon = "!", color = style.error }
|
||||
|
||||
return style
|
|
@ -1,36 +0,0 @@
|
|||
local bit = {}
|
||||
|
||||
local LUA_NBITS = 32
|
||||
local ALLONES = (~(((~0) << (LUA_NBITS - 1)) << 1))
|
||||
|
||||
local function trim(x)
|
||||
return (x & ALLONES)
|
||||
end
|
||||
|
||||
local function mask(n)
|
||||
return (~((ALLONES << 1) << ((n) - 1)))
|
||||
end
|
||||
|
||||
local function check_args(field, width)
|
||||
assert(field >= 0, "field cannot be negative")
|
||||
assert(width > 0, "width must be positive")
|
||||
assert(field + width < LUA_NBITS and field + width >= 0,
|
||||
"trying to access non-existent bits")
|
||||
end
|
||||
|
||||
function bit.extract(n, field, width)
|
||||
local w = width or 1
|
||||
check_args(field, w)
|
||||
local m = trim(n)
|
||||
return m >> field & mask(w)
|
||||
end
|
||||
|
||||
function bit.replace(n, v, field, width)
|
||||
local w = width or 1
|
||||
check_args(field, w)
|
||||
local m = trim(n)
|
||||
local x = v & mask(width);
|
||||
return m & ~(mask(w) << field) | (x << field)
|
||||
end
|
||||
|
||||
return bit
|
|
@ -1,93 +1,22 @@
|
|||
local core = require "core"
|
||||
local command = {}
|
||||
|
||||
---A predicate function accepts arguments from `command.perform()` and evaluates to a boolean. </br>
|
||||
---If the function returns true, then the function associated with the command is executed.
|
||||
---
|
||||
---The predicate function can also return other values after the boolean, which will
|
||||
---be passed into the function associated with the command.
|
||||
---@alias core.command.predicate_function fun(...: any): boolean, ...
|
||||
|
||||
---A predicate is a string, an Object or a function, that is used to determine
|
||||
---whether a command should be executed.
|
||||
---
|
||||
---If the predicate is a string, it is resolved into an `Object` via `require()`
|
||||
---and checked against the active view with `Object:extends()`. </br>
|
||||
---For example, `"core.docview"` will match any view that inherits from `DocView`. </br>
|
||||
---A `!` can be appended to the predicate to strictly match the current view via `Object:is()`,
|
||||
---instead of matching any view that inherits the predicate.
|
||||
---
|
||||
---If the predicate is a table, it is checked against the active view with `Object:extends()`.
|
||||
---Strict matching via `Object:is()` is not available.
|
||||
---
|
||||
---If the predicate is a function, it must behave like a predicate function.
|
||||
---@see core.command.predicate_function
|
||||
---@alias core.command.predicate string|core.object|core.command.predicate_function
|
||||
|
||||
---A command is identified by a command name.
|
||||
---The command name contains a category and the name itself, separated by a colon (':').
|
||||
---
|
||||
---All commands should be in lowercase and should not contain whitespaces; instead
|
||||
---they should be replaced by a dash ('-').
|
||||
---@alias core.command.command_name string
|
||||
|
||||
---The predicate and its associated function.
|
||||
---@class core.command.command
|
||||
---@field predicate core.command.predicate_function
|
||||
---@field perform fun(...: any)
|
||||
|
||||
---@type { [string]: core.command.command }
|
||||
command.map = {}
|
||||
|
||||
---@type core.command.predicate_function
|
||||
local always_true = function() return true end
|
||||
|
||||
|
||||
---This function takes in a predicate and produces a predicate function
|
||||
---that is internally used to dispatch and execute commands.
|
||||
---
|
||||
---This function should not be called manually.
|
||||
---@see core.command.predicate
|
||||
---@param predicate core.command.predicate|nil If nil, the predicate always evaluates to true.
|
||||
---@return core.command.predicate_function
|
||||
function command.generate_predicate(predicate)
|
||||
function command.add(predicate, map)
|
||||
predicate = predicate or always_true
|
||||
local strict = false
|
||||
if type(predicate) == "string" then
|
||||
if predicate:match("!$") then
|
||||
strict = true
|
||||
predicate = predicate:gsub("!$", "")
|
||||
end
|
||||
predicate = require(predicate)
|
||||
end
|
||||
if type(predicate) == "table" then
|
||||
local class = predicate
|
||||
if not strict then
|
||||
predicate = function(...) return core.active_view:extends(class), core.active_view, ... end
|
||||
else
|
||||
predicate = function(...) return core.active_view:is(class), core.active_view, ... end
|
||||
end
|
||||
predicate = function() return core.active_view:is(class) end
|
||||
end
|
||||
---@cast predicate core.command.predicate_function
|
||||
return predicate
|
||||
end
|
||||
|
||||
|
||||
---Adds commands to the map.
|
||||
---
|
||||
---The function accepts a table containing a list of commands
|
||||
---and their functions. </br>
|
||||
---If a command already exists, it will be replaced.
|
||||
---@see core.command.predicate
|
||||
---@see core.command.command_name
|
||||
---@param predicate core.command.predicate
|
||||
---@param map { [core.command.command_name]: fun(...: any) }
|
||||
function command.add(predicate, map)
|
||||
predicate = command.generate_predicate(predicate)
|
||||
for name, fn in pairs(map) do
|
||||
if command.map[name] then
|
||||
core.log_quiet("Replacing existing command \"%s\"", name)
|
||||
end
|
||||
assert(not command.map[name], "command already exists: " .. name)
|
||||
command.map[name] = { predicate = predicate, perform = fn }
|
||||
end
|
||||
end
|
||||
|
@ -97,85 +26,45 @@ local function capitalize_first(str)
|
|||
return str:sub(1, 1):upper() .. str:sub(2)
|
||||
end
|
||||
|
||||
---Prettifies the command name.
|
||||
---
|
||||
---This function adds a space between the colon and the command name,
|
||||
---replaces dashes with spaces and capitalizes the command appropriately.
|
||||
---@see core.command.command_name
|
||||
---@param name core.command.command_name
|
||||
---@return string
|
||||
function command.prettify_name(name)
|
||||
---@diagnostic disable-next-line: redundant-return-value
|
||||
return name:gsub(":", ": "):gsub("-", " "):gsub("%S+", capitalize_first)
|
||||
end
|
||||
|
||||
|
||||
---Returns all the commands that can be executed (their predicates evaluate to true).
|
||||
---@return core.command.command_name[]
|
||||
function command.get_all_valid()
|
||||
local res = {}
|
||||
local memoized_predicates = {}
|
||||
for name, cmd in pairs(command.map) do
|
||||
if memoized_predicates[cmd.predicate] == nil then
|
||||
memoized_predicates[cmd.predicate] = cmd.predicate()
|
||||
end
|
||||
if memoized_predicates[cmd.predicate] then
|
||||
if cmd.predicate() then
|
||||
table.insert(res, name)
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
---Checks whether a command can be executed (its predicate evaluates to true).
|
||||
---@param name core.command.command_name
|
||||
---@param ... any
|
||||
---@return boolean
|
||||
function command.is_valid(name, ...)
|
||||
return command.map[name] and command.map[name].predicate(...)
|
||||
end
|
||||
|
||||
local function perform(name, ...)
|
||||
local cmd = command.map[name]
|
||||
if not cmd then return false end
|
||||
local res = { cmd.predicate(...) }
|
||||
if table.remove(res, 1) then
|
||||
if #res > 0 then
|
||||
-- send values returned from predicate
|
||||
cmd.perform(table.unpack(res))
|
||||
else
|
||||
-- send original parameters
|
||||
cmd.perform(...)
|
||||
end
|
||||
if cmd and cmd.predicate(...) then
|
||||
cmd.perform(...)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
---Performs a command.
|
||||
---
|
||||
---The arguments passed into this function are forwarded to the predicate function. </br>
|
||||
---If the predicate function returns more than 1 value, the other values are passed
|
||||
---to the command.
|
||||
---
|
||||
---Otherwise, the arguments passed into this function are passed directly
|
||||
---to the command.
|
||||
---@see core.command.predicate
|
||||
---@see core.command.predicate_function
|
||||
---@param name core.command.command_name
|
||||
---@param ... any
|
||||
---@return boolean # true if the command is performed successfully.
|
||||
function command.perform(name, ...)
|
||||
local ok, res = core.try(perform, name, ...)
|
||||
function command.perform(...)
|
||||
local ok, res = core.try(perform, ...)
|
||||
return not ok or res
|
||||
end
|
||||
|
||||
|
||||
---Inserts the default commands for Lite XL into the map.
|
||||
function command.add_defaults()
|
||||
local reg = {
|
||||
"core", "root", "command", "doc", "findreplace",
|
||||
"files", "dialog", "log", "statusbar"
|
||||
"files", "drawwhitespace", "dialog"
|
||||
}
|
||||
for _, name in ipairs(reg) do
|
||||
require("core.commands." .. name)
|
||||
|
|
|
@ -2,23 +2,23 @@ local core = require "core"
|
|||
local command = require "core.command"
|
||||
|
||||
command.add("core.commandview", {
|
||||
["command:submit"] = function(active_view)
|
||||
active_view:submit()
|
||||
["command:submit"] = function()
|
||||
core.active_view:submit()
|
||||
end,
|
||||
|
||||
["command:complete"] = function(active_view)
|
||||
active_view:complete()
|
||||
["command:complete"] = function()
|
||||
core.active_view:complete()
|
||||
end,
|
||||
|
||||
["command:escape"] = function(active_view)
|
||||
active_view:exit()
|
||||
["command:escape"] = function()
|
||||
core.active_view:exit()
|
||||
end,
|
||||
|
||||
["command:select-previous"] = function(active_view)
|
||||
active_view:move_suggestion_idx(1)
|
||||
["command:select-previous"] = function()
|
||||
core.active_view:move_suggestion_idx(1)
|
||||
end,
|
||||
|
||||
["command:select-next"] = function(active_view)
|
||||
active_view:move_suggestion_idx(-1)
|
||||
["command:select-next"] = function()
|
||||
core.active_view:move_suggestion_idx(-1)
|
||||
end,
|
||||
})
|
||||
|
|
|
@ -10,18 +10,8 @@ local restore_title_view = false
|
|||
|
||||
local function suggest_directory(text)
|
||||
text = common.home_expand(text)
|
||||
local basedir = common.dirname(core.project_dir)
|
||||
return common.home_encode_list((basedir and text == basedir .. PATHSEP or text == "") and
|
||||
core.recent_projects or common.dir_path_suggest(text))
|
||||
end
|
||||
|
||||
local function check_directory_path(path)
|
||||
local abs_path = system.absolute_path(path)
|
||||
local info = abs_path and system.get_file_info(abs_path)
|
||||
if not info or info.type ~= 'dir' then
|
||||
return nil
|
||||
end
|
||||
return abs_path
|
||||
return common.home_encode_list((text == "" or text == common.home_expand(common.dirname(core.project_dir)))
|
||||
and core.recent_projects or common.dir_path_suggest(text))
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
|
@ -48,42 +38,36 @@ command.add(nil, {
|
|||
end,
|
||||
|
||||
["core:reload-module"] = function()
|
||||
core.command_view:enter("Reload Module", {
|
||||
submit = function(text, item)
|
||||
local text = item and item.text or text
|
||||
core.reload_module(text)
|
||||
core.log("Reloaded module %q", text)
|
||||
end,
|
||||
suggest = function(text)
|
||||
local items = {}
|
||||
for name in pairs(package.loaded) do
|
||||
table.insert(items, name)
|
||||
end
|
||||
return common.fuzzy_match(items, text)
|
||||
core.command_view:enter("Reload Module", function(text, item)
|
||||
local text = item and item.text or text
|
||||
core.reload_module(text)
|
||||
core.log("Reloaded module %q", text)
|
||||
end, function(text)
|
||||
local items = {}
|
||||
for name in pairs(package.loaded) do
|
||||
table.insert(items, name)
|
||||
end
|
||||
})
|
||||
return common.fuzzy_match(items, text)
|
||||
end)
|
||||
end,
|
||||
|
||||
["core:find-command"] = function()
|
||||
local commands = command.get_all_valid()
|
||||
core.command_view:enter("Do Command", {
|
||||
submit = function(text, item)
|
||||
if item then
|
||||
command.perform(item.command)
|
||||
end
|
||||
end,
|
||||
suggest = function(text)
|
||||
local res = common.fuzzy_match(commands, text)
|
||||
for i, name in ipairs(res) do
|
||||
res[i] = {
|
||||
text = command.prettify_name(name),
|
||||
info = keymap.get_binding(name),
|
||||
command = name,
|
||||
}
|
||||
end
|
||||
return res
|
||||
core.command_view:enter("Do Command", function(text, item)
|
||||
if item then
|
||||
command.perform(item.command)
|
||||
end
|
||||
})
|
||||
end, function(text)
|
||||
local res = common.fuzzy_match(commands, text)
|
||||
for i, name in ipairs(res) do
|
||||
res[i] = {
|
||||
text = command.prettify_name(name),
|
||||
info = keymap.get_binding(name),
|
||||
command = name,
|
||||
}
|
||||
end
|
||||
return res
|
||||
end)
|
||||
end,
|
||||
|
||||
["core:find-file"] = function()
|
||||
|
@ -97,72 +81,56 @@ command.add(nil, {
|
|||
table.insert(files, common.home_encode(path .. item.filename))
|
||||
end
|
||||
end
|
||||
core.command_view:enter("Open File From Project", {
|
||||
submit = function(text, item)
|
||||
text = item and item.text or text
|
||||
core.root_view:open_doc(core.open_doc(common.home_expand(text)))
|
||||
end,
|
||||
suggest = function(text)
|
||||
return common.fuzzy_match_with_recents(files, core.visited_files, text)
|
||||
end
|
||||
})
|
||||
core.command_view:enter("Open File From Project", function(text, item)
|
||||
text = item and item.text or text
|
||||
core.root_view:open_doc(core.open_doc(common.home_expand(text)))
|
||||
end, function(text)
|
||||
return common.fuzzy_match_with_recents(files, core.visited_files, text)
|
||||
end)
|
||||
end,
|
||||
|
||||
["core:new-doc"] = function()
|
||||
core.root_view:open_doc(core.open_doc())
|
||||
end,
|
||||
|
||||
["core:new-named-doc"] = function()
|
||||
core.command_view:enter("File name", {
|
||||
submit = function(text)
|
||||
core.root_view:open_doc(core.open_doc(text))
|
||||
end
|
||||
})
|
||||
end,
|
||||
|
||||
["core:open-file"] = function()
|
||||
local view = core.active_view
|
||||
local text
|
||||
if view.doc and view.doc.abs_filename then
|
||||
local dirname, filename = view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
||||
if dirname then
|
||||
dirname = core.normalize_to_project_dir(dirname)
|
||||
text = dirname == core.project_dir and "" or common.home_encode(dirname) .. PATHSEP
|
||||
local text = dirname == core.project_dir and "" or common.home_encode(dirname) .. PATHSEP
|
||||
core.command_view:set_text(text)
|
||||
end
|
||||
end
|
||||
core.command_view:enter("Open File", {
|
||||
text = text,
|
||||
submit = function(text)
|
||||
local filename = system.absolute_path(common.home_expand(text))
|
||||
core.root_view:open_doc(core.open_doc(filename))
|
||||
end,
|
||||
suggest = function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||
end,
|
||||
validate = function(text)
|
||||
local filename = common.home_expand(text)
|
||||
local path_stat, err = system.get_file_info(filename)
|
||||
if err then
|
||||
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 %s, is a folder", text)
|
||||
else
|
||||
core.command_view:enter("Open File", function(text)
|
||||
local filename = system.absolute_path(common.home_expand(text))
|
||||
core.root_view:open_doc(core.open_doc(filename))
|
||||
end, function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||
end, nil, function(text)
|
||||
local filename = common.home_expand(text)
|
||||
local path_stat, err = system.get_file_info(filename)
|
||||
if err then
|
||||
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,
|
||||
})
|
||||
end
|
||||
core.error("Cannot open file %s: %s", text, err)
|
||||
elseif path_stat.type == 'dir' then
|
||||
core.error("Cannot open %s, is a folder", text)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end)
|
||||
end,
|
||||
|
||||
["core:open-log"] = function()
|
||||
local node = core.root_view:get_active_node_default()
|
||||
local node = core.root_view:get_active_node()
|
||||
node:add_view(LogView())
|
||||
end,
|
||||
|
||||
|
@ -173,79 +141,62 @@ command.add(nil, {
|
|||
end,
|
||||
|
||||
["core:open-project-module"] = function()
|
||||
if not system.get_file_info(".lite_project.lua") then
|
||||
core.try(core.write_init_project_module, ".lite_project.lua")
|
||||
local filename = ".lite_project.lua"
|
||||
if system.get_file_info(filename) then
|
||||
core.root_view:open_doc(core.open_doc(filename))
|
||||
else
|
||||
local doc = core.open_doc()
|
||||
core.root_view:open_doc(doc)
|
||||
doc:save(filename)
|
||||
end
|
||||
local doc = core.open_doc(".lite_project.lua")
|
||||
core.root_view:open_doc(doc)
|
||||
doc:save()
|
||||
end,
|
||||
|
||||
["core:change-project-folder"] = function()
|
||||
local dirname = common.dirname(core.project_dir)
|
||||
local text
|
||||
if dirname then
|
||||
text = common.basepath(common.home_encode(dirname))
|
||||
core.command_view:set_text(common.home_encode(dirname))
|
||||
end
|
||||
core.command_view:enter("Change Project Folder", {
|
||||
text = text,
|
||||
submit = function(text)
|
||||
local path = common.home_expand(text)
|
||||
local abs_path = check_directory_path(path)
|
||||
if not abs_path then
|
||||
core.error("Cannot open directory %q", path)
|
||||
return
|
||||
end
|
||||
if abs_path == core.project_dir then return end
|
||||
core.confirm_close_docs(core.docs, function(dirpath)
|
||||
core.open_folder_project(dirpath)
|
||||
end, abs_path)
|
||||
end,
|
||||
suggest = suggest_directory
|
||||
})
|
||||
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
|
||||
local path_stat = system.get_file_info(text)
|
||||
if not path_stat or path_stat.type ~= 'dir' then
|
||||
core.error("Cannot open folder %q", text)
|
||||
return
|
||||
end
|
||||
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)
|
||||
local text
|
||||
if dirname then
|
||||
text = common.home_encode(dirname) .. PATHSEP
|
||||
core.command_view:set_text(common.home_encode(dirname))
|
||||
end
|
||||
core.command_view:enter("Open Project", {
|
||||
text = text,
|
||||
submit = function(text)
|
||||
local path = common.home_expand(text)
|
||||
local abs_path = check_directory_path(path)
|
||||
if not abs_path then
|
||||
core.error("Cannot open directory %q", path)
|
||||
return
|
||||
end
|
||||
if abs_path == core.project_dir then
|
||||
core.error("Directory %q is currently opened", abs_path)
|
||||
return
|
||||
end
|
||||
system.exec(string.format("%q %q", EXEFILE, abs_path))
|
||||
end,
|
||||
suggest = suggest_directory
|
||||
})
|
||||
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)
|
||||
if not path_stat or path_stat.type ~= 'dir' then
|
||||
core.error("Cannot open folder %q", text)
|
||||
return
|
||||
end
|
||||
system.exec(string.format("%q %q", EXEFILE, text))
|
||||
end, suggest_directory)
|
||||
end,
|
||||
|
||||
["core:add-directory"] = function()
|
||||
core.command_view:enter("Add Directory", {
|
||||
submit = function(text)
|
||||
text = common.home_expand(text)
|
||||
local path_stat, err = system.get_file_info(text)
|
||||
if not path_stat then
|
||||
core.error("cannot open %q: %s", text, err)
|
||||
return
|
||||
elseif path_stat.type ~= 'dir' then
|
||||
core.error("%q is not a directory", text)
|
||||
return
|
||||
end
|
||||
core.add_project_directory(system.absolute_path(text))
|
||||
end,
|
||||
suggest = suggest_directory
|
||||
})
|
||||
core.command_view:enter("Add Directory", function(text)
|
||||
text = common.home_expand(text)
|
||||
local path_stat, err = system.get_file_info(text)
|
||||
if not path_stat then
|
||||
core.error("cannot open %q: %s", text, err)
|
||||
return
|
||||
elseif path_stat.type ~= 'dir' then
|
||||
core.error("%q is not a directory", text)
|
||||
return
|
||||
end
|
||||
core.add_project_directory(system.absolute_path(text))
|
||||
end, suggest_directory)
|
||||
end,
|
||||
|
||||
["core:remove-directory"] = function()
|
||||
|
@ -254,17 +205,14 @@ command.add(nil, {
|
|||
for i = n, 2, -1 do
|
||||
dir_list[n - i + 1] = core.project_directories[i].name
|
||||
end
|
||||
core.command_view:enter("Remove Directory", {
|
||||
submit = function(text, item)
|
||||
text = common.home_expand(item and item.text or text)
|
||||
if not core.remove_project_directory(text) then
|
||||
core.error("No directory %q to be removed", text)
|
||||
end
|
||||
end,
|
||||
suggest = function(text)
|
||||
text = common.home_expand(text)
|
||||
return common.home_encode_list(common.dir_list_suggest(text, dir_list))
|
||||
core.command_view:enter("Remove Directory", function(text, item)
|
||||
text = common.home_expand(item and item.text or text)
|
||||
if not core.remove_project_directory(text) then
|
||||
core.error("No directory %q to be removed", text)
|
||||
end
|
||||
})
|
||||
end, function(text)
|
||||
text = common.home_expand(text)
|
||||
return common.home_encode_list(common.dir_list_suggest(text, dir_list))
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
|
|
@ -3,25 +3,30 @@ local command = require "core.command"
|
|||
local common = require "core.common"
|
||||
|
||||
command.add("core.nagview", {
|
||||
["dialog:previous-entry"] = function(v)
|
||||
["dialog:previous-entry"] = function()
|
||||
local v = core.active_view
|
||||
local hover = v.hovered_item or 1
|
||||
v:change_hovered(hover == 1 and #v.options or hover - 1)
|
||||
end,
|
||||
["dialog:next-entry"] = function(v)
|
||||
["dialog:next-entry"] = function()
|
||||
local v = core.active_view
|
||||
local hover = v.hovered_item or 1
|
||||
v:change_hovered(hover == #v.options and 1 or hover + 1)
|
||||
end,
|
||||
["dialog:select-yes"] = function(v)
|
||||
["dialog:select-yes"] = function()
|
||||
local v = core.active_view
|
||||
if v ~= core.nag_view then return end
|
||||
v:change_hovered(common.find_index(v.options, "default_yes"))
|
||||
command.perform "dialog:select"
|
||||
end,
|
||||
["dialog:select-no"] = function(v)
|
||||
["dialog:select-no"] = function()
|
||||
local v = core.active_view
|
||||
if v ~= core.nag_view then return end
|
||||
v:change_hovered(common.find_index(v.options, "default_no"))
|
||||
command.perform "dialog:select"
|
||||
end,
|
||||
["dialog:select"] = function(v)
|
||||
["dialog:select"] = function()
|
||||
local v = core.active_view
|
||||
if v.hovered_item then
|
||||
v.on_selected(v.options[v.hovered_item])
|
||||
v:next()
|
||||
|
|
|
@ -3,9 +3,12 @@ local command = require "core.command"
|
|||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local translate = require "core.doc.translate"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local tokenizer = require "core.tokenizer"
|
||||
|
||||
|
||||
local function dv()
|
||||
return core.active_view
|
||||
end
|
||||
|
||||
|
||||
local function doc()
|
||||
|
@ -37,198 +40,51 @@ local function save(filename)
|
|||
filename = core.normalize_to_project_dir(filename)
|
||||
abs_filename = core.project_absolute_path(filename)
|
||||
end
|
||||
local ok, err = pcall(doc().save, doc(), filename, abs_filename)
|
||||
if ok then
|
||||
local saved_filename = doc().filename
|
||||
core.log("Saved \"%s\"", saved_filename)
|
||||
else
|
||||
core.error(err)
|
||||
core.nag_view:show("Saving failed", string.format("Couldn't save file \"%s\". Do you want to save to another location?", doc().filename), {
|
||||
{ text = "Yes", default_yes = true },
|
||||
{ text = "No", default_no = true }
|
||||
}, function(item)
|
||||
if item.text == "Yes" then
|
||||
core.add_thread(function()
|
||||
-- we need to run this in a thread because of the odd way the nagview is.
|
||||
command.perform("doc:save-as")
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
doc():save(filename, abs_filename)
|
||||
local saved_filename = doc().filename
|
||||
core.log("Saved \"%s\"", saved_filename)
|
||||
end
|
||||
|
||||
local function cut_or_copy(delete)
|
||||
local full_text = ""
|
||||
local text = ""
|
||||
core.cursor_clipboard = {}
|
||||
core.cursor_clipboard_whole_line = {}
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
text = doc():get_text(line1, col1, line2, col2)
|
||||
full_text = full_text == "" and text or (text .. " " .. full_text)
|
||||
core.cursor_clipboard_whole_line[idx] = false
|
||||
local text = doc():get_text(line1, col1, line2, col2)
|
||||
if delete then
|
||||
doc():delete_to_cursor(idx, 0)
|
||||
end
|
||||
else -- Cut/copy whole line
|
||||
-- Remove newline from the text. It will be added as needed on paste.
|
||||
text = string.sub(doc().lines[line1], 1, -2)
|
||||
full_text = full_text == "" and text .. "\n" or (text .. "\n" .. full_text)
|
||||
core.cursor_clipboard_whole_line[idx] = true
|
||||
if delete then
|
||||
if line1 < #doc().lines then
|
||||
doc():remove(line1, 1, line1 + 1, 1)
|
||||
elseif #doc().lines == 1 then
|
||||
doc():remove(line1, 1, line1, math.huge)
|
||||
else
|
||||
doc():remove(line1 - 1, math.huge, line1, math.huge)
|
||||
end
|
||||
doc():set_selections(idx, line1, col1, line2, col2)
|
||||
end
|
||||
full_text = full_text == "" and text or (full_text .. "\n" .. text)
|
||||
doc().cursor_clipboard[idx] = text
|
||||
else
|
||||
doc().cursor_clipboard[idx] = ""
|
||||
end
|
||||
core.cursor_clipboard[idx] = text
|
||||
end
|
||||
if delete then doc():merge_cursors() end
|
||||
core.cursor_clipboard["full"] = full_text
|
||||
doc().cursor_clipboard["full"] = full_text
|
||||
system.set_clipboard(full_text)
|
||||
end
|
||||
|
||||
local function split_cursor(dv, direction)
|
||||
local function split_cursor(direction)
|
||||
local new_cursors = {}
|
||||
local dv_translate = direction < 0
|
||||
and DocView.translate.previous_line
|
||||
or DocView.translate.next_line
|
||||
for _, line1, col1 in dv.doc:get_selections() do
|
||||
if line1 + direction >= 1 and line1 + direction <= #dv.doc.lines then
|
||||
table.insert(new_cursors, { dv_translate(dv.doc, line1, col1, dv) })
|
||||
for _, line1, col1 in doc():get_selections() do
|
||||
if line1 + direction >= 1 and line1 + direction <= #doc().lines then
|
||||
table.insert(new_cursors, { line1 + direction, col1 })
|
||||
end
|
||||
end
|
||||
-- add selections in the order that will leave the "last" added one as doc.last_selection
|
||||
local start, stop = 1, #new_cursors
|
||||
if direction < 0 then
|
||||
start, stop = #new_cursors, 1
|
||||
end
|
||||
for i = start, stop, direction do
|
||||
local v = new_cursors[i]
|
||||
dv.doc:add_selection(v[1], v[2])
|
||||
end
|
||||
for i,v in ipairs(new_cursors) do doc():add_selection(v[1], v[2]) end
|
||||
core.blink_reset()
|
||||
end
|
||||
|
||||
local function set_cursor(dv, x, y, snap_type)
|
||||
local line, col = dv:resolve_screen_position(x, y)
|
||||
dv.doc:set_selection(line, col, line, col)
|
||||
local function set_cursor(x, y, snap_type)
|
||||
local line, col = dv():resolve_screen_position(x, y)
|
||||
doc():set_selection(line, col, line, col)
|
||||
if snap_type == "word" or snap_type == "lines" then
|
||||
command.perform("doc:select-" .. snap_type)
|
||||
end
|
||||
dv.mouse_selecting = { line, col, snap_type }
|
||||
dv().mouse_selecting = { line, col, snap_type }
|
||||
core.blink_reset()
|
||||
end
|
||||
|
||||
local function line_comment(comment, line1, col1, line2, col2)
|
||||
local start_comment = (type(comment) == 'table' and comment[1] or comment) .. " "
|
||||
local end_comment = (type(comment) == 'table' and " " .. comment[2])
|
||||
local uncomment = true
|
||||
local start_offset = math.huge
|
||||
for line = line1, line2 do
|
||||
local text = doc().lines[line]
|
||||
local s = text:find("%S")
|
||||
if s then
|
||||
local cs, ce = text:find(start_comment, s, true)
|
||||
if cs ~= s then
|
||||
uncomment = false
|
||||
end
|
||||
start_offset = math.min(start_offset, s)
|
||||
end
|
||||
end
|
||||
|
||||
local end_line = col2 == #doc().lines[line2]
|
||||
for line = line1, line2 do
|
||||
local text = doc().lines[line]
|
||||
local s = text:find("%S")
|
||||
if s and uncomment then
|
||||
if end_comment and text:sub(#text - #end_comment, #text - 1) == end_comment then
|
||||
doc():remove(line, #text - #end_comment, line, #text)
|
||||
end
|
||||
local cs, ce = text:find(start_comment, s, true)
|
||||
if ce then
|
||||
doc():remove(line, cs, line, ce + 1)
|
||||
end
|
||||
elseif s then
|
||||
doc():insert(line, start_offset, start_comment)
|
||||
if end_comment then
|
||||
doc():insert(line, #doc().lines[line], " " .. comment[2])
|
||||
end
|
||||
end
|
||||
end
|
||||
col1 = col1 + (col1 > start_offset and #start_comment or 0) * (uncomment and -1 or 1)
|
||||
col2 = col2 + (col2 > start_offset and #start_comment or 0) * (uncomment and -1 or 1)
|
||||
if end_comment and end_line then
|
||||
col2 = col2 + #end_comment * (uncomment and -1 or 1)
|
||||
end
|
||||
return line1, col1, line2, col2
|
||||
end
|
||||
|
||||
local function block_comment(comment, line1, col1, line2, col2)
|
||||
-- automatically skip spaces
|
||||
local word_start = doc():get_text(line1, col1, line1, math.huge):find("%S")
|
||||
local word_end = doc():get_text(line2, 1, line2, col2):find("%s*$")
|
||||
col1 = col1 + (word_start and (word_start - 1) or 0)
|
||||
col2 = word_end and word_end or col2
|
||||
|
||||
local block_start = doc():get_text(line1, col1, line1, col1 + #comment[1])
|
||||
local block_end = doc():get_text(line2, col2 - #comment[2], line2, col2)
|
||||
|
||||
if block_start == comment[1] and block_end == comment[2] then
|
||||
-- remove up to 1 whitespace after the comment
|
||||
local start_len, stop_len = #comment[1], #comment[2]
|
||||
if doc():get_text(line1, col1 + #comment[1], line1, col1 + #comment[1] + 1):find("%s$") then
|
||||
start_len = start_len + 1
|
||||
end
|
||||
if doc():get_text(line2, col2 - #comment[2] - 1, line2, col2):find("^%s") then
|
||||
stop_len = stop_len + 1
|
||||
end
|
||||
|
||||
doc():remove(line1, col1, line1, col1 + start_len)
|
||||
col2 = col2 - (line1 == line2 and start_len or 0)
|
||||
doc():remove(line2, col2 - stop_len, line2, col2)
|
||||
|
||||
return line1, col1, line2, col2 - stop_len
|
||||
else
|
||||
doc():insert(line1, col1, comment[1] .. " ")
|
||||
col2 = col2 + (line1 == line2 and (#comment[1] + 1) or 0)
|
||||
doc():insert(line2, col2, " " .. comment[2])
|
||||
|
||||
return line1, col1, line2, col2 + #comment[2] + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function insert_paste(doc, value, whole_line, idx)
|
||||
if whole_line then
|
||||
local line1, col1 = doc:get_selection_idx(idx)
|
||||
doc:insert(line1, 1, value:gsub("\r", "").."\n")
|
||||
-- Because we're inserting at the start of the line,
|
||||
-- if the cursor is in the middle of the line
|
||||
-- it gets carried to the next line along with the old text.
|
||||
-- If it's at the start of the line it doesn't get carried,
|
||||
-- so we move it of as many characters as we're adding.
|
||||
if col1 == 1 then
|
||||
doc:move_to_cursor(idx, #value+1)
|
||||
end
|
||||
else
|
||||
doc:text_input(value:gsub("\r", ""), idx)
|
||||
end
|
||||
end
|
||||
|
||||
local commands = {
|
||||
["doc:select-none"] = function(dv)
|
||||
local l1, c1 = dv.doc:get_selection_idx(dv.doc.last_selection)
|
||||
if not l1 then
|
||||
l1, c1 = dv.doc:get_selection_idx(1)
|
||||
end
|
||||
dv.doc:set_selection(l1, c1)
|
||||
end,
|
||||
|
||||
local selection_commands = {
|
||||
["doc:cut"] = function()
|
||||
cut_or_copy(true)
|
||||
end,
|
||||
|
@ -237,283 +93,219 @@ local commands = {
|
|||
cut_or_copy(false)
|
||||
end,
|
||||
|
||||
["doc:undo"] = function(dv)
|
||||
dv.doc:undo()
|
||||
["doc:select-none"] = function()
|
||||
local line, col = doc():get_selection()
|
||||
doc():set_selection(line, col)
|
||||
end
|
||||
}
|
||||
|
||||
local commands = {
|
||||
["doc:undo"] = function()
|
||||
doc():undo()
|
||||
end,
|
||||
|
||||
["doc:redo"] = function(dv)
|
||||
dv.doc:redo()
|
||||
["doc:redo"] = function()
|
||||
doc():redo()
|
||||
end,
|
||||
|
||||
["doc:paste"] = function(dv)
|
||||
["doc:paste"] = function()
|
||||
local clipboard = system.get_clipboard()
|
||||
-- If the clipboard has changed since our last look, use that instead
|
||||
if core.cursor_clipboard["full"] ~= clipboard then
|
||||
core.cursor_clipboard = {}
|
||||
core.cursor_clipboard_whole_line = {}
|
||||
for idx in dv.doc:get_selections() do
|
||||
insert_paste(dv.doc, clipboard, false, idx)
|
||||
end
|
||||
return
|
||||
if doc().cursor_clipboard["full"] ~= clipboard then
|
||||
doc().cursor_clipboard = {}
|
||||
end
|
||||
-- Use internal clipboard(s)
|
||||
-- If there are mixed whole lines and normal lines, consider them all as normal
|
||||
local only_whole_lines = true
|
||||
for _,whole_line in pairs(core.cursor_clipboard_whole_line) do
|
||||
if not whole_line then
|
||||
only_whole_lines = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if #core.cursor_clipboard_whole_line == (#dv.doc.selections/4) then
|
||||
-- If we have the same number of clipboards and selections,
|
||||
-- paste each clipboard into its corresponding selection
|
||||
for idx in dv.doc:get_selections() do
|
||||
insert_paste(dv.doc, core.cursor_clipboard[idx], only_whole_lines, idx)
|
||||
end
|
||||
else
|
||||
-- Paste every clipboard and add a selection at the end of each one
|
||||
local new_selections = {}
|
||||
for idx in dv.doc:get_selections() do
|
||||
for cb_idx in ipairs(core.cursor_clipboard_whole_line) do
|
||||
insert_paste(dv.doc, core.cursor_clipboard[cb_idx], only_whole_lines, idx)
|
||||
if not only_whole_lines then
|
||||
table.insert(new_selections, {dv.doc:get_selection_idx(idx)})
|
||||
end
|
||||
end
|
||||
if only_whole_lines then
|
||||
table.insert(new_selections, {dv.doc:get_selection_idx(idx)})
|
||||
end
|
||||
end
|
||||
local first = true
|
||||
for _,selection in pairs(new_selections) do
|
||||
if first then
|
||||
dv.doc:set_selection(table.unpack(selection))
|
||||
first = false
|
||||
else
|
||||
dv.doc:add_selection(table.unpack(selection))
|
||||
end
|
||||
end
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||
local value = doc().cursor_clipboard[idx] or clipboard
|
||||
doc():text_input(value:gsub("\r", ""), idx)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:newline"] = function(dv)
|
||||
for idx, line, col in dv.doc:get_selections(false, true) do
|
||||
local indent = dv.doc.lines[line]:match("^[\t ]*")
|
||||
["doc:newline"] = function()
|
||||
for idx, line, col in doc():get_selections(false, true) do
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
if col <= #indent then
|
||||
indent = indent:sub(#indent + 2 - col)
|
||||
end
|
||||
-- Remove current line if it contains only whitespace
|
||||
if not config.keep_newline_whitespace and dv.doc.lines[line]:match("^%s+$") then
|
||||
dv.doc:remove(line, 1, line, math.huge)
|
||||
doc():text_input("\n" .. indent, idx)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:newline-below"] = function()
|
||||
for idx, line in doc():get_selections(false, true) do
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
doc():insert(line, math.huge, "\n" .. indent)
|
||||
doc():set_selections(idx, line + 1, math.huge)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:newline-above"] = function()
|
||||
for idx, line in doc():get_selections(false, true) do
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
doc():insert(line, 1, indent .. "\n")
|
||||
doc():set_selections(idx, line, math.huge)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:delete"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||
if line1 == line2 and col1 == col2 and doc().lines[line1]:find("^%s*$", col1) then
|
||||
doc():remove(line1, col1, line1, math.huge)
|
||||
end
|
||||
dv.doc:text_input("\n" .. indent, idx)
|
||||
doc():delete_to_cursor(idx, translate.next_char)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:newline-below"] = function(dv)
|
||||
for idx, line in dv.doc:get_selections(false, true) do
|
||||
local indent = dv.doc.lines[line]:match("^[\t ]*")
|
||||
dv.doc:insert(line, math.huge, "\n" .. indent)
|
||||
dv.doc:set_selections(idx, line + 1, math.huge)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:newline-above"] = function(dv)
|
||||
for idx, line in dv.doc:get_selections(false, true) do
|
||||
local indent = dv.doc.lines[line]:match("^[\t ]*")
|
||||
dv.doc:insert(line, 1, indent .. "\n")
|
||||
dv.doc:set_selections(idx, line, math.huge)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:delete"] = function(dv)
|
||||
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true, true) do
|
||||
if line1 == line2 and col1 == col2 and dv.doc.lines[line1]:find("^%s*$", col1) then
|
||||
dv.doc:remove(line1, col1, line1, math.huge)
|
||||
end
|
||||
dv.doc:delete_to_cursor(idx, translate.next_char)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:backspace"] = function(dv)
|
||||
local _, indent_size = dv.doc:get_indent_info()
|
||||
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true, true) do
|
||||
["doc:backspace"] = function()
|
||||
local _, indent_size = doc():get_indent_info()
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||
if line1 == line2 and col1 == col2 then
|
||||
local text = dv.doc:get_text(line1, 1, line1, col1)
|
||||
local text = doc():get_text(line1, 1, line1, col1)
|
||||
if #text >= indent_size and text:find("^ *$") then
|
||||
dv.doc:delete_to_cursor(idx, 0, -indent_size)
|
||||
goto continue
|
||||
doc():delete_to_cursor(idx, 0, -indent_size)
|
||||
return
|
||||
end
|
||||
end
|
||||
dv.doc:delete_to_cursor(idx, translate.previous_char)
|
||||
::continue::
|
||||
doc():delete_to_cursor(idx, translate.previous_char)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:select-all"] = function(dv)
|
||||
dv.doc:set_selection(1, 1, math.huge, math.huge)
|
||||
-- avoid triggering DocView:scroll_to_make_visible
|
||||
dv.last_line1 = 1
|
||||
dv.last_col1 = 1
|
||||
dv.last_line2 = #dv.doc.lines
|
||||
dv.last_col2 = #dv.doc.lines[#dv.doc.lines]
|
||||
["doc:select-all"] = function()
|
||||
doc():set_selection(1, 1, math.huge, math.huge)
|
||||
end,
|
||||
|
||||
["doc:select-lines"] = function(dv)
|
||||
for idx, line1, _, line2 in dv.doc:get_selections(true) do
|
||||
["doc:select-lines"] = function()
|
||||
for idx, line1, _, line2 in doc():get_selections(true) do
|
||||
append_line_if_last_line(line2)
|
||||
dv.doc:set_selections(idx, line1, 1, line2 + 1, 1)
|
||||
doc():set_selections(idx, line1, 1, line2 + 1, 1)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:select-word"] = function(dv)
|
||||
for idx, line1, col1 in dv.doc:get_selections(true) do
|
||||
local line1, col1 = translate.start_of_word(dv.doc, line1, col1)
|
||||
local line2, col2 = translate.end_of_word(dv.doc, line1, col1)
|
||||
dv.doc:set_selections(idx, line2, col2, line1, col1)
|
||||
["doc:select-word"] = function()
|
||||
for idx, line1, col1 in doc():get_selections(true) do
|
||||
local line1, col1 = translate.start_of_word(doc(), line1, col1)
|
||||
local line2, col2 = translate.end_of_word(doc(), line1, col1)
|
||||
doc():set_selections(idx, line2, col2, line1, col1)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:join-lines"] = function(dv)
|
||||
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true) do
|
||||
["doc:join-lines"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
||||
if line1 == line2 then line2 = line2 + 1 end
|
||||
local text = dv.doc:get_text(line1, 1, line2, math.huge)
|
||||
local text = doc():get_text(line1, 1, line2, math.huge)
|
||||
text = text:gsub("(.-)\n[\t ]*", function(x)
|
||||
return x:find("^%s*$") and x or x .. " "
|
||||
end)
|
||||
dv.doc:insert(line1, 1, text)
|
||||
dv.doc:remove(line1, #text + 1, line2, math.huge)
|
||||
doc():insert(line1, 1, text)
|
||||
doc():remove(line1, #text + 1, line2, math.huge)
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
dv.doc:set_selections(idx, line1, math.huge)
|
||||
doc():set_selections(idx, line1, math.huge)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:indent"] = function(dv)
|
||||
["doc:indent"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
local l1, c1, l2, c2 = dv.doc:indent_text(false, line1, col1, line2, col2)
|
||||
local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2)
|
||||
if l1 then
|
||||
dv.doc:set_selections(idx, l1, c1, l2, c2)
|
||||
doc():set_selections(idx, l1, c1, l2, c2)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:unindent"] = function(dv)
|
||||
["doc:unindent"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
local l1, c1, l2, c2 = dv.doc:indent_text(true, line1, col1, line2, col2)
|
||||
local l1, c1, l2, c2 = doc():indent_text(true, line1, col1, line2, col2)
|
||||
if l1 then
|
||||
dv.doc:set_selections(idx, l1, c1, l2, c2)
|
||||
doc():set_selections(idx, l1, c1, l2, c2)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:duplicate-lines"] = function(dv)
|
||||
["doc:duplicate-lines"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
append_line_if_last_line(line2)
|
||||
local text = doc():get_text(line1, 1, line2 + 1, 1)
|
||||
dv.doc:insert(line2 + 1, 1, text)
|
||||
doc():insert(line2 + 1, 1, text)
|
||||
local n = line2 - line1 + 1
|
||||
dv.doc:set_selections(idx, line1 + n, col1, line2 + n, col2)
|
||||
doc():set_selections(idx, line1 + n, col1, line2 + n, col2)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:delete-lines"] = function(dv)
|
||||
["doc:delete-lines"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
append_line_if_last_line(line2)
|
||||
dv.doc:remove(line1, 1, line2 + 1, 1)
|
||||
dv.doc:set_selections(idx, line1, col1)
|
||||
doc():remove(line1, 1, line2 + 1, 1)
|
||||
doc():set_selections(idx, line1, col1)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:move-lines-up"] = function(dv)
|
||||
["doc:move-lines-up"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
append_line_if_last_line(line2)
|
||||
if line1 > 1 then
|
||||
local text = doc().lines[line1 - 1]
|
||||
dv.doc:insert(line2 + 1, 1, text)
|
||||
dv.doc:remove(line1 - 1, 1, line1, 1)
|
||||
dv.doc:set_selections(idx, line1 - 1, col1, line2 - 1, col2)
|
||||
doc():insert(line2 + 1, 1, text)
|
||||
doc():remove(line1 - 1, 1, line1, 1)
|
||||
doc():set_selections(idx, line1 - 1, col1, line2 - 1, col2)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:move-lines-down"] = function(dv)
|
||||
["doc:move-lines-down"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
append_line_if_last_line(line2 + 1)
|
||||
if line2 < #dv.doc.lines then
|
||||
local text = dv.doc.lines[line2 + 1]
|
||||
dv.doc:remove(line2 + 1, 1, line2 + 2, 1)
|
||||
dv.doc:insert(line1, 1, text)
|
||||
dv.doc:set_selections(idx, line1 + 1, col1, line2 + 1, col2)
|
||||
if line2 < #doc().lines then
|
||||
local text = doc().lines[line2 + 1]
|
||||
doc():remove(line2 + 1, 1, line2 + 2, 1)
|
||||
doc():insert(line1, 1, text)
|
||||
doc():set_selections(idx, line1 + 1, col1, line2 + 1, col2)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:toggle-block-comments"] = function(dv)
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
local current_syntax = dv.doc.syntax
|
||||
if line1 > 1 then
|
||||
-- Use the previous line state, as it will be the state
|
||||
-- of the beginning of the current line
|
||||
local state = dv.doc.highlighter:get_line(line1 - 1).state
|
||||
local syntaxes = tokenizer.extract_subsyntaxes(dv.doc.syntax, state)
|
||||
-- Go through all the syntaxes until the first with `block_comment` defined
|
||||
for _, s in pairs(syntaxes) do
|
||||
if s.block_comment then
|
||||
current_syntax = s
|
||||
break
|
||||
["doc:toggle-line-comments"] = function()
|
||||
local comment = doc().syntax.comment
|
||||
if not comment then return end
|
||||
local indentation = doc():get_indent_string()
|
||||
local comment_text = comment .. " "
|
||||
for idx, line1, _, line2 in doc_multiline_selections(true) do
|
||||
local uncomment = true
|
||||
local start_offset = math.huge
|
||||
for line = line1, line2 do
|
||||
local text = doc().lines[line]
|
||||
local s = text:find("%S")
|
||||
local cs, ce = text:find(comment_text, s, true)
|
||||
if s and cs ~= s then
|
||||
uncomment = false
|
||||
start_offset = math.min(start_offset, s)
|
||||
end
|
||||
end
|
||||
for line = line1, line2 do
|
||||
local text = doc().lines[line]
|
||||
local s = text:find("%S")
|
||||
if uncomment then
|
||||
local cs, ce = text:find(comment_text, s, true)
|
||||
if ce then
|
||||
doc():remove(line, cs, line, ce + 1)
|
||||
end
|
||||
elseif s then
|
||||
doc():insert(line, start_offset, comment_text)
|
||||
end
|
||||
end
|
||||
local comment = current_syntax.block_comment
|
||||
if not comment then
|
||||
if dv.doc.syntax.comment then
|
||||
command.perform "doc:toggle-line-comments"
|
||||
end
|
||||
return
|
||||
end
|
||||
-- if nothing is selected, toggle the whole line
|
||||
if line1 == line2 and col1 == col2 then
|
||||
col1 = 1
|
||||
col2 = #dv.doc.lines[line2]
|
||||
end
|
||||
dv.doc:set_selections(idx, block_comment(comment, line1, col1, line2, col2))
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:toggle-line-comments"] = function(dv)
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
local current_syntax = dv.doc.syntax
|
||||
if line1 > 1 then
|
||||
-- Use the previous line state, as it will be the state
|
||||
-- of the beginning of the current line
|
||||
local state = dv.doc.highlighter:get_line(line1 - 1).state
|
||||
local syntaxes = tokenizer.extract_subsyntaxes(dv.doc.syntax, state)
|
||||
-- Go through all the syntaxes until the first with comments defined
|
||||
for _, s in pairs(syntaxes) do
|
||||
if s.comment or s.block_comment then
|
||||
current_syntax = s
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
local comment = current_syntax.comment or current_syntax.block_comment
|
||||
if comment then
|
||||
dv.doc:set_selections(idx, line_comment(comment, line1, col1, line2, col2))
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:upper-case"] = function(dv)
|
||||
dv.doc:replace(string.uupper)
|
||||
["doc:upper-case"] = function()
|
||||
doc():replace(string.upper)
|
||||
end,
|
||||
|
||||
["doc:lower-case"] = function(dv)
|
||||
dv.doc:replace(string.ulower)
|
||||
["doc:lower-case"] = function()
|
||||
doc():replace(string.lower)
|
||||
end,
|
||||
|
||||
["doc:go-to-line"] = function(dv)
|
||||
["doc:go-to-line"] = function()
|
||||
local dv = dv()
|
||||
|
||||
local items
|
||||
local function init_items()
|
||||
if items then return end
|
||||
|
@ -525,197 +317,165 @@ local commands = {
|
|||
end
|
||||
end
|
||||
|
||||
core.command_view:enter("Go To Line", {
|
||||
submit = function(text, item)
|
||||
local line = item and item.line or tonumber(text)
|
||||
if not line then
|
||||
core.error("Invalid line number or unmatched string")
|
||||
return
|
||||
end
|
||||
dv.doc:set_selection(line, 1 )
|
||||
dv:scroll_to_line(line, true)
|
||||
end,
|
||||
suggest = function(text)
|
||||
if not text:find("^%d*$") then
|
||||
init_items()
|
||||
return common.fuzzy_match(items, text)
|
||||
end
|
||||
core.command_view:enter("Go To Line", function(text, item)
|
||||
local line = item and item.line or tonumber(text)
|
||||
if not line then
|
||||
core.error("Invalid line number or unmatched string")
|
||||
return
|
||||
end
|
||||
})
|
||||
dv.doc:set_selection(line, 1 )
|
||||
dv:scroll_to_line(line, true)
|
||||
|
||||
end, function(text)
|
||||
if not text:find("^%d*$") then
|
||||
init_items()
|
||||
return common.fuzzy_match(items, text)
|
||||
end
|
||||
end)
|
||||
end,
|
||||
|
||||
["doc:toggle-line-ending"] = function(dv)
|
||||
dv.doc.crlf = not dv.doc.crlf
|
||||
["doc:toggle-line-ending"] = function()
|
||||
doc().crlf = not doc().crlf
|
||||
end,
|
||||
|
||||
["doc:save-as"] = function(dv)
|
||||
["doc:save-as"] = function()
|
||||
local last_doc = core.last_active_view and core.last_active_view.doc
|
||||
local text
|
||||
if dv.doc.filename then
|
||||
text = dv.doc.filename
|
||||
if doc().filename then
|
||||
core.command_view:set_text(doc().filename)
|
||||
elseif last_doc and last_doc.filename then
|
||||
local dirname, filename = core.last_active_view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
||||
text = core.normalize_to_project_dir(dirname) .. PATHSEP
|
||||
core.command_view:set_text(core.normalize_to_project_dir(dirname) .. PATHSEP)
|
||||
end
|
||||
core.command_view:enter("Save As", {
|
||||
text = text,
|
||||
submit = function(filename)
|
||||
save(common.home_expand(filename))
|
||||
end,
|
||||
suggest = function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||
end
|
||||
})
|
||||
core.command_view:enter("Save As", function(filename)
|
||||
save(common.home_expand(filename))
|
||||
end, function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||
end)
|
||||
end,
|
||||
|
||||
["doc:save"] = function(dv)
|
||||
if dv.doc.filename then
|
||||
["doc:save"] = function()
|
||||
if doc().filename then
|
||||
save()
|
||||
else
|
||||
command.perform("doc:save-as")
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:reload"] = function(dv)
|
||||
dv.doc:reload()
|
||||
end,
|
||||
|
||||
["file:rename"] = function(dv)
|
||||
local old_filename = dv.doc.filename
|
||||
["file:rename"] = function()
|
||||
local old_filename = doc().filename
|
||||
if not old_filename then
|
||||
core.error("Cannot rename unsaved doc")
|
||||
return
|
||||
end
|
||||
core.command_view:enter("Rename", {
|
||||
text = old_filename,
|
||||
submit = function(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,
|
||||
suggest = function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||
core.command_view:set_text(old_filename)
|
||||
core.command_view:enter("Rename", function(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, function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||
end)
|
||||
end,
|
||||
|
||||
|
||||
["file:delete"] = function(dv)
|
||||
local filename = dv.doc.abs_filename
|
||||
["file:delete"] = function()
|
||||
local filename = doc().abs_filename
|
||||
if not filename then
|
||||
core.error("Cannot remove unsaved doc")
|
||||
return
|
||||
end
|
||||
for i,docview in ipairs(core.get_views_referencing_doc(dv.doc)) do
|
||||
for i,docview in ipairs(core.get_views_referencing_doc(doc())) do
|
||||
local node = core.root_view.root_node:get_node_for_view(docview)
|
||||
node:close_view(core.root_view.root_node, docview)
|
||||
node:close_view(core.root_view, docview)
|
||||
end
|
||||
os.remove(filename)
|
||||
core.log("Removed \"%s\"", filename)
|
||||
end,
|
||||
|
||||
["doc:select-to-cursor"] = function(dv, x, y, clicks)
|
||||
|
||||
["doc:select-to-cursor"] = function(x, y, clicks)
|
||||
local line1, col1 = select(3, doc():get_selection())
|
||||
local line2, col2 = dv:resolve_screen_position(x, y)
|
||||
dv.mouse_selecting = { line1, col1, nil }
|
||||
dv.doc:set_selection(line2, col2, line1, col1)
|
||||
local line2, col2 = dv():resolve_screen_position(x, y)
|
||||
dv().mouse_selecting = { line1, col1, nil }
|
||||
doc():set_selection(line2, col2, line1, col1)
|
||||
end,
|
||||
|
||||
["doc:set-cursor"] = function(x, y)
|
||||
set_cursor(x, y, "set")
|
||||
end,
|
||||
|
||||
["doc:set-cursor-word"] = function(x, y)
|
||||
set_cursor(x, y, "word")
|
||||
end,
|
||||
|
||||
["doc:set-cursor-line"] = function(x, y, clicks)
|
||||
set_cursor(x, y, "lines")
|
||||
end,
|
||||
|
||||
["doc:split-cursor"] = function(x, y, clicks)
|
||||
local line, col = dv():resolve_screen_position(x, y)
|
||||
doc():add_selection(line, col, line, col)
|
||||
end,
|
||||
|
||||
["doc:create-cursor-previous-line"] = function(dv)
|
||||
split_cursor(dv, -1)
|
||||
dv.doc:merge_cursors()
|
||||
["doc:create-cursor-previous-line"] = function()
|
||||
split_cursor(-1)
|
||||
doc():merge_cursors()
|
||||
end,
|
||||
|
||||
["doc:create-cursor-next-line"] = function(dv)
|
||||
split_cursor(dv, 1)
|
||||
dv.doc:merge_cursors()
|
||||
["doc:create-cursor-next-line"] = function()
|
||||
split_cursor(1)
|
||||
doc():merge_cursors()
|
||||
end
|
||||
|
||||
}
|
||||
|
||||
command.add(function(x, y)
|
||||
if x == nil or y == nil or not core.active_view:extends(DocView) then return false end
|
||||
local dv = core.active_view
|
||||
local x1,y1,x2,y2 = dv.position.x, dv.position.y, dv.position.x + dv.size.x, dv.position.y + dv.size.y
|
||||
return x >= x1 + dv:get_gutter_width() and x < x2 and y >= y1 and y < y2, dv, x, y
|
||||
end, {
|
||||
["doc:set-cursor"] = function(dv, x, y)
|
||||
set_cursor(dv, x, y, "set")
|
||||
end,
|
||||
|
||||
["doc:set-cursor-word"] = function(dv, x, y)
|
||||
set_cursor(dv, x, y, "word")
|
||||
end,
|
||||
|
||||
["doc:set-cursor-line"] = function(dv, x, y, clicks)
|
||||
set_cursor(dv, x, y, "lines")
|
||||
end,
|
||||
|
||||
["doc:split-cursor"] = function(dv, x, y, clicks)
|
||||
local line, col = dv:resolve_screen_position(x, y)
|
||||
local removal_target = nil
|
||||
for idx, line1, col1 in dv.doc:get_selections(true) do
|
||||
if line1 == line and col1 == col and #doc().selections > 4 then
|
||||
removal_target = idx
|
||||
end
|
||||
end
|
||||
if removal_target then
|
||||
dv.doc:remove_selection(removal_target)
|
||||
else
|
||||
dv.doc:add_selection(line, col, line, col)
|
||||
end
|
||||
dv.mouse_selecting = { line, col, "set" }
|
||||
end
|
||||
})
|
||||
|
||||
local translations = {
|
||||
["previous-char"] = translate,
|
||||
["next-char"] = translate,
|
||||
["previous-word-start"] = translate,
|
||||
["next-word-end"] = translate,
|
||||
["previous-block-start"] = translate,
|
||||
["next-block-end"] = translate,
|
||||
["start-of-doc"] = translate,
|
||||
["end-of-doc"] = translate,
|
||||
["start-of-line"] = translate,
|
||||
["end-of-line"] = translate,
|
||||
["start-of-word"] = translate,
|
||||
["start-of-indentation"] = translate,
|
||||
["end-of-word"] = translate,
|
||||
["previous-line"] = DocView.translate,
|
||||
["next-line"] = DocView.translate,
|
||||
["previous-page"] = DocView.translate,
|
||||
["next-page"] = DocView.translate,
|
||||
["previous-char"] = translate.previous_char,
|
||||
["next-char"] = translate.next_char,
|
||||
["previous-word-start"] = translate.previous_word_start,
|
||||
["next-word-end"] = translate.next_word_end,
|
||||
["previous-block-start"] = translate.previous_block_start,
|
||||
["next-block-end"] = translate.next_block_end,
|
||||
["start-of-doc"] = translate.start_of_doc,
|
||||
["end-of-doc"] = translate.end_of_doc,
|
||||
["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,
|
||||
["previous-page"] = DocView.translate.previous_page,
|
||||
["next-page"] = DocView.translate.next_page,
|
||||
}
|
||||
|
||||
for name, obj in pairs(translations) do
|
||||
commands["doc:move-to-" .. name] = function(dv) dv.doc:move_to(obj[name:gsub("-", "_")], dv) end
|
||||
commands["doc:select-to-" .. name] = function(dv) dv.doc:select_to(obj[name:gsub("-", "_")], dv) end
|
||||
commands["doc:delete-to-" .. name] = function(dv) dv.doc:delete_to(obj[name:gsub("-", "_")], dv) end
|
||||
for name, fn in pairs(translations) do
|
||||
commands["doc:move-to-" .. name] = function() doc():move_to(fn, dv()) end
|
||||
commands["doc:select-to-" .. name] = function() doc():select_to(fn, dv()) end
|
||||
commands["doc:delete-to-" .. name] = function() doc():delete_to(fn, dv()) end
|
||||
end
|
||||
|
||||
commands["doc:move-to-previous-char"] = function(dv)
|
||||
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true) do
|
||||
commands["doc:move-to-previous-char"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
dv.doc:set_selections(idx, line1, col1)
|
||||
else
|
||||
dv.doc:move_to_cursor(idx, translate.previous_char)
|
||||
doc():set_selections(idx, line1, col1)
|
||||
end
|
||||
end
|
||||
dv.doc:merge_cursors()
|
||||
doc():move_to(translate.previous_char)
|
||||
end
|
||||
|
||||
commands["doc:move-to-next-char"] = function(dv)
|
||||
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true) do
|
||||
commands["doc:move-to-next-char"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
dv.doc:set_selections(idx, line2, col2)
|
||||
else
|
||||
dv.doc:move_to_cursor(idx, translate.next_char)
|
||||
doc():set_selections(idx, line2, col2)
|
||||
end
|
||||
end
|
||||
dv.doc:merge_cursors()
|
||||
doc():move_to(translate.next_char)
|
||||
end
|
||||
|
||||
command.add("core.docview", commands)
|
||||
command.add(function()
|
||||
return core.active_view:is(DocView) and core.active_view.doc:has_any_selection()
|
||||
end ,selection_commands)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
|
||||
command.add(nil, {
|
||||
["draw-whitespace:toggle"] = function()
|
||||
config.draw_whitespace = not config.draw_whitespace
|
||||
end,
|
||||
|
||||
["draw-whitespace:disable"] = function()
|
||||
config.draw_whitespace = false
|
||||
end,
|
||||
|
||||
["draw-whitespace:enable"] = function()
|
||||
config.draw_whitespace = true
|
||||
end,
|
||||
})
|
|
@ -4,13 +4,11 @@ local common = require "core.common"
|
|||
|
||||
command.add(nil, {
|
||||
["files:create-directory"] = function()
|
||||
core.command_view:enter("New directory name", {
|
||||
submit = function(text)
|
||||
local success, err, path = common.mkdirp(text)
|
||||
if not success then
|
||||
core.error("cannot create directory %q: %s", path, err)
|
||||
end
|
||||
core.command_view:enter("New directory name", function(text)
|
||||
local success, err, path = common.mkdirp(text)
|
||||
if not success then
|
||||
core.error("cannot create directory %q: %s", path, err)
|
||||
end
|
||||
})
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
|
|
@ -46,91 +46,70 @@ end
|
|||
local function insert_unique(t, v)
|
||||
local n = #t
|
||||
for i = 1, n do
|
||||
if t[i] == v then
|
||||
table.remove(t, i)
|
||||
break
|
||||
end
|
||||
if t[i] == v then return end
|
||||
end
|
||||
table.insert(t, 1, v)
|
||||
t[n + 1] = v
|
||||
end
|
||||
|
||||
|
||||
local function find(label, search_fn)
|
||||
last_view, last_sel = core.active_view,
|
||||
{ core.active_view.doc:get_selection() }
|
||||
local text = last_view.doc:get_text(table.unpack(last_sel))
|
||||
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, {
|
||||
text = text,
|
||||
select_text = true,
|
||||
show_suggestions = false,
|
||||
submit = function(text, item)
|
||||
insert_unique(core.previous_find, text)
|
||||
core.status_view:remove_tooltip()
|
||||
if found_expression then
|
||||
last_fn, last_text = search_fn, text
|
||||
else
|
||||
core.error("Couldn't find %q", text)
|
||||
last_view.doc:set_selection(table.unpack(last_sel))
|
||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
||||
end
|
||||
end,
|
||||
suggest = function(text)
|
||||
update_preview(last_sel, search_fn, 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_expression then
|
||||
last_fn, last_text = search_fn, text
|
||||
return core.previous_find
|
||||
end,
|
||||
cancel = function(explicit)
|
||||
core.status_view:remove_tooltip()
|
||||
if explicit then
|
||||
last_view.doc:set_selection(table.unpack(last_sel))
|
||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
||||
end
|
||||
else
|
||||
core.error("Couldn't find %q", text)
|
||||
last_view.doc:set_selection(table.unpack(last_sel))
|
||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
||||
end
|
||||
})
|
||||
end, function(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
|
||||
last_view.doc:set_selection(table.unpack(last_sel))
|
||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local function replace(kind, default, fn)
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
core.command_view:enter("Find To Replace " .. kind, {
|
||||
text = default,
|
||||
select_text = true,
|
||||
show_suggestions = false,
|
||||
submit = function(old)
|
||||
insert_unique(core.previous_find, old)
|
||||
core.command_view:set_text(default, true)
|
||||
|
||||
local s = string.format("Replace %s %q With", kind, old)
|
||||
core.command_view:enter(s, {
|
||||
text = old,
|
||||
select_text = true,
|
||||
show_suggestions = false,
|
||||
submit = function(new)
|
||||
core.status_view:remove_tooltip()
|
||||
insert_unique(core.previous_replace, new)
|
||||
local results = doc():replace(function(text)
|
||||
return fn(text, old, new)
|
||||
end)
|
||||
local n = 0
|
||||
for _,v in pairs(results) do
|
||||
n = n + v
|
||||
end
|
||||
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
|
||||
end,
|
||||
suggest = function() return core.previous_replace end,
|
||||
cancel = function()
|
||||
core.status_view:remove_tooltip()
|
||||
end
|
||||
})
|
||||
end,
|
||||
suggest = function() return core.previous_find end,
|
||||
cancel = function()
|
||||
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()
|
||||
end
|
||||
})
|
||||
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() 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
|
||||
|
||||
local function has_selection()
|
||||
|
@ -164,16 +143,13 @@ local function is_in_any_selection(line, col)
|
|||
end
|
||||
|
||||
local function select_add_next(all)
|
||||
local il1, ic1
|
||||
for _, l1, c1, l2, c2 in doc():get_selections(true, true) do
|
||||
if not il1 then
|
||||
il1, ic1 = l1, c1
|
||||
end
|
||||
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 not is_in_any_selection(l2, c2) then
|
||||
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)
|
||||
|
@ -203,7 +179,7 @@ command.add(has_unique_selection, {
|
|||
["find-replace:select-add-all"] = function() select_add_next(true) end
|
||||
})
|
||||
|
||||
command.add("core.docview!", {
|
||||
command.add("core.docview", {
|
||||
["find-replace:find"] = function()
|
||||
find("Find Text", function(doc, line, col, text, case_sensitive, find_regex, find_reverse)
|
||||
local opt = { wrap = true, no_case = not case_sensitive, regex = find_regex, reverse = find_reverse }
|
||||
|
@ -219,7 +195,7 @@ command.add("core.docview!", {
|
|||
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
||||
end
|
||||
local result, matches = regex.gsub(regex.compile(old, "m"), text, new)
|
||||
return result, matches
|
||||
return result, #matches
|
||||
end)
|
||||
end,
|
||||
|
||||
|
@ -243,38 +219,34 @@ command.add("core.docview!", {
|
|||
})
|
||||
|
||||
local function valid_for_finding()
|
||||
-- Allow using this while in the CommandView
|
||||
if core.active_view:is(CommandView) and last_view then
|
||||
return true, last_view
|
||||
end
|
||||
return core.active_view:is(DocView), core.active_view
|
||||
return core.active_view:is(DocView) or core.active_view:is(CommandView)
|
||||
end
|
||||
|
||||
command.add(valid_for_finding, {
|
||||
["find-replace:repeat-find"] = function(dv)
|
||||
["find-replace:repeat-find"] = function()
|
||||
if not last_fn then
|
||||
core.error("No find to continue from")
|
||||
else
|
||||
local sl1, sc1, sl2, sc2 = dv.doc:get_selection(true)
|
||||
local line1, col1, line2, col2 = last_fn(dv.doc, sl2, sc2, last_text, case_sensitive, find_regex, false)
|
||||
local sl1, sc1, sl2, sc2 = doc():get_selection(true)
|
||||
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc2, last_text, case_sensitive, find_regex, false)
|
||||
if line1 then
|
||||
dv.doc:set_selection(line2, col2, line1, col1)
|
||||
dv:scroll_to_line(line2, true)
|
||||
doc():set_selection(line2, col2, line1, col1)
|
||||
last_view:scroll_to_line(line2, true)
|
||||
else
|
||||
core.error("Couldn't find %q", last_text)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["find-replace:previous-find"] = function(dv)
|
||||
["find-replace:previous-find"] = function()
|
||||
if not last_fn then
|
||||
core.error("No find to continue from")
|
||||
else
|
||||
local sl1, sc1, sl2, sc2 = dv.doc:get_selection(true)
|
||||
local line1, col1, line2, col2 = last_fn(dv.doc, sl1, sc1, last_text, case_sensitive, find_regex, true)
|
||||
local sl1, sc1, sl2, sc2 = doc():get_selection(true)
|
||||
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc1, last_text, case_sensitive, find_regex, true)
|
||||
if line1 then
|
||||
dv.doc:set_selection(line2, col2, line1, col1)
|
||||
dv:scroll_to_line(line2, true)
|
||||
doc():set_selection(line2, col2, line1, col1)
|
||||
last_view:scroll_to_line(line2, true)
|
||||
else
|
||||
core.error("Couldn't find %q", last_text)
|
||||
end
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["log:open-as-doc"] = function()
|
||||
local doc = core.open_doc("logs.txt")
|
||||
core.root_view:open_doc(doc)
|
||||
doc:insert(1, 1, core.get_log())
|
||||
doc.new_file = false
|
||||
doc:clean()
|
||||
end,
|
||||
["log:copy-to-clipboard"] = function()
|
||||
system.set_clipboard(core.get_log())
|
||||
end
|
||||
})
|
|
@ -7,11 +7,13 @@ local config = require "core.config"
|
|||
|
||||
|
||||
local t = {
|
||||
["root:close"] = function(node)
|
||||
["root:close"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
node:close_active_view(core.root_view.root_node)
|
||||
end,
|
||||
|
||||
["root:close-or-quit"] = function(node)
|
||||
["root:close-or-quit"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
if node and (not node:is_empty() or not node.is_primary_node) then
|
||||
node:close_active_view(core.root_view.root_node)
|
||||
else
|
||||
|
@ -28,22 +30,25 @@ local t = {
|
|||
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(node)
|
||||
|
||||
["root:switch-to-previous-tab"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local idx = node:get_view_idx(core.active_view)
|
||||
idx = idx - 1
|
||||
if idx < 1 then idx = #node.views end
|
||||
node:set_active_view(node.views[idx])
|
||||
end,
|
||||
|
||||
["root:switch-to-next-tab"] = function(node)
|
||||
["root:switch-to-next-tab"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local idx = node:get_view_idx(core.active_view)
|
||||
idx = idx + 1
|
||||
if idx > #node.views then idx = 1 end
|
||||
node:set_active_view(node.views[idx])
|
||||
end,
|
||||
|
||||
["root:move-tab-left"] = function(node)
|
||||
["root:move-tab-left"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local idx = node:get_view_idx(core.active_view)
|
||||
if idx > 1 then
|
||||
table.remove(node.views, idx)
|
||||
|
@ -51,21 +56,24 @@ local t = {
|
|||
end
|
||||
end,
|
||||
|
||||
["root:move-tab-right"] = function(node)
|
||||
["root:move-tab-right"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local idx = node:get_view_idx(core.active_view)
|
||||
if idx < #node.views then
|
||||
table.remove(node.views, idx)
|
||||
table.insert(node.views, idx + 1, core.active_view)
|
||||
end
|
||||
end,
|
||||
|
||||
["root:shrink"] = function(node)
|
||||
|
||||
["root:shrink"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local parent = node:get_parent_node(core.root_view.root_node)
|
||||
local n = (parent.a == node) and -0.1 or 0.1
|
||||
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
|
||||
end,
|
||||
|
||||
["root:grow"] = function(node)
|
||||
["root:grow"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local parent = node:get_parent_node(core.root_view.root_node)
|
||||
local n = (parent.a == node) and 0.1 or -0.1
|
||||
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
|
||||
|
@ -74,7 +82,8 @@ local t = {
|
|||
|
||||
|
||||
for i = 1, 9 do
|
||||
t["root:switch-to-tab-" .. i] = function(node)
|
||||
t["root:switch-to-tab-" .. i] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local view = node.views[i]
|
||||
if view then
|
||||
node:set_active_view(view)
|
||||
|
@ -84,7 +93,8 @@ end
|
|||
|
||||
|
||||
for _, dir in ipairs { "left", "right", "up", "down" } do
|
||||
t["root:split-" .. dir] = function(node)
|
||||
t["root:split-" .. dir] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local av = node.active_view
|
||||
node:split(dir)
|
||||
if av:is(DocView) then
|
||||
|
@ -92,7 +102,8 @@ for _, dir in ipairs { "left", "right", "up", "down" } do
|
|||
end
|
||||
end
|
||||
|
||||
t["root:switch-to-" .. dir] = function(node)
|
||||
t["root:switch-to-" .. dir] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local x, y
|
||||
if dir == "left" or dir == "right" then
|
||||
y = node.position.y + node.size.y / 2
|
||||
|
@ -112,7 +123,7 @@ end
|
|||
command.add(function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local sx, sy = node:get_locked_size()
|
||||
return not sx and not sy, node
|
||||
return not sx and not sy
|
||||
end, t)
|
||||
|
||||
command.add(nil, {
|
||||
|
@ -123,13 +134,5 @@ command.add(nil, {
|
|||
return true
|
||||
end
|
||||
return false
|
||||
end,
|
||||
["root:horizontal-scroll"] = function(delta)
|
||||
local view = (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) or core.active_view
|
||||
if view and view.scrollable then
|
||||
view.scroll.to.x = view.scroll.to.x + delta * -config.mouse_wheel_scroll
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
})
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local style = require "core.style"
|
||||
local StatusView = require "core.statusview"
|
||||
|
||||
local function status_view_item_names()
|
||||
local items = core.status_view:get_items_list()
|
||||
local names = {}
|
||||
for _, item in ipairs(items) do
|
||||
table.insert(names, item.name)
|
||||
end
|
||||
return names
|
||||
end
|
||||
|
||||
local function status_view_items_data(names)
|
||||
local data = {}
|
||||
for _, name in ipairs(names) do
|
||||
local item = core.status_view:get_item(name)
|
||||
table.insert(data, {
|
||||
text = command.prettify_name(item.name),
|
||||
info = item.alignment == StatusView.Item.LEFT and "Left" or "Right",
|
||||
name = item.name
|
||||
})
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
local function status_view_get_items(text)
|
||||
local names = status_view_item_names()
|
||||
local results = common.fuzzy_match(names, text)
|
||||
results = status_view_items_data(results)
|
||||
return results
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
["status-bar:toggle"] = function()
|
||||
core.status_view:toggle()
|
||||
end,
|
||||
["status-bar:show"] = function()
|
||||
core.status_view:show()
|
||||
end,
|
||||
["status-bar:hide"] = function()
|
||||
core.status_view:hide()
|
||||
end,
|
||||
["status-bar:disable-messages"] = function()
|
||||
core.status_view:display_messages(false)
|
||||
end,
|
||||
["status-bar:enable-messages"] = function()
|
||||
core.status_view:display_messages(true)
|
||||
end,
|
||||
["status-bar:hide-item"] = function()
|
||||
core.command_view:enter("Status bar item to hide", {
|
||||
submit = function(text, item)
|
||||
core.status_view:hide_items(item.name)
|
||||
end,
|
||||
suggest = status_view_get_items
|
||||
})
|
||||
end,
|
||||
["status-bar:show-item"] = function()
|
||||
core.command_view:enter("Status bar item to show", {
|
||||
submit = function(text, item)
|
||||
core.status_view:show_items(item.name)
|
||||
end,
|
||||
suggest = status_view_get_items
|
||||
})
|
||||
end,
|
||||
["status-bar:reset-items"] = function()
|
||||
core.status_view:show_items()
|
||||
end,
|
||||
})
|
|
@ -6,16 +6,13 @@ local DocView = require "core.docview"
|
|||
local View = require "core.view"
|
||||
|
||||
|
||||
---@class core.commandview.input : core.doc
|
||||
---@field super core.doc
|
||||
local SingleLineDoc = Doc:extend()
|
||||
|
||||
function SingleLineDoc:insert(line, col, text)
|
||||
SingleLineDoc.super.insert(self, line, col, text:gsub("\n", ""))
|
||||
end
|
||||
|
||||
---@class core.commandview : core.docview
|
||||
---@field super core.docview
|
||||
|
||||
local CommandView = DocView:extend()
|
||||
|
||||
CommandView.context = "application"
|
||||
|
@ -24,37 +21,21 @@ local max_suggestions = 10
|
|||
|
||||
local noop = function() end
|
||||
|
||||
---@class core.commandview.state
|
||||
---@field submit function
|
||||
---@field suggest function
|
||||
---@field cancel function
|
||||
---@field validate function
|
||||
---@field text string
|
||||
---@field select_text boolean
|
||||
---@field show_suggestions boolean
|
||||
---@field typeahead boolean
|
||||
---@field wrap boolean
|
||||
local default_state = {
|
||||
submit = noop,
|
||||
suggest = noop,
|
||||
cancel = noop,
|
||||
validate = function() return true end,
|
||||
text = "",
|
||||
select_text = false,
|
||||
show_suggestions = true,
|
||||
typeahead = true,
|
||||
wrap = true,
|
||||
validate = function() return true end
|
||||
}
|
||||
|
||||
|
||||
function CommandView:new()
|
||||
CommandView.super.new(self, SingleLineDoc())
|
||||
self.suggestion_idx = 1
|
||||
self.suggestions_offset = 1
|
||||
self.suggestions = {}
|
||||
self.suggestions_height = 0
|
||||
self.show_suggestions = true
|
||||
self.last_change_id = 0
|
||||
self.last_text = ""
|
||||
self.gutter_width = 0
|
||||
self.gutter_text_brightness = 0
|
||||
self.selection_offset = 0
|
||||
|
@ -65,10 +46,8 @@ function CommandView:new()
|
|||
end
|
||||
|
||||
|
||||
---@deprecated
|
||||
function CommandView:set_hidden_suggestions()
|
||||
core.warn("Using deprecated function CommandView:set_hidden_suggestions")
|
||||
self.state.show_suggestions = false
|
||||
self.show_suggestions = false
|
||||
end
|
||||
|
||||
|
||||
|
@ -77,8 +56,8 @@ function CommandView:get_name()
|
|||
end
|
||||
|
||||
|
||||
function CommandView:get_line_screen_position(line, col)
|
||||
local x = CommandView.super.get_line_screen_position(self, 1, col)
|
||||
function CommandView:get_line_screen_position()
|
||||
local x = CommandView.super.get_line_screen_position(self, 1)
|
||||
local _, y = self:get_content_offset()
|
||||
local lh = self:get_line_height()
|
||||
return x, y + (self.size.y - lh) / 2
|
||||
|
@ -89,10 +68,6 @@ function CommandView:get_scrollable_size()
|
|||
return 0
|
||||
end
|
||||
|
||||
function CommandView:get_h_scrollable_size()
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
function CommandView:scroll_to_make_visible()
|
||||
-- no-op function to disable this functionality
|
||||
|
@ -105,7 +80,6 @@ end
|
|||
|
||||
|
||||
function CommandView:set_text(text, select)
|
||||
self.last_text = text
|
||||
self.doc:remove(1, 1, math.huge, math.huge)
|
||||
self.doc:text_input(text)
|
||||
if select then
|
||||
|
@ -115,36 +89,9 @@ end
|
|||
|
||||
|
||||
function CommandView:move_suggestion_idx(dir)
|
||||
local function overflow_suggestion_idx(n, count)
|
||||
if count == 0 then return 0 end
|
||||
if self.state.wrap then
|
||||
return (n - 1) % count + 1
|
||||
else
|
||||
return common.clamp(n, 1, count)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_suggestions_offset()
|
||||
local max_visible = math.min(max_suggestions, #self.suggestions)
|
||||
if dir > 0 then
|
||||
if self.suggestions_offset + max_visible < self.suggestion_idx + 1 then
|
||||
return self.suggestion_idx - max_visible + 1
|
||||
elseif self.suggestions_offset > self.suggestion_idx then
|
||||
return self.suggestion_idx
|
||||
end
|
||||
else
|
||||
if self.suggestions_offset > self.suggestion_idx then
|
||||
return self.suggestion_idx
|
||||
elseif self.suggestions_offset + max_visible < self.suggestion_idx + 1 then
|
||||
return self.suggestion_idx - max_visible + 1
|
||||
end
|
||||
end
|
||||
return self.suggestions_offset
|
||||
end
|
||||
|
||||
if self.state.show_suggestions then
|
||||
if self.show_suggestions then
|
||||
local n = self.suggestion_idx + dir
|
||||
self.suggestion_idx = overflow_suggestion_idx(n, #self.suggestions)
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
else
|
||||
|
@ -155,7 +102,7 @@ function CommandView:move_suggestion_idx(dir)
|
|||
if n == 0 and self.save_suggestion then
|
||||
self:set_text(self.save_suggestion)
|
||||
else
|
||||
self.suggestion_idx = overflow_suggestion_idx(n, #self.suggestions)
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
end
|
||||
else
|
||||
|
@ -165,8 +112,6 @@ function CommandView:move_suggestion_idx(dir)
|
|||
self.last_change_id = self.doc:get_change_id()
|
||||
self.state.suggest(self:get_text())
|
||||
end
|
||||
|
||||
self.suggestions_offset = get_suggestions_offset()
|
||||
end
|
||||
|
||||
|
||||
|
@ -180,60 +125,28 @@ end
|
|||
function CommandView:submit()
|
||||
local suggestion = self.suggestions[self.suggestion_idx]
|
||||
local text = self:get_text()
|
||||
if self.state.validate(text, suggestion) then
|
||||
if self.state.validate(text) then
|
||||
local submit = self.state.submit
|
||||
self:exit(true)
|
||||
submit(text, suggestion)
|
||||
end
|
||||
end
|
||||
|
||||
---@param label string
|
||||
---@varargs any
|
||||
---@overload fun(label:string, options: core.commandview.state)
|
||||
function CommandView:enter(label, ...)
|
||||
|
||||
function CommandView:enter(text, submit, suggest, cancel, validate)
|
||||
if self.state ~= default_state then
|
||||
return
|
||||
end
|
||||
local options = select(1, ...)
|
||||
|
||||
if type(options) ~= "table" then
|
||||
core.warn("Using CommandView:enter in a deprecated way")
|
||||
local submit, suggest, cancel, validate = ...
|
||||
options = {
|
||||
submit = submit,
|
||||
suggest = suggest,
|
||||
cancel = cancel,
|
||||
validate = validate,
|
||||
}
|
||||
end
|
||||
|
||||
-- Support deprecated CommandView:set_hidden_suggestions
|
||||
-- Remove this when set_hidden_suggestions is not supported anymore
|
||||
if options.show_suggestions == nil then
|
||||
options.show_suggestions = self.state.show_suggestions
|
||||
end
|
||||
|
||||
self.state = common.merge(default_state, options)
|
||||
|
||||
-- We need to keep the text entered with CommandView:set_text to
|
||||
-- maintain compatibility with deprecated usage, but still allow
|
||||
-- overwriting with options.text
|
||||
local old_text = self:get_text()
|
||||
if old_text ~= "" then
|
||||
core.warn("Using deprecated function CommandView:set_text")
|
||||
end
|
||||
if options.text or options.select_text then
|
||||
local text = options.text or old_text
|
||||
self:set_text(text, self.state.select_text)
|
||||
end
|
||||
-- Replace with a simple
|
||||
-- self:set_text(self.state.text, self.state.select_text)
|
||||
-- once old usage is removed
|
||||
|
||||
self.state = {
|
||||
submit = submit or noop,
|
||||
suggest = suggest or noop,
|
||||
cancel = cancel or noop,
|
||||
validate = validate or function() return true end
|
||||
}
|
||||
core.set_active_view(self)
|
||||
self:update_suggestions()
|
||||
self.gutter_text_brightness = 100
|
||||
self.label = label .. ": "
|
||||
self.label = text .. ": "
|
||||
end
|
||||
|
||||
|
||||
|
@ -246,13 +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
|
||||
self.last_text = ""
|
||||
end
|
||||
|
||||
|
||||
function CommandView:get_line_height()
|
||||
return math.floor(self:get_font():get_height() * 1.2)
|
||||
end
|
||||
|
||||
|
||||
|
@ -277,7 +185,6 @@ function CommandView:update_suggestions()
|
|||
end
|
||||
self.suggestions = res
|
||||
self.suggestion_idx = 1
|
||||
self.suggestions_offset = 1
|
||||
end
|
||||
|
||||
|
||||
|
@ -291,45 +198,35 @@ function CommandView:update()
|
|||
-- update suggestions if text has changed
|
||||
if self.last_change_id ~= self.doc:get_change_id() then
|
||||
self:update_suggestions()
|
||||
if self.state.typeahead and self.suggestions[self.suggestion_idx] then
|
||||
local current_text = self:get_text()
|
||||
local suggested_text = self.suggestions[self.suggestion_idx].text or ""
|
||||
if #self.last_text < #current_text and
|
||||
string.find(suggested_text, current_text, 1, true) == 1 then
|
||||
self:set_text(suggested_text)
|
||||
self.doc:set_selection(1, #current_text + 1, 1, math.huge)
|
||||
end
|
||||
self.last_text = current_text
|
||||
end
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
end
|
||||
|
||||
-- update gutter text color brightness
|
||||
self:move_towards("gutter_text_brightness", 0, 0.1, "commandview")
|
||||
self:move_towards("gutter_text_brightness", 0, 0.1)
|
||||
|
||||
-- update gutter width
|
||||
local dest = self:get_font():get_width(self.label) + style.padding.x
|
||||
if self.size.y <= 0 then
|
||||
self.gutter_width = dest
|
||||
else
|
||||
self:move_towards("gutter_width", dest, nil, "commandview")
|
||||
self:move_towards("gutter_width", dest)
|
||||
end
|
||||
|
||||
-- update suggestions box height
|
||||
local lh = self:get_suggestion_line_height()
|
||||
local dest = self.state.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0
|
||||
self:move_towards("suggestions_height", dest, nil, "commandview")
|
||||
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
|
||||
local dest = (self.suggestion_idx - self.suggestions_offset + 1) * self:get_suggestion_line_height()
|
||||
self:move_towards("selection_offset", dest, nil, "commandview")
|
||||
local dest = math.min(self.suggestion_idx, max_suggestions) * self:get_suggestion_line_height()
|
||||
self:move_towards("selection_offset", dest)
|
||||
|
||||
-- update size based on whether this is the active_view
|
||||
local dest = 0
|
||||
if self == core.active_view then
|
||||
dest = style.font:get_height() + style.padding.y * 2
|
||||
end
|
||||
self:move_towards(self.size, "y", dest, nil, "commandview")
|
||||
self:move_towards(self.size, "y", dest)
|
||||
end
|
||||
|
||||
|
||||
|
@ -346,7 +243,6 @@ function CommandView:draw_line_gutter(idx, x, y)
|
|||
x = x + style.padding.x
|
||||
renderer.draw_text(self:get_font(), self.label, x, y + yoffset, color)
|
||||
core.pop_clip_rect()
|
||||
return self:get_line_height()
|
||||
end
|
||||
|
||||
|
||||
|
@ -357,7 +253,6 @@ local function draw_suggestions_box(self)
|
|||
local h = math.ceil(self.suggestions_height)
|
||||
local rx, ry, rw, rh = self.position.x, self.position.y - h - dh, self.size.x, h
|
||||
|
||||
core.push_clip_rect(rx, ry, rw, rh)
|
||||
-- draw suggestions background
|
||||
if #self.suggestions > 0 then
|
||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||
|
@ -367,18 +262,20 @@ local function draw_suggestions_box(self)
|
|||
end
|
||||
|
||||
-- draw suggestion text
|
||||
local first = math.max(self.suggestions_offset, 1)
|
||||
local last = math.min(self.suggestions_offset + max_suggestions, #self.suggestions)
|
||||
for i=first, last do
|
||||
local suggestion_offset = math.max(self.suggestion_idx - max_suggestions, 0)
|
||||
core.push_clip_rect(rx, ry, rw, rh)
|
||||
local i = 1 + suggestion_offset
|
||||
while i <= #self.suggestions do
|
||||
local item = self.suggestions[i]
|
||||
local color = (i == self.suggestion_idx) and style.accent or style.text
|
||||
local y = self.position.y - (i - first + 1) * lh - dh
|
||||
local y = self.position.y - (i - suggestion_offset) * lh - dh
|
||||
common.draw_text(self:get_font(), color, item.text, nil, x, y, 0, lh)
|
||||
|
||||
if item.info then
|
||||
local w = self.size.x - x - style.padding.x
|
||||
common.draw_text(self:get_font(), style.dim, item.info, "right", x, y, w, lh)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
@ -386,7 +283,7 @@ end
|
|||
|
||||
function CommandView:draw()
|
||||
CommandView.super.draw(self)
|
||||
if self.state.show_suggestions then
|
||||
if self.show_suggestions then
|
||||
core.root_view:defer_draw(draw_suggestions_box, self)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,69 +1,27 @@
|
|||
local common = {}
|
||||
|
||||
|
||||
---Checks if the byte at offset is a UTF-8 continuation byte.
|
||||
---
|
||||
---UTF-8 encodes code points in 1 to 4 bytes.
|
||||
---For a multi-byte sequence, each byte following the start byte is a continuation byte.
|
||||
---@param s string
|
||||
---@param offset? integer The offset of the string to start searching. Defaults to 1.
|
||||
---@return boolean
|
||||
function common.is_utf8_cont(s, offset)
|
||||
local byte = s:byte(offset or 1)
|
||||
return byte >= 0x80 and byte < 0xc0
|
||||
end
|
||||
|
||||
|
||||
---Returns an iterator that yields a UTF-8 character on each iteration.
|
||||
---@param text string
|
||||
---@return fun(): string
|
||||
function common.utf8_chars(text)
|
||||
return text:gmatch("[\0-\x7f\xc2-\xf4][\x80-\xbf]*")
|
||||
end
|
||||
|
||||
|
||||
---Clamps the number n between lo and hi.
|
||||
---@param n number
|
||||
---@param lo number
|
||||
---@param hi number
|
||||
---@return number
|
||||
function common.clamp(n, lo, hi)
|
||||
return math.max(math.min(n, hi), lo)
|
||||
end
|
||||
|
||||
|
||||
---Returns a new table containing the contents of b merged into a.
|
||||
---@param a table|nil
|
||||
---@param b table?
|
||||
---@return table
|
||||
function common.merge(a, b)
|
||||
a = type(a) == "table" and a or {}
|
||||
local t = {}
|
||||
for k, v in pairs(a) do
|
||||
t[k] = v
|
||||
end
|
||||
if b and type(b) == "table" then
|
||||
for k, v in pairs(b) do
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
---Returns the value of a number rounded to the nearest integer.
|
||||
---@param n number
|
||||
---@return number
|
||||
function common.round(n)
|
||||
return n >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5)
|
||||
end
|
||||
|
||||
|
||||
---Returns the first index where a subtable in tbl has prop set.
|
||||
---If none is found, nil is returned.
|
||||
---@param tbl table
|
||||
---@param prop any
|
||||
---@return number|nil
|
||||
function common.find_index(tbl, prop)
|
||||
for i, o in ipairs(tbl) do
|
||||
if o[prop] then return i end
|
||||
|
@ -71,16 +29,6 @@ function common.find_index(tbl, prop)
|
|||
end
|
||||
|
||||
|
||||
---Returns a value between a and b on a linear scale, based on the
|
||||
---interpolation point t.
|
||||
---
|
||||
---If a and b are tables, a table containing the result for all the
|
||||
---elements in a and b is returned.
|
||||
---@param a number
|
||||
---@param b number
|
||||
---@param t number
|
||||
---@return number
|
||||
---@overload fun(a: table, b: table, t: number): table
|
||||
function common.lerp(a, b, t)
|
||||
if type(a) ~= "table" then
|
||||
return a + (b - a) * t
|
||||
|
@ -93,29 +41,11 @@ function common.lerp(a, b, t)
|
|||
end
|
||||
|
||||
|
||||
---Returns the euclidean distance between two points.
|
||||
---@param x1 number
|
||||
---@param y1 number
|
||||
---@param x2 number
|
||||
---@param y2 number
|
||||
---@return number
|
||||
function common.distance(x1, y1, x2, y2)
|
||||
return math.sqrt(((x2-x1) ^ 2)+((y2-y1) ^ 2))
|
||||
return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2))
|
||||
end
|
||||
|
||||
|
||||
---Parses a CSS color string.
|
||||
---
|
||||
---Only these formats are supported:
|
||||
---* `rgb(r, g, b)`
|
||||
---* `rgba(r, g, b, a)`
|
||||
---* `#rrggbbaa`
|
||||
---* `#rrggbb`
|
||||
---@param str string
|
||||
---@return number r
|
||||
---@return number g
|
||||
---@return number b
|
||||
---@return number a
|
||||
function common.color(str)
|
||||
local r, g, b, a = str:match("^#(%x%x)(%x%x)(%x%x)(%x?%x?)$")
|
||||
if r then
|
||||
|
@ -136,21 +66,26 @@ function common.color(str)
|
|||
end
|
||||
|
||||
|
||||
---Splices a numerically indexed table.
|
||||
---This function mutates the original table.
|
||||
---@param t any[]
|
||||
---@param at number Index at which to start splicing.
|
||||
---@param remove number Number of elements to remove.
|
||||
---@param insert? any[] A table containing elements to insert after splicing.
|
||||
function common.splice(t, at, remove, insert)
|
||||
assert(remove >= 0, "bad argument #3 to 'splice' (non-negative value expected)")
|
||||
insert = insert or {}
|
||||
local len = #insert
|
||||
if remove ~= len then table.move(t, at + remove, #t + remove, at + len) end
|
||||
table.move(insert, 1, len, at, t)
|
||||
local offset = #insert - remove
|
||||
local old_len = #t
|
||||
if offset < 0 then
|
||||
for i = at - offset, old_len - offset do
|
||||
t[i + offset] = t[i]
|
||||
end
|
||||
elseif offset > 0 then
|
||||
for i = old_len, at, -1 do
|
||||
t[i + offset] = t[i]
|
||||
end
|
||||
end
|
||||
for i, item in ipairs(insert) do
|
||||
t[at + i - 1] = item
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function compare_score(a, b)
|
||||
return a.score > b.score
|
||||
end
|
||||
|
@ -171,16 +106,6 @@ local function fuzzy_match_items(items, needle, files)
|
|||
end
|
||||
|
||||
|
||||
---Performs fuzzy matching.
|
||||
---
|
||||
---If the haystack is a string, a score ranging from 0 to 1 is returned. </br>
|
||||
---If the haystack is a table, a table containing the haystack sorted in ascending
|
||||
---order of similarity is returned.
|
||||
---@param haystack string
|
||||
---@param needle string
|
||||
---@param files boolean If true, the matching process will be performed in reverse to better match paths.
|
||||
---@return number
|
||||
---@overload fun(haystack: string[], needle: string, files: boolean): string[]
|
||||
function common.fuzzy_match(haystack, needle, files)
|
||||
if type(haystack) == "table" then
|
||||
return fuzzy_match_items(haystack, needle, files)
|
||||
|
@ -189,14 +114,6 @@ function common.fuzzy_match(haystack, needle, files)
|
|||
end
|
||||
|
||||
|
||||
---Performs fuzzy matching and returns recently used strings if needed.
|
||||
---
|
||||
---If the needle is empty, then a list of recently used strings
|
||||
---are added to the result, followed by strings from the haystack.
|
||||
---@param haystack string[]
|
||||
---@param recents string[]
|
||||
---@param needle string
|
||||
---@return string[]
|
||||
function common.fuzzy_match_with_recents(haystack, recents, needle)
|
||||
if needle == "" then
|
||||
local recents_ext = {}
|
||||
|
@ -215,42 +132,9 @@ function common.fuzzy_match_with_recents(haystack, recents, needle)
|
|||
end
|
||||
|
||||
|
||||
---Returns a list of paths that are relative to the input path.
|
||||
---
|
||||
---If a root directory is specified, the function returns paths
|
||||
---that are relative to the root directory.
|
||||
---@param text string The input path.
|
||||
---@param root? string The root directory.
|
||||
---@return string[]
|
||||
function common.path_suggest(text, root)
|
||||
if root and root:sub(-1) ~= PATHSEP then
|
||||
root = root .. PATHSEP
|
||||
end
|
||||
local path, name
|
||||
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
|
||||
path, name = text:match("^(.-)([^:"..PATHSEP.."]*)$")
|
||||
else
|
||||
path, name = text:match("^(.-)([^"..PATHSEP.."]*)$")
|
||||
end
|
||||
|
||||
local clean_dotslash = false
|
||||
-- ignore root if path is absolute
|
||||
local is_absolute = common.is_absolute_path(text)
|
||||
if not is_absolute then
|
||||
if path == "" then
|
||||
path = root or "."
|
||||
clean_dotslash = not root
|
||||
else
|
||||
path = (root or "") .. path
|
||||
end
|
||||
end
|
||||
|
||||
-- Only in Windows allow using both styles of PATHSEP
|
||||
if (PATHSEP == "\\" and not string.match(path:sub(-1), "[\\/]")) or
|
||||
(PATHSEP ~= "\\" and path:sub(-1) ~= PATHSEP) then
|
||||
path = path .. PATHSEP
|
||||
end
|
||||
local files = system.list_dir(path) or {}
|
||||
function common.path_suggest(text)
|
||||
local path, name = text:match("^(.-)([^/\\]*)$")
|
||||
local files = system.list_dir(path == "" and "." or path) or {}
|
||||
local res = {}
|
||||
for _, file in ipairs(files) do
|
||||
file = path .. file
|
||||
|
@ -259,19 +143,6 @@ function common.path_suggest(text, root)
|
|||
if info.type == "dir" then
|
||||
file = file .. PATHSEP
|
||||
end
|
||||
if root then
|
||||
-- remove root part from file path
|
||||
local s, e = file:find(root, nil, true)
|
||||
if s == 1 then
|
||||
file = file:sub(e + 1)
|
||||
end
|
||||
elseif clean_dotslash then
|
||||
-- remove added dot slash
|
||||
local s, e = file:find("." .. PATHSEP, nil, true)
|
||||
if s == 1 then
|
||||
file = file:sub(e + 1)
|
||||
end
|
||||
end
|
||||
if file:lower():find(text:lower(), nil, true) == 1 then
|
||||
table.insert(res, file)
|
||||
end
|
||||
|
@ -281,16 +152,8 @@ function common.path_suggest(text, root)
|
|||
end
|
||||
|
||||
|
||||
---Returns a list of directories that are related to a path.
|
||||
---@param text string The input path.
|
||||
---@return string[]
|
||||
function common.dir_path_suggest(text)
|
||||
local path, name
|
||||
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
|
||||
path, name = text:match("^(.-)([^:"..PATHSEP.."]*)$")
|
||||
else
|
||||
path, name = text:match("^(.-)([^"..PATHSEP.."]*)$")
|
||||
end
|
||||
local path, name = text:match("^(.-)([^/\\]*)$")
|
||||
local files = system.list_dir(path == "" and "." or path) or {}
|
||||
local res = {}
|
||||
for _, file in ipairs(files) do
|
||||
|
@ -304,18 +167,8 @@ function common.dir_path_suggest(text)
|
|||
end
|
||||
|
||||
|
||||
---Filters a list of paths to find those that are related to the input path.
|
||||
---@param text string The input path.
|
||||
---@param dir_list string[] A list of paths to filter.
|
||||
---@return string[]
|
||||
function common.dir_list_suggest(text, dir_list)
|
||||
local path, name
|
||||
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
|
||||
path, name = text:match("^(.-)([^:"..PATHSEP.."]*)$")
|
||||
else
|
||||
path, name = text:match("^(.-)([^"..PATHSEP.."]*)$")
|
||||
end
|
||||
|
||||
local path, name = text:match("^(.-)([^/\\]*)$")
|
||||
local res = {}
|
||||
for _, dir_path in ipairs(dir_list) do
|
||||
if dir_path:lower():find(text:lower(), nil, true) == 1 then
|
||||
|
@ -326,15 +179,6 @@ function common.dir_list_suggest(text, dir_list)
|
|||
end
|
||||
|
||||
|
||||
---Matches a string against a list of patterns.
|
||||
---
|
||||
---If a match was found, its start and end index is returned.
|
||||
---Otherwise, false is returned.
|
||||
---@param text string
|
||||
---@param pattern string|string[]
|
||||
---@param ... any Other options for string.find().
|
||||
---@return number|boolean start_index
|
||||
---@return number|nil end_index
|
||||
function common.match_pattern(text, pattern, ...)
|
||||
if type(pattern) == "string" then
|
||||
return text:find(pattern, ...)
|
||||
|
@ -347,24 +191,8 @@ function common.match_pattern(text, pattern, ...)
|
|||
end
|
||||
|
||||
|
||||
---Draws text onto the window.
|
||||
---The function returns the X and Y coordinates of the bottom-right
|
||||
---corner of the text.
|
||||
---@param font renderer.font
|
||||
---@param color renderer.color
|
||||
---@param text string
|
||||
---@param align string
|
||||
---| '"left"' # Align text to the left of the bounding box
|
||||
---| '"right"' # Align text to the right of the bounding box
|
||||
---| '"center"' # Center text in the bounding box
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param w number
|
||||
---@param h number
|
||||
---@return number x_advance
|
||||
---@return number y_advance
|
||||
function common.draw_text(font, color, text, align, x,y,w,h)
|
||||
local tw, th = font:get_width(text), font:get_height()
|
||||
local tw, th = font:get_width(text), font:get_height(text)
|
||||
if align == "center" then
|
||||
x = x + (w - tw) / 2
|
||||
elseif align == "right" then
|
||||
|
@ -375,16 +203,6 @@ function common.draw_text(font, color, text, align, x,y,w,h)
|
|||
end
|
||||
|
||||
|
||||
---Prints the execution time of a function.
|
||||
---
|
||||
---The execution time and percentage of frame time
|
||||
---for the function is printed to standard output. </br>
|
||||
---The frame rate is always assumed to be 60 FPS, thus
|
||||
---a value of 100% would mean that the benchmark took
|
||||
---1/60 of a second to execute.
|
||||
---@param name string
|
||||
---@param fn fun(...: any): any
|
||||
---@return any # The result returned by the function
|
||||
function common.bench(name, fn, ...)
|
||||
local start = system.get_time()
|
||||
local res = fn(...)
|
||||
|
@ -395,129 +213,34 @@ function common.bench(name, fn, ...)
|
|||
return res
|
||||
end
|
||||
|
||||
-- From gvx/Ser
|
||||
local oddvals = {[tostring(1/0)] = "1/0", [tostring(-1/0)] = "-1/0", [tostring(-(0/0))] = "-(0/0)", [tostring(0/0)] = "0/0"}
|
||||
|
||||
local function serialize(val, pretty, indent_str, escape, sort, limit, level)
|
||||
local space = pretty and " " or ""
|
||||
local indent = pretty and string.rep(indent_str, level) or ""
|
||||
local newline = pretty and "\n" or ""
|
||||
local ty = type(val)
|
||||
if ty == "string" then
|
||||
local out = string.format("%q", val)
|
||||
if escape then
|
||||
out = string.gsub(out, "\\\n", "\\n")
|
||||
out = string.gsub(out, "\\7", "\\a")
|
||||
out = string.gsub(out, "\\8", "\\b")
|
||||
out = string.gsub(out, "\\9", "\\t")
|
||||
out = string.gsub(out, "\\11", "\\v")
|
||||
out = string.gsub(out, "\\12", "\\f")
|
||||
out = string.gsub(out, "\\13", "\\r")
|
||||
end
|
||||
return out
|
||||
elseif ty == "table" then
|
||||
-- early exit
|
||||
if level >= limit then return tostring(val) end
|
||||
local next_indent = pretty and (indent .. indent_str) or ""
|
||||
function common.serialize(val)
|
||||
if type(val) == "string" then
|
||||
return string.format("%q", val)
|
||||
elseif type(val) == "table" then
|
||||
local t = {}
|
||||
for k, v in pairs(val) do
|
||||
table.insert(t,
|
||||
next_indent .. "[" ..
|
||||
serialize(k, pretty, indent_str, escape, sort, limit, level + 1) ..
|
||||
"]" .. space .. "=" .. space .. serialize(v, pretty, indent_str, escape, sort, limit, level + 1))
|
||||
table.insert(t, "[" .. common.serialize(k) .. "]=" .. common.serialize(v))
|
||||
end
|
||||
if #t == 0 then return "{}" end
|
||||
if sort then table.sort(t) end
|
||||
return "{" .. newline .. table.concat(t, "," .. newline) .. newline .. indent .. "}"
|
||||
end
|
||||
if ty == "number" then
|
||||
-- tostring is locale-dependent, so we need to replace an eventual `,` with `.`
|
||||
local res, _ = tostring(val):gsub(",", ".")
|
||||
-- handle inf/nan
|
||||
return oddvals[res] or res
|
||||
return "{" .. table.concat(t, ",") .. "}"
|
||||
end
|
||||
return tostring(val)
|
||||
end
|
||||
|
||||
|
||||
---@class common.serializeoptions
|
||||
---@field pretty boolean Enables pretty printing.
|
||||
---@field indent_str string The indentation character to use. Defaults to `" "`.
|
||||
---@field escape boolean Uses normal escape characters ("\n") instead of decimal escape sequences ("\10").
|
||||
---@field limit number Limits the depth when serializing nested tables. Defaults to `math.huge`.
|
||||
---@field sort boolean Sorts the output if it is a sortable table.
|
||||
---@field initial_indent number The initial indentation level. Defaults to 0.
|
||||
|
||||
---Serializes a value into a Lua string that is loadable with load().
|
||||
---
|
||||
---Only these basic types are supported:
|
||||
---* nil
|
||||
---* boolean
|
||||
---* number (except very large numbers and special constants, e.g. `math.huge`, `inf` and `nan`)
|
||||
---* integer
|
||||
---* string
|
||||
---* table
|
||||
---
|
||||
---@param val any
|
||||
---@param opts? common.serializeoptions
|
||||
---@return string
|
||||
function common.serialize(val, opts)
|
||||
opts = opts or {}
|
||||
local indent_str = opts.indent_str or " "
|
||||
local initial_indent = opts.initial_indent or 0
|
||||
local indent = opts.pretty and string.rep(indent_str, initial_indent) or ""
|
||||
local limit = (opts.limit or math.huge) + initial_indent
|
||||
return indent .. serialize(val, opts.pretty, indent_str,
|
||||
opts.escape, opts.sort, limit, initial_indent)
|
||||
end
|
||||
|
||||
|
||||
---Returns the last portion of a path.
|
||||
---@param path string
|
||||
---@return string
|
||||
function common.basename(path)
|
||||
-- a path should never end by / or \ except if it is '/' (unix root) or
|
||||
-- 'X:\' (windows drive)
|
||||
return path:match("[^"..PATHSEP.."]+$") or path
|
||||
return path:match("[^\\/]+$") or path
|
||||
end
|
||||
|
||||
|
||||
---Returns the base path with the pathsep, if needed.
|
||||
---@param path string
|
||||
---@return string
|
||||
function common.basepath(path)
|
||||
-- Check for AmigaOS 4 and MorphOS if the last character is semicolon
|
||||
-- In these systems the volume name doesn't have a / or \ after the name
|
||||
-- but it is like VOLUME:
|
||||
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") and (string.sub(path, -1) == ":") then
|
||||
return path
|
||||
end
|
||||
return path .. PATHSEP
|
||||
end
|
||||
|
||||
|
||||
---Returns the directory name of a path.
|
||||
---If the path doesn't have a directory, this function may return nil.
|
||||
---@param path string
|
||||
---@return string|nil
|
||||
-- can return nil if there is no directory part in the path
|
||||
function common.dirname(path)
|
||||
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
|
||||
local drive, relpath = path:match('^([%w%s]*:)(.+)')
|
||||
if drive and relpath then
|
||||
local dir = relpath:match("(.+)["..PATHSEP.."][^"..PATHSEP.."]+$")
|
||||
if dir then
|
||||
return drive .. dir
|
||||
end
|
||||
end
|
||||
return path
|
||||
end
|
||||
return path:match("(.+)["..PATHSEP.."][^"..PATHSEP.."]+$")
|
||||
return path:match("(.+)[\\/][^\\/]+$")
|
||||
end
|
||||
|
||||
|
||||
---Returns a path where the user's home directory is replaced by `"~"`.
|
||||
---@param text string
|
||||
---@return string
|
||||
function common.home_encode(text)
|
||||
if HOME and string.find(text, HOME, 1, true) == 1 then
|
||||
local dir_pos = #HOME + 1
|
||||
|
@ -531,9 +254,6 @@ function common.home_encode(text)
|
|||
end
|
||||
|
||||
|
||||
---Returns a list of paths where the user's home directory is replaced by `"~"`.
|
||||
---@param paths string[] A list of paths to encode
|
||||
---@return string[]
|
||||
function common.home_encode_list(paths)
|
||||
local t = {}
|
||||
for i = 1, #paths do
|
||||
|
@ -543,69 +263,41 @@ function common.home_encode_list(paths)
|
|||
end
|
||||
|
||||
|
||||
---Expands the `"~"` prefix in a path into the user's home directory.
|
||||
---This function is not guaranteed to return an absolute path.
|
||||
---@param text string
|
||||
---@return string
|
||||
function common.home_expand(text)
|
||||
if text == nil then
|
||||
return HOME
|
||||
end
|
||||
return HOME and text:gsub("^~", HOME) or text
|
||||
end
|
||||
|
||||
|
||||
local function split_on_slash(s, sep_pattern)
|
||||
local t = {}
|
||||
if s:match("^["..PATHSEP.."]") then
|
||||
if s:match("^[/\\]") then
|
||||
t[#t + 1] = ""
|
||||
end
|
||||
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
|
||||
local drive = s:match("^([%w%s]*:)")
|
||||
if drive then
|
||||
t[#t + 1] = ""
|
||||
s = s:gsub("^" .. drive, "")
|
||||
end
|
||||
end
|
||||
for fragment in string.gmatch(s, "([^"..PATHSEP.."]+)") do
|
||||
for fragment in string.gmatch(s, "([^/\\]+)") do
|
||||
t[#t + 1] = fragment
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
---Normalizes the drive letter in a Windows path to uppercase.
|
||||
---This function expects an absolute path, e.g. a path from `system.absolute_path`.
|
||||
---
|
||||
---This function is needed because the path returned by `system.absolute_path`
|
||||
---may contain drive letters in upper or lowercase.
|
||||
---@param filename string|nil The input path.
|
||||
---@return string|nil
|
||||
-- The filename argument given to the function is supposed to
|
||||
-- come from system.absolute_path and as such should be an
|
||||
-- absolute path without . or .. elements.
|
||||
-- This function exists because on Windows the drive letter returned
|
||||
-- by system.absolute_path is sometimes with a lower case and sometimes
|
||||
-- with an upper case to we normalize to upper case.
|
||||
function common.normalize_volume(filename)
|
||||
if not filename then return end
|
||||
if PATHSEP == '\\' then
|
||||
local drive, rem = filename:match('^([a-zA-Z]:\\)(.-)'..PATHSEP..'?$')
|
||||
local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
|
||||
if drive then
|
||||
return drive:upper() .. rem
|
||||
end
|
||||
end
|
||||
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
|
||||
local drive, rem = filename:match('^([%w%s]*:)(.-)' .. PATHSEP .. '?$')
|
||||
if drive then
|
||||
return drive .. rem
|
||||
end
|
||||
end
|
||||
return filename
|
||||
end
|
||||
|
||||
|
||||
---Normalizes a path into the same format across platforms.
|
||||
---
|
||||
---On Windows, all drive letters are converted to uppercase.
|
||||
---UNC paths with drive letters are converted back to ordinary Windows paths.
|
||||
---All path separators (`"/"`, `"\\"`) are converted to platform-specific ones.
|
||||
---@param filename string|nil
|
||||
---@return string|nil
|
||||
function common.normalize_path(filename)
|
||||
if not filename then return end
|
||||
local volume
|
||||
|
@ -620,11 +312,6 @@ function common.normalize_path(filename)
|
|||
volume, filename = drive, rem
|
||||
end
|
||||
end
|
||||
elseif (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
|
||||
local drive, relpath = filename:match('^([%w%s]*:)(.+)')
|
||||
if relpath then
|
||||
volume, filename = drive, relpath
|
||||
end
|
||||
else
|
||||
local relpath = filename:match('^/(.+)')
|
||||
if relpath then
|
||||
|
@ -651,32 +338,13 @@ function common.normalize_path(filename)
|
|||
end
|
||||
|
||||
|
||||
---Checks whether a path is absolute or relative.
|
||||
---@param path string
|
||||
---@return boolean
|
||||
function common.is_absolute_path(path)
|
||||
return path:sub(1, 1) == PATHSEP or path:match("^(%a):\\") or path:match('^([%w%s]*):')
|
||||
end
|
||||
|
||||
|
||||
---Checks whether a path belongs to a parent directory.
|
||||
---@param filename string The path to check.
|
||||
---@param path string The parent path.
|
||||
---@return boolean
|
||||
function common.path_belongs_to(filename, path)
|
||||
return string.find(filename, common.basepath(path), 1, true) == 1
|
||||
return string.find(filename, path .. PATHSEP, 1, true) == 1
|
||||
end
|
||||
|
||||
|
||||
---Checks whether a path is relative to another path.
|
||||
---@param ref_dir string The path to check against.
|
||||
---@param dir string The input path.
|
||||
---@return boolean
|
||||
function common.relative_path(ref_dir, dir)
|
||||
local drive_pattern = "^(%a):\\"
|
||||
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
|
||||
drive_pattern = "^([%w%s]*:)"
|
||||
end
|
||||
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:\
|
||||
|
@ -700,11 +368,6 @@ function common.relative_path(ref_dir, dir)
|
|||
end
|
||||
|
||||
|
||||
---Creates a directory recursively if necessary.
|
||||
---@param path string
|
||||
---@return boolean success
|
||||
---@return string|nil error
|
||||
---@return string|nil path The path where an error occured.
|
||||
function common.mkdirp(path)
|
||||
local stat = system.get_file_info(path)
|
||||
if stat and stat.type then
|
||||
|
@ -714,12 +377,12 @@ function common.mkdirp(path)
|
|||
while path and path ~= "" do
|
||||
local success_mkdir = system.mkdir(path)
|
||||
if success_mkdir then break end
|
||||
local updir, basedir = path:match("(.*)["..PATHSEP.."](.+)$")
|
||||
local updir, basedir = path:match("(.*)[/\\](.+)$")
|
||||
table.insert(subdirs, 1, basedir or path)
|
||||
path = updir
|
||||
end
|
||||
for _, dirname in ipairs(subdirs) do
|
||||
path = path and common.basepath(path) .. dirname or dirname
|
||||
path = path and path .. PATHSEP .. dirname or dirname
|
||||
if not system.mkdir(path) then
|
||||
return false, "cannot create directory", path
|
||||
end
|
||||
|
@ -727,13 +390,6 @@ function common.mkdirp(path)
|
|||
return true
|
||||
end
|
||||
|
||||
|
||||
---Removes a path.
|
||||
---@param path string
|
||||
---@param recursively boolean If true, the function will attempt to remove everything in the specified path.
|
||||
---@return boolean success
|
||||
---@return string|nil error
|
||||
---@return string|nil path The path where the error occured.
|
||||
function common.rm(path, recursively)
|
||||
local stat = system.get_file_info(path)
|
||||
if not stat or (stat.type ~= "file" and stat.type ~= "dir") then
|
||||
|
@ -781,5 +437,4 @@ function common.rm(path, recursively)
|
|||
return true
|
||||
end
|
||||
|
||||
|
||||
return common
|
||||
|
|
|
@ -1,51 +1,26 @@
|
|||
local common = require "core.common"
|
||||
|
||||
local config = {}
|
||||
|
||||
config.fps = 60
|
||||
config.max_log_items = 800
|
||||
config.max_log_items = 80
|
||||
config.message_timeout = 5
|
||||
config.mouse_wheel_scroll = 50 * SCALE
|
||||
config.animate_drag_scroll = false
|
||||
config.scroll_past_end = true
|
||||
---@type "expanded" | "contracted" | false @Force the scrollbar status of the DocView
|
||||
config.force_scrollbar_status = false
|
||||
config.file_size_limit = 10
|
||||
config.ignore_files = {
|
||||
-- folders
|
||||
"^%.svn/", "^%.git/", "^%.hg/", "^CVS/", "^%.Trash/", "^%.Trash%-.*/",
|
||||
"^node_modules/", "^%.cache/", "^__pycache__/",
|
||||
-- files
|
||||
"%.pyc$", "%.pyo$", "%.exe$", "%.dll$", "%.obj$", "%.o$",
|
||||
"%.a$", "%.lib$", "%.so$", "%.dylib$", "%.ncb$", "%.sdf$",
|
||||
"%.suo$", "%.pdb$", "%.idb$", "%.class$", "%.psd$", "%.db$",
|
||||
"^desktop%.ini$", "^%.DS_Store$", "^%.directory$",
|
||||
}
|
||||
config.ignore_files = {"^%."}
|
||||
config.symbol_pattern = "[%a_][%w_]*"
|
||||
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||
config.undo_merge_timeout = 0.3
|
||||
config.max_undos = 10000
|
||||
config.max_tabs = 8
|
||||
config.always_show_tabs = true
|
||||
-- Possible values: false, true, "no_selection"
|
||||
config.highlight_current_line = true
|
||||
config.line_height = 1.2
|
||||
config.indent_size = 2
|
||||
config.tab_type = "soft"
|
||||
config.keep_newline_whitespace = false
|
||||
config.line_limit = 80
|
||||
config.max_symbols = 4000
|
||||
config.max_project_files = 2000
|
||||
config.transitions = true
|
||||
config.disabled_transitions = {
|
||||
scroll = false,
|
||||
commandview = false,
|
||||
contextmenu = false,
|
||||
logview = false,
|
||||
nagbar = false,
|
||||
tabs = false,
|
||||
tab_drag = false,
|
||||
statusbar = false,
|
||||
}
|
||||
config.animation_rate = 1.0
|
||||
config.blink_period = 0.8
|
||||
config.disable_blink = false
|
||||
|
@ -54,52 +29,12 @@ config.borderless = false
|
|||
config.tab_close_button = true
|
||||
config.max_clicks = 3
|
||||
|
||||
-- set as true to be able to test non supported plugins
|
||||
config.skip_plugins_version = false
|
||||
|
||||
-- holds the plugins real config table
|
||||
local plugins_config = {}
|
||||
|
||||
-- virtual representation of plugins config table
|
||||
-- Disable plugin loading setting to false the config entry
|
||||
-- of the same name.
|
||||
config.plugins = {}
|
||||
|
||||
-- allows virtual access to the plugins config table
|
||||
setmetatable(config.plugins, {
|
||||
__index = function(_, k)
|
||||
if not plugins_config[k] then
|
||||
plugins_config[k] = { enabled = true, config = {} }
|
||||
end
|
||||
if plugins_config[k].enabled ~= false then
|
||||
return plugins_config[k].config
|
||||
end
|
||||
return false
|
||||
end,
|
||||
__newindex = function(_, k, v)
|
||||
if not plugins_config[k] then
|
||||
plugins_config[k] = { enabled = nil, config = {} }
|
||||
end
|
||||
if v == false and package.loaded["plugins."..k] then
|
||||
local core = require "core"
|
||||
core.warn("[%s] is already enabled, restart the editor for the change to take effect", k)
|
||||
return
|
||||
elseif plugins_config[k].enabled == false and v ~= false then
|
||||
plugins_config[k].enabled = true
|
||||
end
|
||||
if v == false then
|
||||
plugins_config[k].enabled = false
|
||||
elseif type(v) == "table" then
|
||||
plugins_config[k].enabled = true
|
||||
plugins_config[k].config = common.merge(plugins_config[k].config, v)
|
||||
end
|
||||
end,
|
||||
__pairs = function()
|
||||
return coroutine.wrap(function()
|
||||
for name, status in pairs(plugins_config) do
|
||||
coroutine.yield(name, status.config)
|
||||
end
|
||||
end)
|
||||
end
|
||||
})
|
||||
|
||||
config.plugins.trimwhitespace = false
|
||||
config.plugins.lineguide = false
|
||||
config.plugins.drawwhitespace = false
|
||||
|
||||
return config
|
||||
|
|
|
@ -5,52 +5,28 @@ local config = require "core.config"
|
|||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local Object = require "core.object"
|
||||
local View = require "core.view"
|
||||
|
||||
local border_width = 1
|
||||
local divider_width = 1
|
||||
local divider_padding = 5
|
||||
local DIVIDER = {}
|
||||
|
||||
---An item in the context menu.
|
||||
---@class core.contextmenu.item
|
||||
---@field text string
|
||||
---@field info string|nil If provided, this text is displayed on the right side of the menu.
|
||||
---@field command string|fun()
|
||||
|
||||
---A list of items with the same predicate.
|
||||
---@see core.command.predicate
|
||||
---@class core.contextmenu.itemset
|
||||
---@field predicate core.command.predicate
|
||||
---@field items core.contextmenu.item[]
|
||||
|
||||
---A context menu.
|
||||
---@class core.contextmenu : core.object
|
||||
---@field itemset core.contextmenu.itemset[]
|
||||
---@field show_context_menu boolean
|
||||
---@field selected number
|
||||
---@field position core.view.position
|
||||
---@field current_scale number
|
||||
local ContextMenu = Object:extend()
|
||||
|
||||
---A unique value representing the divider in a context menu.
|
||||
ContextMenu.DIVIDER = DIVIDER
|
||||
|
||||
---Creates a new context menu.
|
||||
function ContextMenu:new()
|
||||
self.itemset = {}
|
||||
self.show_context_menu = false
|
||||
self.selected = -1
|
||||
self.height = 0
|
||||
self.position = { x = 0, y = 0 }
|
||||
self.current_scale = SCALE
|
||||
end
|
||||
|
||||
local function get_item_size(item)
|
||||
local lw, lh
|
||||
if item == DIVIDER then
|
||||
lw = 0
|
||||
lh = divider_width + divider_padding * SCALE * 2
|
||||
lh = divider_width
|
||||
else
|
||||
lw = style.font:get_width(item.text)
|
||||
if item.info then
|
||||
|
@ -61,10 +37,18 @@ local function get_item_size(item)
|
|||
return lw, lh
|
||||
end
|
||||
|
||||
local function update_items_size(items, update_binding)
|
||||
local width, height = 0, 0
|
||||
for _, item in ipairs(items) do
|
||||
if update_binding and item ~= DIVIDER then
|
||||
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.get_binding(item.command)
|
||||
end
|
||||
local lw, lh = get_item_size(item)
|
||||
|
@ -73,21 +57,9 @@ local function update_items_size(items, update_binding)
|
|||
end
|
||||
width = width + style.padding.x * 2
|
||||
items.width, items.height = width, height
|
||||
end
|
||||
|
||||
---Registers a list of items into the context menu with a predicate.
|
||||
---@param predicate core.command.predicate
|
||||
---@param items core.contextmenu.item[]
|
||||
function ContextMenu:register(predicate, items)
|
||||
predicate = command.generate_predicate(predicate)
|
||||
update_items_size(items, true)
|
||||
table.insert(self.itemset, { predicate = predicate, items = items })
|
||||
end
|
||||
|
||||
---Shows the context menu.
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@return boolean # If true, the context menu is shown.
|
||||
function ContextMenu:show(x, y)
|
||||
self.items = nil
|
||||
local items_list = { width = 0, height = 0 }
|
||||
|
@ -110,28 +82,27 @@ function ContextMenu:show(x, y)
|
|||
local w, h = self.items.width, self.items.height
|
||||
|
||||
-- by default the box is opened on the right and below
|
||||
x = common.clamp(x, 0, core.root_view.size.x - w - style.padding.x)
|
||||
y = common.clamp(y, 0, core.root_view.size.y - h)
|
||||
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
|
||||
core.request_cursor("arrow")
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Hides the context menu.
|
||||
function ContextMenu:hide()
|
||||
self.show_context_menu = false
|
||||
self.items = nil
|
||||
self.selected = -1
|
||||
self.height = 0
|
||||
core.request_cursor(core.active_view.cursor)
|
||||
end
|
||||
|
||||
---Returns an iterator that iterates over each context menu item and their dimensions.
|
||||
---@return fun(): number, core.contextmenu.item, number, number, number, number
|
||||
function ContextMenu:each_item()
|
||||
local x, y, w = self.position.x, self.position.y, self.items.width
|
||||
local oy = y
|
||||
|
@ -145,12 +116,8 @@ function ContextMenu:each_item()
|
|||
end)
|
||||
end
|
||||
|
||||
---Event handler for mouse movements.
|
||||
---@param px any
|
||||
---@param py any
|
||||
---@return boolean # true if the event is caught.
|
||||
function ContextMenu:on_mouse_moved(px, py)
|
||||
if not self.show_context_menu then return false end
|
||||
if not self.show_context_menu then return end
|
||||
|
||||
self.selected = -1
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
|
@ -159,11 +126,12 @@ function ContextMenu:on_mouse_moved(px, py)
|
|||
break
|
||||
end
|
||||
end
|
||||
if self.selected >= 0 then
|
||||
core.request_cursor("arrow")
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
---Event handler for when the selection is confirmed.
|
||||
---@param item core.contextmenu.item
|
||||
function ContextMenu:on_selected(item)
|
||||
if type(item.command) == "string" then
|
||||
command.perform(item.command)
|
||||
|
@ -172,94 +140,56 @@ function ContextMenu:on_selected(item)
|
|||
end
|
||||
end
|
||||
|
||||
local function change_value(value, change)
|
||||
return value + change
|
||||
end
|
||||
|
||||
---Selects the the previous item.
|
||||
function ContextMenu:focus_previous()
|
||||
self.selected = (self.selected == -1 or self.selected == 1) and #self.items or change_value(self.selected, -1)
|
||||
if self:get_item_selected() == DIVIDER then
|
||||
self.selected = change_value(self.selected, -1)
|
||||
end
|
||||
end
|
||||
|
||||
---Selects the next item.
|
||||
function ContextMenu:focus_next()
|
||||
self.selected = (self.selected == -1 or self.selected == #self.items) and 1 or change_value(self.selected, 1)
|
||||
if self:get_item_selected() == DIVIDER then
|
||||
self.selected = change_value(self.selected, 1)
|
||||
end
|
||||
end
|
||||
|
||||
---Gets the currently selected item.
|
||||
---@return core.contextmenu.item|nil
|
||||
function ContextMenu:get_item_selected()
|
||||
return (self.items or {})[self.selected]
|
||||
end
|
||||
|
||||
---Hides the context menu and performs the command if an item is selected.
|
||||
function ContextMenu:call_selected_item()
|
||||
local selected = self:get_item_selected()
|
||||
self:hide()
|
||||
if selected then
|
||||
self:on_selected(selected)
|
||||
end
|
||||
end
|
||||
|
||||
---Event handler for mouse press.
|
||||
---@param button core.view.mousebutton
|
||||
---@param px number
|
||||
---@param py number
|
||||
---@param clicks number
|
||||
---@return boolean # true if the event is caught.
|
||||
function ContextMenu:on_mouse_pressed(button, px, py, clicks)
|
||||
function ContextMenu:on_mouse_pressed(button, x, y, clicks)
|
||||
local selected = (self.items or {})[self.selected]
|
||||
local caught = false
|
||||
|
||||
if self.show_context_menu then
|
||||
if button == "left" then
|
||||
local selected = self:get_item_selected()
|
||||
if selected then
|
||||
self:on_selected(selected)
|
||||
end
|
||||
end
|
||||
self:hide()
|
||||
caught = true
|
||||
else
|
||||
if button == "right" then
|
||||
caught = self:show(px, py)
|
||||
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
|
||||
|
||||
---@type fun(self: table, k: string, dest: number, rate?: number, name?: string)
|
||||
ContextMenu.move_towards = View.move_towards
|
||||
|
||||
---Event handler for content update.
|
||||
function ContextMenu:update()
|
||||
if self.show_context_menu then
|
||||
self:move_towards("height", self.items.height, nil, "contextmenu")
|
||||
-- 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
|
||||
|
||||
---Draws the context menu.
|
||||
---
|
||||
---This wraps `ContextMenu:draw_context_menu()`.
|
||||
---@see core.contextmenu.draw_context_menu
|
||||
function ContextMenu:draw()
|
||||
if not self.show_context_menu then return end
|
||||
if self.current_scale ~= SCALE then
|
||||
update_items_size(self.items)
|
||||
for _, set in ipairs(self.itemset) do
|
||||
update_items_size(set.items)
|
||||
end
|
||||
self.current_scale = SCALE
|
||||
end
|
||||
core.root_view:defer_draw(self.draw_context_menu, self)
|
||||
end
|
||||
|
||||
---Draws the context menu.
|
||||
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
|
||||
|
@ -275,7 +205,7 @@ function ContextMenu:draw_context_menu()
|
|||
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
if item == DIVIDER then
|
||||
renderer.draw_rect(x, y + divider_padding * SCALE, w, divider_width, style.divider)
|
||||
renderer.draw_rect(x, y, w, h, style.caret)
|
||||
else
|
||||
if i == self.selected then
|
||||
renderer.draw_rect(x, y, w, h, style.selection)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local dirwatch = {}
|
||||
|
||||
function dirwatch:__index(idx)
|
||||
|
@ -14,8 +13,8 @@ function dirwatch.new()
|
|||
watched = {},
|
||||
reverse_watched = {},
|
||||
monitor = dirmonitor.new(),
|
||||
single_watch_top = nil,
|
||||
single_watch_count = 0
|
||||
windows_watch_top = nil,
|
||||
windows_watch_count = 0
|
||||
}
|
||||
setmetatable(t, dirwatch)
|
||||
return t
|
||||
|
@ -30,32 +29,29 @@ end
|
|||
-- Should be called on every directory in a subdirectory.
|
||||
-- In windows, this is a no-op for anything underneath a top-level directory,
|
||||
-- but code should be called anyway, so we can ensure that we have a proper
|
||||
-- experience across all platforms. Should be an absolute path.
|
||||
-- Can also be called on individual files, though this should be used sparingly,
|
||||
-- so as not to run into system limits (like in the autoreload plugin).
|
||||
-- experience across all platforms.
|
||||
function dirwatch:watch(directory, bool)
|
||||
if bool == false then return self:unwatch(directory) end
|
||||
local info = system.get_file_info(directory)
|
||||
if not info then return end
|
||||
if not self.watched[directory] and not self.scanned[directory] then
|
||||
if self.monitor:mode() == "single" then
|
||||
if info.type ~= "dir" then return self:scan(directory) end
|
||||
if not self.single_watch_top or directory:find(self.single_watch_top, 1, true) ~= 1 then
|
||||
if PLATFORM == "Windows" then
|
||||
if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then
|
||||
-- Get the highest level of directory that is common to this directory, and the original.
|
||||
local target = directory
|
||||
while self.single_watch_top and self.single_watch_top:find(target, 1, true) ~= 1 do
|
||||
while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do
|
||||
target = common.dirname(target)
|
||||
end
|
||||
if target ~= self.single_watch_top then
|
||||
if target ~= self.windows_watch_top then
|
||||
local value = self.monitor:watch(target)
|
||||
if value and value < 0 then
|
||||
return self:scan(directory)
|
||||
end
|
||||
self.single_watch_top = target
|
||||
self.windows_watch_top = target
|
||||
self.windows_watch_count = self.windows_watch_count + 1
|
||||
end
|
||||
end
|
||||
self.single_watch_count = self.single_watch_count + 1
|
||||
self.watched[directory] = true
|
||||
elseif PLATFORM == "AmigaOS 4" then
|
||||
return self:scan(directory)
|
||||
else
|
||||
local value = self.monitor:watch(directory)
|
||||
-- If for whatever reason, we can't watch this directory, revert back to scanning.
|
||||
|
@ -69,17 +65,16 @@ function dirwatch:watch(directory, bool)
|
|||
end
|
||||
end
|
||||
|
||||
-- this should be an absolute path
|
||||
function dirwatch:unwatch(directory)
|
||||
if self.watched[directory] then
|
||||
if self.monitor:mode() == "multiple" then
|
||||
self.monitor:unwatch(self.watched[directory])
|
||||
self.reverse_watched[directory] = nil
|
||||
if PLATFORM ~= "Windows" then
|
||||
self.monitor.unwatch(directory)
|
||||
self.reverse_watched[self.watched[directory]] = nil
|
||||
else
|
||||
self.single_watch_count = self.single_watch_count - 1
|
||||
if self.single_watch_count == 0 then
|
||||
self.single_watch_top = nil
|
||||
self.monitor:unwatch(directory)
|
||||
self.windows_watch_count = self.windows_watch_count - 1
|
||||
if self.windows_watch_count == 0 then
|
||||
self.windows_watch_top = nil
|
||||
self.monitor.unwatch(directory)
|
||||
end
|
||||
end
|
||||
self.watched[directory] = nil
|
||||
|
@ -90,151 +85,28 @@ end
|
|||
|
||||
-- designed to be run inside a coroutine.
|
||||
function dirwatch:check(change_callback, scan_time, wait_time)
|
||||
local had_change = false
|
||||
local last_error
|
||||
self.monitor:check(function(id)
|
||||
had_change = true
|
||||
if self.monitor:mode() == "single" then
|
||||
local path = common.dirname(id)
|
||||
if not string.match(id, "^/") and not string.match(id, "^%a:[/\\]") then
|
||||
path = common.dirname(self.single_watch_top .. PATHSEP .. id)
|
||||
end
|
||||
change_callback(path)
|
||||
if PLATFORM == "Windows" then
|
||||
change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id))
|
||||
elseif self.reverse_watched[id] then
|
||||
change_callback(self.reverse_watched[id])
|
||||
end
|
||||
end, function(err)
|
||||
last_error = err
|
||||
end)
|
||||
if last_error ~= nil then error(last_error) end
|
||||
local start_time = system.get_time()
|
||||
for directory, old_modified in pairs(self.scanned) do
|
||||
if old_modified then
|
||||
local info = system.get_file_info(directory)
|
||||
local new_modified = info and info.modified
|
||||
if old_modified ~= new_modified then
|
||||
local new_modified = system.get_file_info(directory).modified
|
||||
if old_modified < new_modified then
|
||||
change_callback(directory)
|
||||
had_change = true
|
||||
self.scanned[directory] = new_modified
|
||||
end
|
||||
end
|
||||
if system.get_time() - start_time > (scan_time or 0.01) then
|
||||
coroutine.yield(wait_time or 0.01)
|
||||
if system.get_time() - start_time > scan_time then
|
||||
coroutine.yield(wait_time)
|
||||
start_time = system.get_time()
|
||||
end
|
||||
end
|
||||
return had_change
|
||||
end
|
||||
|
||||
|
||||
-- inspect config.ignore_files patterns and prepare ready to use entries.
|
||||
local function compile_ignore_files()
|
||||
local ipatterns = config.ignore_files
|
||||
local compiled = {}
|
||||
-- config.ignore_files could be a simple string...
|
||||
if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end
|
||||
for i, pattern in ipairs(ipatterns) do
|
||||
-- we ignore malformed pattern that raise an error
|
||||
if pcall(string.match, "a", pattern) then
|
||||
table.insert(compiled, {
|
||||
use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end
|
||||
-- An '/' or '/$' at the end means we want to match a directory.
|
||||
match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value
|
||||
pattern = pattern -- get the actual pattern
|
||||
})
|
||||
end
|
||||
end
|
||||
return compiled
|
||||
end
|
||||
|
||||
|
||||
local function fileinfo_pass_filter(info, ignore_compiled)
|
||||
if info.size >= config.file_size_limit * 1e6 then return false end
|
||||
local basename = common.basename(info.filename)
|
||||
-- replace '\' with '/' for Windows where PATHSEP = '\'
|
||||
local fullname = "/" .. info.filename:gsub("\\", "/")
|
||||
for _, compiled in ipairs(ignore_compiled) do
|
||||
local test = compiled.use_path and fullname or basename
|
||||
if compiled.match_dir then
|
||||
if info.type == "dir" and string.match(test .. "/", compiled.pattern) then
|
||||
return false
|
||||
end
|
||||
else
|
||||
if string.match(test, compiled.pattern) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function compare_file(a, b)
|
||||
return system.path_compare(a.filename, a.type, b.filename, b.type)
|
||||
end
|
||||
|
||||
|
||||
-- compute a file's info entry completed with "filename" to be used
|
||||
-- in project scan or falsy if it shouldn't appear in the list.
|
||||
local function get_project_file_info(root, file, ignore_compiled)
|
||||
local info = system.get_file_info(common.basepath(root) .. file)
|
||||
|
||||
-- info can be not nil but info.type may be nil if is neither a file neither
|
||||
-- a directory, for example for /dev/* entries on linux.
|
||||
if info and info.type then
|
||||
info.filename = file
|
||||
return fileinfo_pass_filter(info, ignore_compiled) and info
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- "root" will by an absolute path without trailing '/'
|
||||
-- "path" will be a path starting without '/' and without trailing '/'
|
||||
-- or the empty string.
|
||||
-- It identifies a sub-path within "root".
|
||||
-- The current path location will therefore always be: root .. path.
|
||||
-- When recursing, "root" will always be the same, only "path" will change.
|
||||
-- Returns a list of file "items". In each item the "filename" will be the
|
||||
-- complete file path relative to "root" *without* the trailing '/', and without the starting '/'.
|
||||
function dirwatch.get_directory_files(dir, root, path, entries_count, recurse_pred)
|
||||
local t = {}
|
||||
local t0 = system.get_time()
|
||||
local ignore_compiled = compile_ignore_files()
|
||||
|
||||
local all = system.list_dir(common.basepath(root) .. path)
|
||||
|
||||
if not all then return nil end
|
||||
local entries = { }
|
||||
for _, file in ipairs(all) do
|
||||
local info = get_project_file_info(root, (path ~= "" and (path .. PATHSEP) or "") .. file, ignore_compiled)
|
||||
if info then
|
||||
table.insert(entries, info)
|
||||
end
|
||||
end
|
||||
table.sort(entries, compare_file)
|
||||
|
||||
local recurse_complete = true
|
||||
for _, info in ipairs(entries) do
|
||||
table.insert(t, info)
|
||||
entries_count = entries_count + 1
|
||||
if info.type == "dir" then
|
||||
if recurse_pred(dir, info.filename, entries_count, system.get_time() - t0) then
|
||||
local t_rec, complete, n = dirwatch.get_directory_files(dir, root, info.filename, entries_count, recurse_pred)
|
||||
recurse_complete = recurse_complete and complete
|
||||
if n ~= nil then
|
||||
entries_count = n
|
||||
for _, info_rec in ipairs(t_rec) do
|
||||
table.insert(t, info_rec)
|
||||
end
|
||||
end
|
||||
else
|
||||
recurse_complete = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return t, recurse_complete, entries_count
|
||||
end
|
||||
|
||||
|
||||
return dirwatch
|
||||
|
||||
|
|
|
@ -10,58 +10,34 @@ local Highlighter = Object:extend()
|
|||
|
||||
function Highlighter:new(doc)
|
||||
self.doc = doc
|
||||
self.running = false
|
||||
self:reset()
|
||||
end
|
||||
|
||||
-- init incremental syntax highlighting
|
||||
function Highlighter:start()
|
||||
if self.running then return end
|
||||
self.running = true
|
||||
-- init incremental syntax highlighting
|
||||
core.add_thread(function()
|
||||
while self.first_invalid_line <= self.max_wanted_line do
|
||||
local max = math.min(self.first_invalid_line + 40, self.max_wanted_line)
|
||||
local retokenized_from
|
||||
for i = self.first_invalid_line, max do
|
||||
local state = (i > 1) and self.lines[i - 1].state
|
||||
local line = self.lines[i]
|
||||
if line and line.resume and (line.init_state ~= state or line.text ~= self.doc.lines[i]) then
|
||||
-- Reset the progress if no longer valid
|
||||
line.resume = nil
|
||||
end
|
||||
if not (line and line.init_state == state and line.text == self.doc.lines[i] and not line.resume) then
|
||||
retokenized_from = retokenized_from or i
|
||||
self.lines[i] = self:tokenize_line(i, state, line and line.resume)
|
||||
if self.lines[i].resume then
|
||||
self.first_invalid_line = i
|
||||
goto yield
|
||||
while true do
|
||||
if self.first_invalid_line > self.max_wanted_line then
|
||||
self.max_wanted_line = 0
|
||||
coroutine.yield(1 / config.fps)
|
||||
|
||||
else
|
||||
local max = math.min(self.first_invalid_line + 40, self.max_wanted_line)
|
||||
|
||||
for i = self.first_invalid_line, max do
|
||||
local state = (i > 1) and self.lines[i - 1].state
|
||||
local line = self.lines[i]
|
||||
if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
|
||||
self.lines[i] = self:tokenize_line(i, state)
|
||||
end
|
||||
elseif retokenized_from then
|
||||
self:update_notify(retokenized_from, i - retokenized_from - 1)
|
||||
retokenized_from = nil
|
||||
end
|
||||
end
|
||||
|
||||
self.first_invalid_line = max + 1
|
||||
::yield::
|
||||
if retokenized_from then
|
||||
self:update_notify(retokenized_from, max - retokenized_from)
|
||||
self.first_invalid_line = max + 1
|
||||
core.redraw = true
|
||||
coroutine.yield()
|
||||
end
|
||||
core.redraw = true
|
||||
coroutine.yield()
|
||||
end
|
||||
self.max_wanted_line = 0
|
||||
self.running = false
|
||||
end, self)
|
||||
end
|
||||
|
||||
local function set_max_wanted_lines(self, amount)
|
||||
self.max_wanted_line = amount
|
||||
if self.first_invalid_line <= self.max_wanted_line then
|
||||
self:start()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Highlighter:reset()
|
||||
self.lines = {}
|
||||
|
@ -78,7 +54,7 @@ end
|
|||
|
||||
function Highlighter:invalidate(idx)
|
||||
self.first_invalid_line = math.min(self.first_invalid_line, idx)
|
||||
set_max_wanted_lines(self, math.min(self.max_wanted_line, #self.doc.lines))
|
||||
self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines)
|
||||
end
|
||||
|
||||
function Highlighter:insert_notify(line, n)
|
||||
|
@ -95,16 +71,12 @@ function Highlighter:remove_notify(line, n)
|
|||
common.splice(self.lines, line, n)
|
||||
end
|
||||
|
||||
function Highlighter:update_notify(line, n)
|
||||
-- plugins can hook here to be notified that lines have been retokenized
|
||||
end
|
||||
|
||||
|
||||
function Highlighter:tokenize_line(idx, state, resume)
|
||||
function Highlighter:tokenize_line(idx, state)
|
||||
local res = {}
|
||||
res.init_state = state
|
||||
res.text = self.doc.lines[idx]
|
||||
res.tokens, res.state, res.resume = tokenizer.tokenize(self.doc.syntax, res.text, state, resume)
|
||||
res.tokens, res.state = tokenizer.tokenize(self.doc.syntax, res.text, state)
|
||||
return res
|
||||
end
|
||||
|
||||
|
@ -115,9 +87,8 @@ function Highlighter:get_line(idx)
|
|||
local prev = self.lines[idx - 1]
|
||||
line = self:tokenize_line(idx, prev and prev.state)
|
||||
self.lines[idx] = line
|
||||
self:update_notify(idx, 0)
|
||||
end
|
||||
set_max_wanted_lines(self, math.max(self.max_wanted_line, idx))
|
||||
self.max_wanted_line = math.max(self.max_wanted_line, idx)
|
||||
return line
|
||||
end
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ local syntax = require "core.syntax"
|
|||
local config = require "core.config"
|
||||
local common = require "core.common"
|
||||
|
||||
---@class core.doc : core.object
|
||||
|
||||
local Doc = Object:extend()
|
||||
|
||||
|
||||
|
@ -33,7 +33,7 @@ end
|
|||
function Doc:reset()
|
||||
self.lines = { "\n" }
|
||||
self.selections = { 1, 1, 1, 1 }
|
||||
self.last_selection = 1
|
||||
self.cursor_clipboard = {}
|
||||
self.undo_stack = { idx = 1 }
|
||||
self.redo_stack = { idx = 1 }
|
||||
self.clean_change_id = 1
|
||||
|
@ -44,12 +44,7 @@ end
|
|||
|
||||
function Doc:reset_syntax()
|
||||
local header = self:get_text(1, 1, self:position_offset(1, 1, 128))
|
||||
local path = self.abs_filename
|
||||
if not path and self.filename then
|
||||
path = common.basepath(core.project_dir) .. self.filename
|
||||
end
|
||||
if path then path = common.normalize_path(path) end
|
||||
local syn = syntax.get(path, header)
|
||||
local syn = syntax.get(self.filename or "", header)
|
||||
if self.syntax ~= syn then
|
||||
self.syntax = syn
|
||||
self.highlighter:soft_reset()
|
||||
|
@ -60,7 +55,6 @@ end
|
|||
function Doc:set_filename(filename, abs_filename)
|
||||
self.filename = filename
|
||||
self.abs_filename = abs_filename
|
||||
self:reset_syntax()
|
||||
end
|
||||
|
||||
|
||||
|
@ -86,23 +80,11 @@ function Doc:load(filename)
|
|||
end
|
||||
|
||||
|
||||
function Doc:reload()
|
||||
if self.filename then
|
||||
local sel = { self:get_selection() }
|
||||
self:load(self.filename)
|
||||
self:clean()
|
||||
self:set_selection(table.unpack(sel))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
else
|
||||
assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path")
|
||||
end
|
||||
local fp = assert( io.open(filename, "wb") )
|
||||
for _, line in ipairs(self.lines) do
|
||||
|
@ -112,6 +94,7 @@ function Doc:save(filename, abs_filename)
|
|||
fp:close()
|
||||
self:set_filename(filename, abs_filename)
|
||||
self.new_file = false
|
||||
self:reset_syntax()
|
||||
self:clean()
|
||||
end
|
||||
|
||||
|
@ -123,7 +106,6 @@ end
|
|||
|
||||
function Doc:is_dirty()
|
||||
if self.new_file then
|
||||
if self.filename then return true end
|
||||
return #self.lines > 1 or #self.lines[1] > 1
|
||||
else
|
||||
return self.clean_change_id ~= self:get_change_id()
|
||||
|
@ -148,37 +130,13 @@ function Doc:get_change_id()
|
|||
return self.undo_stack.idx
|
||||
end
|
||||
|
||||
local function sort_positions(line1, col1, line2, col2)
|
||||
if line1 > line2 or line1 == line2 and col1 > col2 then
|
||||
return line2, col2, line1, col1, true
|
||||
end
|
||||
return line1, col1, line2, col2, false
|
||||
end
|
||||
|
||||
-- Cursor section. Cursor indices are *only* valid during a get_selections() call.
|
||||
-- Cursors will always be iterated in order from top to bottom. Through normal operation
|
||||
-- curors can never swap positions; only merge or split, or change their position in cursor
|
||||
-- order.
|
||||
function Doc:get_selection(sort)
|
||||
local line1, col1, line2, col2, swap = self:get_selection_idx(self.last_selection, sort)
|
||||
if not line1 then
|
||||
line1, col1, line2, col2, swap = self:get_selection_idx(1, sort)
|
||||
end
|
||||
return line1, col1, line2, col2, swap
|
||||
end
|
||||
|
||||
|
||||
---Get the selection specified by `idx`
|
||||
---@param idx integer @the index of the selection to retrieve
|
||||
---@param sort? boolean @whether to sort the selection returned
|
||||
---@return integer,integer,integer,integer,boolean? @line1, col1, line2, col2, was the selection sorted
|
||||
function Doc:get_selection_idx(idx, sort)
|
||||
local line1, col1, line2, col2 = self.selections[idx*4-3], self.selections[idx*4-2], self.selections[idx*4-1], self.selections[idx*4]
|
||||
if line1 and sort then
|
||||
return sort_positions(line1, col1, line2, col2)
|
||||
else
|
||||
return line1, col1, line2, col2
|
||||
end
|
||||
local idx, line1, col1, line2, col2 = self:get_selections(sort)({ self.selections, sort }, 0)
|
||||
return line1, col1, line2, col2, sort
|
||||
end
|
||||
|
||||
function Doc:get_selection_text(limit)
|
||||
|
@ -212,6 +170,13 @@ function Doc:sanitize_selection()
|
|||
end
|
||||
end
|
||||
|
||||
local function sort_positions(line1, col1, line2, col2)
|
||||
if line1 > line2 or line1 == line2 and col1 > col2 then
|
||||
return line2, col2, line1, col1
|
||||
end
|
||||
return line1, col1, line2, col2
|
||||
end
|
||||
|
||||
function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm)
|
||||
assert(not line2 == not col2, "expected 3 or 5 arguments")
|
||||
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
|
||||
|
@ -230,22 +195,11 @@ function Doc:add_selection(line1, col1, line2, col2, swap)
|
|||
end
|
||||
end
|
||||
self:set_selections(target, line1, col1, line2, col2, swap, 0)
|
||||
self.last_selection = target
|
||||
end
|
||||
|
||||
|
||||
function Doc:remove_selection(idx)
|
||||
if self.last_selection >= idx then
|
||||
self.last_selection = self.last_selection - 1
|
||||
end
|
||||
common.splice(self.selections, (idx - 1) * 4 + 1, 4)
|
||||
end
|
||||
|
||||
|
||||
function Doc:set_selection(line1, col1, line2, col2, swap)
|
||||
self.selections = {}
|
||||
self.selections, self.cursor_clipboard = {}, {}
|
||||
self:set_selections(1, line1, col1, line2, col2, swap)
|
||||
self.last_selection = 1
|
||||
end
|
||||
|
||||
function Doc:merge_cursors(idx)
|
||||
|
@ -254,13 +208,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)
|
||||
if self.last_selection >= (i+3)/4 then
|
||||
self.last_selection = self.last_selection - 1
|
||||
end
|
||||
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)
|
||||
|
@ -282,13 +235,9 @@ end
|
|||
-- End of cursor seciton.
|
||||
|
||||
function Doc:sanitize_position(line, col)
|
||||
local nlines = #self.lines
|
||||
if line > nlines then
|
||||
return nlines, #self.lines[nlines]
|
||||
elseif line < 1 then
|
||||
return 1, 1
|
||||
end
|
||||
return line, common.clamp(col, 1, #self.lines[line])
|
||||
line = common.clamp(line, 1, #self.lines)
|
||||
col = common.clamp(col, 1, #self.lines[line])
|
||||
return line, col
|
||||
end
|
||||
|
||||
|
||||
|
@ -407,7 +356,7 @@ 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
|
||||
|
@ -437,61 +386,25 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
|||
local before = self.lines[line1]:sub(1, col1 - 1)
|
||||
local after = self.lines[line2]:sub(col2)
|
||||
|
||||
local line_removal = line2 - line1
|
||||
local col_removal = col2 - col1
|
||||
|
||||
-- splice line into line array
|
||||
common.splice(self.lines, line1, line_removal + 1, { before .. after })
|
||||
|
||||
local merge = false
|
||||
|
||||
-- keep selections in correct positions: each pair (line, col)
|
||||
-- * remains unchanged if before the deleted text
|
||||
-- * is set to (line1, col1) if in the deleted text
|
||||
-- * is set to (line1, col - col_removal) if on line2 but out of the deleted text
|
||||
-- * is set to (line - line_removal, col) if after line2
|
||||
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 cline2 < line1 then break end
|
||||
local l1, c1, l2, c2 = cline1, ccol1, cline2, ccol2
|
||||
|
||||
if cline1 > line1 or (cline1 == line1 and ccol1 > col1) then
|
||||
if cline1 > line2 then
|
||||
l1 = l1 - line_removal
|
||||
else
|
||||
l1 = line1
|
||||
c1 = (cline1 == line2 and ccol1 > col2) and c1 - col_removal or col1
|
||||
end
|
||||
end
|
||||
|
||||
if cline2 > line1 or (cline2 == line1 and ccol2 > col1) then
|
||||
if cline2 > line2 then
|
||||
l2 = l2 - line_removal
|
||||
else
|
||||
l2 = line1
|
||||
c2 = (cline2 == line2 and ccol2 > col2) and c2 - col_removal or col1
|
||||
end
|
||||
end
|
||||
|
||||
if l1 == line1 and c1 == col1 then merge = true end
|
||||
self:set_selections(idx, l1, c1, l2, c2)
|
||||
end
|
||||
|
||||
if merge then
|
||||
self:merge_cursors()
|
||||
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:remove_notify(line1, line_removal)
|
||||
self.highlighter:remove_notify(line1, line2 - line1)
|
||||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
|
||||
function Doc:insert(line, col, text)
|
||||
self.redo_stack = { idx = 1 }
|
||||
-- Reset the clean id when we're pushing something new before it
|
||||
if self:get_change_id() < self.clean_change_id then
|
||||
self.clean_change_id = -1
|
||||
end
|
||||
line, col = self:sanitize_position(line, col)
|
||||
self:raw_insert(line, col, text, self.undo_stack, system.get_time())
|
||||
self:on_text_change("insert")
|
||||
|
@ -528,21 +441,9 @@ function Doc:text_input(text, idx)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:ime_text_editing(text, start, length, idx)
|
||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
self:delete_to_cursor(sidx)
|
||||
end
|
||||
self:insert(line1, col1, text)
|
||||
self:set_selections(sidx, line1, col1 + #text, line1, col1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
|
||||
local old_text = self:get_text(line1, col1, line2, col2)
|
||||
local new_text, res = fn(old_text)
|
||||
local new_text, n = fn(old_text)
|
||||
if old_text ~= new_text then
|
||||
self:insert(line2, col2, new_text)
|
||||
self:remove(line1, col1, line2, col2)
|
||||
|
@ -551,22 +452,22 @@ function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
|
|||
self:set_selections(idx, line1, col1, line2, col2)
|
||||
end
|
||||
end
|
||||
return res
|
||||
return n
|
||||
end
|
||||
|
||||
function Doc:replace(fn)
|
||||
local has_selection, results = false, { }
|
||||
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
|
||||
results[idx] = self:replace_cursor(idx, line1, col1, line2, col2, fn)
|
||||
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))
|
||||
results[1] = self:replace_cursor(1, 1, 1, #self.lines, #self.lines[#self.lines], fn)
|
||||
n = n + self:replace_cursor(1, 1, 1, #self.lines, #self.lines[#self.lines], fn)
|
||||
end
|
||||
return results
|
||||
return n
|
||||
end
|
||||
|
||||
|
||||
|
@ -647,12 +548,10 @@ function Doc:indent_text(unindent, line1, col1, line2, col2)
|
|||
if unindent or has_selection or in_beginning_whitespace then
|
||||
local l1d, l2d = #self.lines[line1], #self.lines[line2]
|
||||
for line = line1, line2 do
|
||||
if not has_selection or #self.lines[line] > 1 then -- don't indent empty lines in a selection
|
||||
local e, rnded = self:get_line_indent(self.lines[line], unindent)
|
||||
self:remove(line, 1, line, (e or 0) + 1)
|
||||
self:insert(line, 1,
|
||||
unindent and rnded:sub(1, #rnded - #text) or rnded .. text)
|
||||
end
|
||||
local e, rnded = self:get_line_indent(self.lines[line], unindent)
|
||||
self:remove(line, 1, line, (e or 0) + 1)
|
||||
self:insert(line, 1,
|
||||
unindent and rnded:sub(1, #rnded - #text) or rnded .. text)
|
||||
end
|
||||
l1d, l2d = #self.lines[line1] - l1d, #self.lines[line2] - l2d
|
||||
if (unindent or in_beginning_whitespace) and not has_selection then
|
||||
|
|
|
@ -66,18 +66,7 @@ function search.find(doc, line, col, text, opt)
|
|||
s, e = search_func(line_text, pattern, col, plain)
|
||||
end
|
||||
if s then
|
||||
local line2 = line
|
||||
-- If we've matched the newline too,
|
||||
-- return until the initial character of the next line.
|
||||
if e >= #doc.lines[line] then
|
||||
line2 = line + 1
|
||||
e = 0
|
||||
end
|
||||
-- Avoid returning matches that go beyond the last line.
|
||||
-- This is needed to avoid selecting the "last" newline.
|
||||
if line2 <= #doc.lines then
|
||||
return line, s, line2, e + 1
|
||||
end
|
||||
return line, s, line, e + 1
|
||||
end
|
||||
col = opt.reverse and -1 or 1
|
||||
end
|
||||
|
|
|
@ -4,11 +4,9 @@ local config = require "core.config"
|
|||
local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local translate = require "core.doc.translate"
|
||||
local ime = require "core.ime"
|
||||
local View = require "core.view"
|
||||
|
||||
---@class core.docview : core.view
|
||||
---@field super core.view
|
||||
|
||||
local DocView = View:extend()
|
||||
|
||||
DocView.context = "session"
|
||||
|
@ -31,9 +29,6 @@ DocView.translate = {
|
|||
end,
|
||||
|
||||
["next_page"] = function(doc, line, col, dv)
|
||||
if line == #doc.lines then
|
||||
return #doc.lines, #doc.lines[line]
|
||||
end
|
||||
local min, max = dv:get_visible_line_range()
|
||||
return line + (max - min), 1
|
||||
end,
|
||||
|
@ -61,33 +56,25 @@ function DocView:new(doc)
|
|||
self.doc = assert(doc)
|
||||
self.font = "code_font"
|
||||
self.last_x_offset = {}
|
||||
self.ime_selection = { from = 0, size = 0 }
|
||||
self.ime_status = false
|
||||
self.hovering_gutter = false
|
||||
self.v_scrollbar:set_forced_status(config.force_scrollbar_status)
|
||||
self.h_scrollbar:set_forced_status(config.force_scrollbar_status)
|
||||
end
|
||||
|
||||
|
||||
function DocView:try_close(do_close)
|
||||
if self.doc:is_dirty()
|
||||
and #core.get_views_referencing_doc(self.doc) == 1 then
|
||||
core.command_view:enter("Unsaved Changes; Confirm Close", {
|
||||
submit = function(_, item)
|
||||
if item.text:match("^[cC]") then
|
||||
do_close()
|
||||
elseif item.text:match("^[sS]") then
|
||||
self.doc:save()
|
||||
do_close()
|
||||
end
|
||||
end,
|
||||
suggest = function(text)
|
||||
local items = {}
|
||||
if not text:find("^[^cC]") then table.insert(items, "Close Without Saving") end
|
||||
if not text:find("^[^sS]") then table.insert(items, "Save And Close") end
|
||||
return items
|
||||
core.command_view:enter("Unsaved Changes; Confirm Close", function(_, item)
|
||||
if item.text:match("^[cC]") then
|
||||
do_close()
|
||||
elseif item.text:match("^[sS]") then
|
||||
self.doc:save()
|
||||
do_close()
|
||||
end
|
||||
})
|
||||
end, function(text)
|
||||
local items = {}
|
||||
if not text:find("^[^cC]") then table.insert(items, "Close Without Saving") end
|
||||
if not text:find("^[^sS]") then table.insert(items, "Save And Close") end
|
||||
return items
|
||||
end)
|
||||
else
|
||||
do_close()
|
||||
end
|
||||
|
@ -112,16 +99,11 @@ end
|
|||
|
||||
function DocView:get_scrollable_size()
|
||||
if not config.scroll_past_end then
|
||||
local _, _, _, h_scroll = self.h_scrollbar:get_track_rect()
|
||||
return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2 + h_scroll
|
||||
return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2
|
||||
end
|
||||
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
|
||||
end
|
||||
|
||||
function DocView:get_h_scrollable_size()
|
||||
return math.huge
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_font()
|
||||
return style[self.font]
|
||||
|
@ -139,18 +121,14 @@ function DocView:get_gutter_width()
|
|||
end
|
||||
|
||||
|
||||
function DocView:get_line_screen_position(line, col)
|
||||
function DocView:get_line_screen_position(idx)
|
||||
local x, y = self:get_content_offset()
|
||||
local lh = self:get_line_height()
|
||||
local gw = self:get_gutter_width()
|
||||
y = y + (line-1) * lh + style.padding.y
|
||||
if col then
|
||||
return x + gw + self:get_col_x_offset(line, col), y
|
||||
else
|
||||
return x + gw, y
|
||||
end
|
||||
return x + gw, y + (idx-1) * lh + style.padding.y
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_line_text_y_offset()
|
||||
local lh = self:get_line_height()
|
||||
local th = self:get_font():get_height()
|
||||
|
@ -161,36 +139,24 @@ end
|
|||
function DocView:get_visible_line_range()
|
||||
local x, y, x2, y2 = self:get_content_bounds()
|
||||
local lh = self:get_line_height()
|
||||
local minline = math.max(1, math.floor((y - style.padding.y) / lh) + 1)
|
||||
local maxline = math.min(#self.doc.lines, math.floor((y2 - style.padding.y) / lh) + 1)
|
||||
local minline = math.max(1, math.floor(y / lh))
|
||||
local maxline = math.min(#self.doc.lines, math.floor(y2 / lh) + 1)
|
||||
return minline, maxline
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_col_x_offset(line, col)
|
||||
local default_font = self:get_font()
|
||||
local _, indent_size = self.doc:get_indent_info()
|
||||
default_font:set_tab_size(indent_size)
|
||||
local column = 1
|
||||
local xoffset = 0
|
||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
if font ~= default_font then font:set_tab_size(indent_size) end
|
||||
local length = #text
|
||||
if column + length <= col then
|
||||
xoffset = xoffset + font:get_width(text)
|
||||
column = column + length
|
||||
if column >= col then
|
||||
for char in common.utf8_chars(text) do
|
||||
if column == col then
|
||||
return xoffset
|
||||
end
|
||||
else
|
||||
for char in common.utf8_chars(text) do
|
||||
if column >= col then
|
||||
return xoffset
|
||||
end
|
||||
xoffset = xoffset + font:get_width(char)
|
||||
column = column + #char
|
||||
end
|
||||
xoffset = xoffset + font:get_width(char)
|
||||
column = column + #char
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -203,27 +169,16 @@ function DocView:get_x_offset_col(line, x)
|
|||
|
||||
local xoffset, last_i, i = 0, 1, 1
|
||||
local default_font = self:get_font()
|
||||
local _, indent_size = self.doc:get_indent_info()
|
||||
default_font:set_tab_size(indent_size)
|
||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
if font ~= default_font then font:set_tab_size(indent_size) end
|
||||
local width = font:get_width(text)
|
||||
-- Don't take the shortcut if the width matches x,
|
||||
-- because we need last_i which should be calculated using utf-8.
|
||||
if xoffset + width < x then
|
||||
xoffset = xoffset + width
|
||||
i = i + #text
|
||||
else
|
||||
for char in common.utf8_chars(text) do
|
||||
local w = font:get_width(char)
|
||||
if xoffset >= x then
|
||||
return (xoffset - x > w / 2) and last_i or i
|
||||
end
|
||||
xoffset = xoffset + w
|
||||
last_i = i
|
||||
i = i + #char
|
||||
for char in common.utf8_chars(text) do
|
||||
local w = font:get_width(char)
|
||||
if xoffset >= x then
|
||||
return (xoffset - x > w / 2) and last_i or i
|
||||
end
|
||||
xoffset = xoffset + w
|
||||
last_i = i
|
||||
i = i + #char
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -243,10 +198,8 @@ end
|
|||
function DocView:scroll_to_line(line, ignore_if_visible, instant)
|
||||
local min, max = self:get_visible_line_range()
|
||||
if not (ignore_if_visible and line > min and line < max) then
|
||||
local x, y = self:get_line_screen_position(line)
|
||||
local ox, oy = self:get_content_offset()
|
||||
local _, _, _, scroll_h = self.h_scrollbar:get_track_rect()
|
||||
self.scroll.to.y = math.max(0, y - oy - (self.size.y - scroll_h) / 2)
|
||||
local lh = self:get_line_height()
|
||||
self.scroll.to.y = math.max(0, lh * (line - 1) - self.size.y / 2)
|
||||
if instant then
|
||||
self.scroll.y = self.scroll.to.y
|
||||
end
|
||||
|
@ -255,36 +208,28 @@ end
|
|||
|
||||
|
||||
function DocView:scroll_to_make_visible(line, col)
|
||||
local _, oy = self:get_content_offset()
|
||||
local _, ly = self:get_line_screen_position(line, col)
|
||||
local lh = self:get_line_height()
|
||||
local _, _, _, scroll_h = self.h_scrollbar:get_track_rect()
|
||||
self.scroll.to.y = common.clamp(self.scroll.to.y, ly - oy - self.size.y + scroll_h + lh * 2, ly - oy - lh)
|
||||
local min = self:get_line_height() * (line - 1)
|
||||
local max = self:get_line_height() * (line + 2) - self.size.y
|
||||
self.scroll.to.y = math.min(self.scroll.to.y, min)
|
||||
self.scroll.to.y = math.max(self.scroll.to.y, max)
|
||||
local gw = self:get_gutter_width()
|
||||
local xoffset = self:get_col_x_offset(line, col)
|
||||
local xmargin = 3 * self:get_font():get_width(' ')
|
||||
local xsup = xoffset + gw + xmargin
|
||||
local xinf = xoffset - xmargin
|
||||
local _, _, scroll_w = self.v_scrollbar:get_track_rect()
|
||||
local size_x = math.max(0, self.size.x - scroll_w)
|
||||
if xsup > self.scroll.x + size_x then
|
||||
self.scroll.to.x = xsup - size_x
|
||||
if xsup > self.scroll.x + self.size.x then
|
||||
self.scroll.to.x = xsup - self.size.x
|
||||
elseif xinf < self.scroll.x then
|
||||
self.scroll.to.x = math.max(0, xinf)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function DocView:on_mouse_moved(x, y, ...)
|
||||
DocView.super.on_mouse_moved(self, x, y, ...)
|
||||
|
||||
self.hovering_gutter = false
|
||||
local gw = self:get_gutter_width()
|
||||
|
||||
if self:scrollbar_hovering() or self:scrollbar_dragging() then
|
||||
if self:scrollbar_overlaps_point(x, y) or self.dragging_scrollbar then
|
||||
self.cursor = "arrow"
|
||||
elseif gw > 0 and x >= self.position.x and x <= (self.position.x + gw) then
|
||||
self.cursor = "arrow"
|
||||
self.hovering_gutter = true
|
||||
else
|
||||
self.cursor = "ibeam"
|
||||
end
|
||||
|
@ -326,31 +271,8 @@ function DocView:mouse_selection(doc, snap_type, line1, col1, line2, col2)
|
|||
end
|
||||
|
||||
|
||||
function DocView:on_mouse_pressed(button, x, y, clicks)
|
||||
if button ~= "left" or not self.hovering_gutter then
|
||||
return DocView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||
end
|
||||
local line = self:resolve_screen_position(x, y)
|
||||
if keymap.modkeys["shift"] then
|
||||
local sline, scol, sline2, scol2 = self.doc:get_selection(true)
|
||||
if line > sline then
|
||||
self.doc:set_selection(sline, 1, line, #self.doc.lines[line])
|
||||
else
|
||||
self.doc:set_selection(line, 1, sline2, #self.doc.lines[sline2])
|
||||
end
|
||||
else
|
||||
if clicks == 1 then
|
||||
self.doc:set_selection(line, 1, line, 1)
|
||||
elseif clicks == 2 then
|
||||
self.doc:set_selection(line, 1, line, #self.doc.lines[line])
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function DocView:on_mouse_released(...)
|
||||
DocView.super.on_mouse_released(self, ...)
|
||||
function DocView:on_mouse_released(button)
|
||||
DocView.super.on_mouse_released(self, button)
|
||||
self.mouse_selecting = nil
|
||||
end
|
||||
|
||||
|
@ -359,58 +281,16 @@ function DocView:on_text_input(text)
|
|||
self.doc:text_input(text)
|
||||
end
|
||||
|
||||
function DocView:on_ime_text_editing(text, start, length)
|
||||
self.doc:ime_text_editing(text, start, length)
|
||||
self.ime_status = #text > 0
|
||||
self.ime_selection.from = start
|
||||
self.ime_selection.size = length
|
||||
|
||||
-- Set the composition bounding box that the system IME
|
||||
-- will consider when drawing its interface
|
||||
local line1, col1, line2, col2 = self.doc:get_selection(true)
|
||||
local col = math.min(col1, col2)
|
||||
self:update_ime_location()
|
||||
self:scroll_to_make_visible(line1, col + start)
|
||||
end
|
||||
|
||||
---Update the composition bounding box that the system IME
|
||||
---will consider when drawing its interface
|
||||
function DocView:update_ime_location()
|
||||
if not self.ime_status then return end
|
||||
|
||||
local line1, col1, line2, col2 = self.doc:get_selection(true)
|
||||
local x, y = self:get_line_screen_position(line1)
|
||||
local h = self:get_line_height()
|
||||
local col = math.min(col1, col2)
|
||||
|
||||
local x1, x2 = 0, 0
|
||||
|
||||
if self.ime_selection.size > 0 then
|
||||
-- focus on a part of the text
|
||||
local from = col + self.ime_selection.from
|
||||
local to = from + self.ime_selection.size
|
||||
x1 = self:get_col_x_offset(line1, from)
|
||||
x2 = self:get_col_x_offset(line1, to)
|
||||
else
|
||||
-- focus the whole text
|
||||
x1 = self:get_col_x_offset(line1, col1)
|
||||
x2 = self:get_col_x_offset(line2, col2)
|
||||
end
|
||||
|
||||
ime.set_location(x + x1, y, x2 - x1, h)
|
||||
end
|
||||
|
||||
function DocView:update()
|
||||
-- scroll to make caret visible and reset blink timer if it moved
|
||||
local line1, col1, line2, col2 = self.doc:get_selection()
|
||||
if (line1 ~= self.last_line1 or col1 ~= self.last_col1 or
|
||||
line2 ~= self.last_line2 or col2 ~= self.last_col2) and self.size.x > 0 then
|
||||
if core.active_view == self and not ime.editing then
|
||||
self:scroll_to_make_visible(line1, col1)
|
||||
local line, col = self.doc:get_selection()
|
||||
if (line ~= self.last_line or col ~= self.last_col) and self.size.x > 0 then
|
||||
if core.active_view == self then
|
||||
self:scroll_to_make_visible(line, col)
|
||||
end
|
||||
core.blink_reset()
|
||||
self.last_line1, self.last_col1 = line1, col1
|
||||
self.last_line2, self.last_col2 = line2, col2
|
||||
self.last_line, self.last_col = line, col
|
||||
end
|
||||
|
||||
-- update blink timer
|
||||
|
@ -423,8 +303,6 @@ function DocView:update()
|
|||
core.blink_timer = tb
|
||||
end
|
||||
|
||||
self:update_ime_location()
|
||||
|
||||
DocView.super.update(self)
|
||||
end
|
||||
|
||||
|
@ -435,24 +313,14 @@ function DocView:draw_line_highlight(x, y)
|
|||
end
|
||||
|
||||
|
||||
function DocView:draw_line_text(line, x, y)
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
local default_font = self:get_font()
|
||||
local tx, ty = x, y + self:get_line_text_y_offset()
|
||||
local last_token = nil
|
||||
local tokens = self.doc.highlighter:get_line(line).tokens
|
||||
local tokens_count = #tokens
|
||||
if tokens[tokens_count] ~= nil and string.sub(tokens[tokens_count], -1) == "\n" then
|
||||
last_token = tokens_count - 1
|
||||
end
|
||||
for tidx, type, text in self.doc.highlighter:each_token(line) do
|
||||
for _, type, text in self.doc.highlighter:each_token(idx) do
|
||||
local color = style.syntax[type]
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
-- do not render newline, fixes issue #1164
|
||||
if tidx == last_token then text = text:sub(1, -2) end
|
||||
tx = renderer.draw_text(font, text, tx, ty, color)
|
||||
if tx > self.position.x + self.size.x then break end
|
||||
end
|
||||
return self:get_line_height()
|
||||
end
|
||||
|
||||
function DocView:draw_caret(x, y)
|
||||
|
@ -460,37 +328,28 @@ function DocView:draw_caret(x, y)
|
|||
renderer.draw_rect(x, y, style.caret_width, lh, style.caret)
|
||||
end
|
||||
|
||||
function DocView:draw_line_body(line, x, y)
|
||||
function DocView:draw_line_body(idx, x, y)
|
||||
-- draw highlight if any selection ends on this line
|
||||
local draw_highlight = false
|
||||
local hcl = config.highlight_current_line
|
||||
if hcl ~= false then
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
|
||||
if line1 == line then
|
||||
if hcl == "no_selection" then
|
||||
if (line1 ~= line2) or (col1 ~= col2) then
|
||||
draw_highlight = false
|
||||
break
|
||||
end
|
||||
end
|
||||
draw_highlight = true
|
||||
break
|
||||
end
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
|
||||
if line1 == idx then
|
||||
draw_highlight = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if draw_highlight and core.active_view == self then
|
||||
if draw_highlight and config.highlight_current_line and core.active_view == self then
|
||||
self:draw_line_highlight(x + self.scroll.x, y)
|
||||
end
|
||||
|
||||
-- draw selection if it overlaps this line
|
||||
local lh = self:get_line_height()
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
||||
if line >= line1 and line <= line2 then
|
||||
local text = self.doc.lines[line]
|
||||
if line1 ~= line then col1 = 1 end
|
||||
if line2 ~= line then col2 = #text + 1 end
|
||||
local x1 = x + self:get_col_x_offset(line, col1)
|
||||
local x2 = x + self:get_col_x_offset(line, col2)
|
||||
if idx >= line1 and idx <= line2 then
|
||||
local text = self.doc.lines[idx]
|
||||
if line1 ~= idx then col1 = 1 end
|
||||
if line2 ~= idx then col2 = #text + 1 end
|
||||
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()
|
||||
if x1 ~= x2 then
|
||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||
end
|
||||
|
@ -498,46 +357,21 @@ function DocView:draw_line_body(line, x, y)
|
|||
end
|
||||
|
||||
-- draw line's text
|
||||
return self:draw_line_text(line, x, y)
|
||||
self:draw_line_text(idx, x, y)
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw_line_gutter(line, x, y, width)
|
||||
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 line >= line1 and line <= line2 then
|
||||
if idx >= line1 and idx <= line2 then
|
||||
color = style.line_number2
|
||||
break
|
||||
end
|
||||
end
|
||||
local yoffset = self:get_line_text_y_offset()
|
||||
x = x + style.padding.x
|
||||
local lh = self:get_line_height()
|
||||
common.draw_text(self:get_font(), color, line, "right", x, y, width, lh)
|
||||
return lh
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw_ime_decoration(line1, col1, line2, col2)
|
||||
local x, y = self:get_line_screen_position(line1)
|
||||
local line_size = math.max(1, SCALE)
|
||||
local lh = self:get_line_height()
|
||||
|
||||
-- Draw IME underline
|
||||
local x1 = self:get_col_x_offset(line1, col1)
|
||||
local x2 = self:get_col_x_offset(line2, col2)
|
||||
renderer.draw_rect(x + math.min(x1, x2), y + lh - line_size, math.abs(x1 - x2), line_size, style.text)
|
||||
|
||||
-- Draw IME selection
|
||||
local col = math.min(col1, col2)
|
||||
local from = col + self.ime_selection.from
|
||||
local to = from + self.ime_selection.size
|
||||
x1 = self:get_col_x_offset(line1, from)
|
||||
if from ~= to then
|
||||
x2 = self:get_col_x_offset(line1, to)
|
||||
line_size = style.caret_width
|
||||
renderer.draw_rect(x + math.min(x1, x2), y + lh - line_size, math.abs(x1 - x2), line_size, style.caret)
|
||||
end
|
||||
self:draw_caret(x + x1, y)
|
||||
common.draw_text(self:get_font(), color, idx, "right", x, y + yoffset, width, self:get_line_height())
|
||||
end
|
||||
|
||||
|
||||
|
@ -546,16 +380,13 @@ function DocView:draw_overlay()
|
|||
local minline, maxline = self:get_visible_line_range()
|
||||
-- draw caret if it overlaps this line
|
||||
local T = config.blink_period
|
||||
for _, line1, col1, line2, col2 in self.doc:get_selections() do
|
||||
if line1 >= minline and line1 <= maxline
|
||||
for _, line, col in self.doc:get_selections() do
|
||||
if line >= minline and line <= maxline
|
||||
and system.window_has_focus() then
|
||||
if ime.editing then
|
||||
self:draw_ime_decoration(line1, col1, line2, col2)
|
||||
else
|
||||
if config.disable_blink
|
||||
or (core.blink_timer - core.blink_start) % T < T / 2 then
|
||||
self:draw_caret(self:get_line_screen_position(line1, col1))
|
||||
end
|
||||
if config.disable_blink
|
||||
or (core.blink_timer - core.blink_start) % T < T / 2 then
|
||||
local x, y = self:get_line_screen_position(line)
|
||||
self:draw_caret(x + self:get_col_x_offset(line, col), y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -573,7 +404,8 @@ function DocView:draw()
|
|||
local x, y = self:get_line_screen_position(minline)
|
||||
local gw, gpad = self:get_gutter_width()
|
||||
for i = minline, maxline do
|
||||
y = y + (self:draw_line_gutter(i, self.position.x, y, gpad and gw - gpad or gw) or lh)
|
||||
self:draw_line_gutter(i, self.position.x, y, gpad and gw - gpad or gw)
|
||||
y = y + lh
|
||||
end
|
||||
|
||||
local pos = self.position
|
||||
|
@ -582,7 +414,8 @@ function DocView:draw()
|
|||
-- right side it is redundant with the Node's clip.
|
||||
core.push_clip_rect(pos.x + gw, pos.y, self.size.x - gw, self.size.y)
|
||||
for i = minline, maxline do
|
||||
y = y + (self:draw_line_body(i, x, y) or lh)
|
||||
self:draw_line_body(i, x, y)
|
||||
y = y + lh
|
||||
end
|
||||
self:draw_overlay()
|
||||
core.pop_clip_rect()
|
||||
|
|
|
@ -2,34 +2,22 @@ local style = require "core.style"
|
|||
local keymap = require "core.keymap"
|
||||
local View = require "core.view"
|
||||
|
||||
---@class core.emptyview : core.view
|
||||
---@field super core.view
|
||||
local EmptyView = View:extend()
|
||||
|
||||
local function draw_text(x, y, color)
|
||||
local th = style.big_font:get_height()
|
||||
local dh = 2 * th + style.padding.y * 2
|
||||
local x1, y1 = x, y + (dh - th) / 2
|
||||
x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color)
|
||||
renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color)
|
||||
x = x + style.padding.x
|
||||
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
|
||||
local lines = {
|
||||
{ fmt = "%s to run a command", cmd = "core:find-command" },
|
||||
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
|
||||
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
|
||||
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
|
||||
}
|
||||
local th = style.big_font:get_height()
|
||||
local dh = 2 * th + style.padding.y * 2
|
||||
local x1, y1 = x, y + ((dh - th) / #lines)
|
||||
local xv = x1
|
||||
local title = "Lite XL"
|
||||
local version = "version " .. VERSION
|
||||
local title_width = style.big_font:get_width(title)
|
||||
local version_width = style.font:get_width(version)
|
||||
if version_width > title_width then
|
||||
version = VERSION
|
||||
version_width = style.font:get_width(version)
|
||||
xv = x1 - (version_width - title_width)
|
||||
end
|
||||
x = renderer.draw_text(style.big_font, title, x1, y1, color)
|
||||
renderer.draw_text(style.font, version, xv, y1 + th, color)
|
||||
x = x + style.padding.x
|
||||
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
|
||||
th = style.font:get_height()
|
||||
y = y + (dh - (th + style.padding.y) * #lines) / 2
|
||||
local w = 0
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
local core = require "core"
|
||||
|
||||
local ime = { }
|
||||
|
||||
function ime.reset()
|
||||
ime.editing = false
|
||||
ime.last_location = { x = 0, y = 0, w = 0, h = 0 }
|
||||
end
|
||||
|
||||
---Convert from utf-8 offset and length (from SDL) to byte offsets
|
||||
---@param text string @Textediting string
|
||||
---@param start integer @0-based utf-8 offset of the starting position of the selection
|
||||
---@param length integer @Size of the utf-8 length of the selection
|
||||
function ime.ingest(text, start, length)
|
||||
if #text == 0 then
|
||||
-- finished textediting
|
||||
ime.reset()
|
||||
return "", 0, 0
|
||||
end
|
||||
|
||||
ime.editing = true
|
||||
|
||||
if start < 0 then
|
||||
-- we assume no selection and caret at the end
|
||||
return text, #text, 0
|
||||
end
|
||||
|
||||
-- start is 0-based, so we use start + 1
|
||||
local start_byte = utf8.offset(text, start + 1)
|
||||
if not start_byte then
|
||||
-- bad start offset
|
||||
-- we assume it meant the last byte of the text
|
||||
start_byte = #text
|
||||
else
|
||||
start_byte = math.min(start_byte - 1, #text)
|
||||
end
|
||||
|
||||
if length < 0 then
|
||||
-- caret only
|
||||
return text, start_byte, 0
|
||||
end
|
||||
|
||||
local end_byte = utf8.offset(text, start + length + 1)
|
||||
if not end_byte or end_byte - 1 < start_byte then
|
||||
-- bad length, assume caret only
|
||||
return text, start_byte, 0
|
||||
end
|
||||
|
||||
end_byte = math.min(end_byte - 1, #text)
|
||||
return text, start_byte, end_byte - start_byte
|
||||
end
|
||||
|
||||
---Forward the given textediting SDL event data to Views.
|
||||
---@param text string @Textediting string
|
||||
---@param start integer @0-based utf-8 offset of the starting position of the selection
|
||||
---@param length integer @Size of the utf-8 length of the selection
|
||||
function ime.on_text_editing(text, start, length, ...)
|
||||
if ime.editing or #text > 0 then
|
||||
core.root_view:on_ime_text_editing(ime.ingest(text, start, length, ...))
|
||||
end
|
||||
end
|
||||
|
||||
---Stop IME composition.
|
||||
---Might not completely work on every platform.
|
||||
function ime.stop()
|
||||
if ime.editing then
|
||||
-- SDL_ClearComposition for now doesn't work everywhere
|
||||
system.clear_ime()
|
||||
ime.on_text_editing("", 0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
---Set the bounding box of the text pertaining the IME.
|
||||
---The IME will draw its interface based on this info.
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param w number
|
||||
---@param h number
|
||||
function ime.set_location(x, y, w, h)
|
||||
if not ime.last_location or
|
||||
ime.last_location.x ~= x or
|
||||
ime.last_location.y ~= y or
|
||||
ime.last_location.w ~= w or
|
||||
ime.last_location.h ~= h
|
||||
then
|
||||
ime.last_location.x, ime.last_location.y, ime.last_location.w, ime.last_location.h = x, y, w, h
|
||||
system.set_text_input_rect(x, y, w, h)
|
||||
end
|
||||
end
|
||||
|
||||
ime.reset()
|
||||
return ime
|
File diff suppressed because it is too large
Load Diff
|
@ -6,7 +6,7 @@ local function keymap_macos(keymap)
|
|||
["cmd+n"] = "core:new-doc",
|
||||
["cmd+shift+c"] = "core:change-project-folder",
|
||||
["cmd+shift+o"] = "core:open-project-folder",
|
||||
["cmd+option+r"] = "core:restart",
|
||||
["cmd+shift+r"] = "core:restart",
|
||||
["cmd+ctrl+return"] = "core:toggle-fullscreen",
|
||||
|
||||
["cmd+ctrl+shift+j"] = "root:split-left",
|
||||
|
@ -34,9 +34,7 @@ local function keymap_macos(keymap)
|
|||
["cmd+8"] = "root:switch-to-tab-8",
|
||||
["cmd+9"] = "root:switch-to-tab-9",
|
||||
["wheel"] = "root:scroll",
|
||||
["hwheel"] = "root:horizontal-scroll",
|
||||
["shift+hwheel"] = "root:horizontal-scroll",
|
||||
|
||||
|
||||
["cmd+f"] = "find-replace:find",
|
||||
["cmd+r"] = "find-replace:replace",
|
||||
["f3"] = "find-replace:repeat-find",
|
||||
|
|
|
@ -1,139 +1,35 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local ime = require "core.ime"
|
||||
local keymap = {}
|
||||
|
||||
---@alias keymap.shortcut string
|
||||
---@alias keymap.command string
|
||||
---@alias keymap.modkey string
|
||||
---@alias keymap.pressed boolean
|
||||
---@alias keymap.map table<keymap.shortcut,keymap.command|keymap.command[]>
|
||||
---@alias keymap.rmap table<keymap.command, keymap.shortcut|keymap.shortcut[]>
|
||||
|
||||
---Pressed status of mod keys.
|
||||
---@type table<keymap.modkey, keymap.pressed>
|
||||
keymap.modkeys = {}
|
||||
|
||||
---List of commands assigned to a shortcut been the key of the map the shortcut.
|
||||
---@type keymap.map
|
||||
keymap.map = {}
|
||||
|
||||
---List of shortcuts assigned to a command been the key of the map the command.
|
||||
---@type keymap.rmap
|
||||
keymap.reverse_map = {}
|
||||
|
||||
local macos = PLATFORM == "Mac OS X"
|
||||
local os4 = PLATFORM == "AmigaOS 4"
|
||||
local mos = PLATFORM == "MORPHOS"
|
||||
|
||||
-- Thanks to mathewmariani, taken from his lite-macos github repository.
|
||||
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or os4 and "os4" or mos and "mos" or "generic"))
|
||||
|
||||
---@type table<keymap.modkey, keymap.modkey>
|
||||
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or os4 and "os4" or "generic"))
|
||||
local modkey_map = modkeys_os.map
|
||||
|
||||
---@type keymap.modkey[]
|
||||
local modkeys = modkeys_os.keys
|
||||
|
||||
|
||||
---Normalizes a stroke sequence to follow the modkeys table
|
||||
---@param stroke string
|
||||
---@return string
|
||||
local function normalize_stroke(stroke)
|
||||
local stroke_table = {}
|
||||
for key in stroke:gmatch("[^+]+") do
|
||||
table.insert(stroke_table, key)
|
||||
end
|
||||
table.sort(stroke_table, function(a, b)
|
||||
if a == b then return false end
|
||||
for _, mod in ipairs(modkeys) do
|
||||
if a == mod or b == mod then
|
||||
return a == mod
|
||||
end
|
||||
end
|
||||
return a < b
|
||||
end)
|
||||
return table.concat(stroke_table, "+")
|
||||
end
|
||||
|
||||
|
||||
---Generates a stroke sequence including currently pressed mod keys.
|
||||
---@param key string
|
||||
---@return string
|
||||
local function key_to_stroke(key)
|
||||
local keys = { key }
|
||||
local function key_to_stroke(k)
|
||||
local stroke = ""
|
||||
for _, mk in ipairs(modkeys) do
|
||||
if keymap.modkeys[mk] then
|
||||
table.insert(keys, mk)
|
||||
stroke = stroke .. mk .. "+"
|
||||
end
|
||||
end
|
||||
return normalize_stroke(table.concat(keys, "+"))
|
||||
return stroke .. k
|
||||
end
|
||||
|
||||
|
||||
---Remove the given value from an array associated to a key in a table.
|
||||
---@param tbl table<string, string> The table containing the key
|
||||
---@param k string The key containing the array
|
||||
---@param v? string The value to remove from the array
|
||||
local function remove_only(tbl, k, v)
|
||||
if tbl[k] then
|
||||
if v then
|
||||
local j = 0
|
||||
for i=1, #tbl[k] do
|
||||
while tbl[k][i + j] == v do
|
||||
j = j + 1
|
||||
end
|
||||
tbl[k][i] = tbl[k][i + j]
|
||||
end
|
||||
else
|
||||
tbl[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---Removes from a keymap.map the bindings that are already registered.
|
||||
---@param map keymap.map
|
||||
local function remove_duplicates(map)
|
||||
for stroke, commands in pairs(map) do
|
||||
local normalized_stroke = normalize_stroke(stroke)
|
||||
if type(commands) == "string" or type(commands) == "function" then
|
||||
commands = { commands }
|
||||
end
|
||||
if keymap.map[normalized_stroke] then
|
||||
for _, registered_cmd in ipairs(keymap.map[normalized_stroke]) do
|
||||
local j = 0
|
||||
for i=1, #commands do
|
||||
while commands[i + j] == registered_cmd do
|
||||
j = j + 1
|
||||
end
|
||||
commands[i] = commands[i + j]
|
||||
end
|
||||
end
|
||||
end
|
||||
if #commands < 1 then
|
||||
map[stroke] = nil
|
||||
else
|
||||
map[stroke] = commands
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Add bindings by replacing commands that were previously assigned to a shortcut.
|
||||
---@param map keymap.map
|
||||
function keymap.add_direct(map)
|
||||
for stroke, commands in pairs(map) do
|
||||
stroke = normalize_stroke(stroke)
|
||||
|
||||
if type(commands) == "string" or type(commands) == "function" then
|
||||
if type(commands) == "string" then
|
||||
commands = { commands }
|
||||
end
|
||||
if keymap.map[stroke] then
|
||||
for _, cmd in ipairs(keymap.map[stroke]) do
|
||||
remove_only(keymap.reverse_map, cmd, stroke)
|
||||
end
|
||||
end
|
||||
keymap.map[stroke] = commands
|
||||
for _, cmd in ipairs(commands) do
|
||||
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
|
||||
|
@ -142,24 +38,15 @@ function keymap.add_direct(map)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
---Adds bindings by appending commands to already registered shortcut or by
|
||||
---replacing currently assigned commands if overwrite is specified.
|
||||
---@param map keymap.map
|
||||
---@param overwrite? boolean
|
||||
function keymap.add(map, overwrite)
|
||||
remove_duplicates(map)
|
||||
for stroke, commands in pairs(map) do
|
||||
if macos then
|
||||
stroke = stroke:gsub("%f[%a]ctrl%f[%A]", "cmd")
|
||||
end
|
||||
stroke = normalize_stroke(stroke)
|
||||
if type(commands) == "string" then
|
||||
commands = { commands }
|
||||
end
|
||||
if overwrite then
|
||||
if keymap.map[stroke] then
|
||||
for _, cmd in ipairs(keymap.map[stroke]) do
|
||||
remove_only(keymap.reverse_map, cmd, stroke)
|
||||
end
|
||||
end
|
||||
keymap.map[stroke] = commands
|
||||
else
|
||||
keymap.map[stroke] = keymap.map[stroke] or {}
|
||||
|
@ -175,35 +62,35 @@ function keymap.add(map, overwrite)
|
|||
end
|
||||
|
||||
|
||||
---Unregisters the given shortcut and associated command.
|
||||
---@param shortcut string
|
||||
---@param cmd string
|
||||
function keymap.unbind(shortcut, cmd)
|
||||
shortcut = normalize_stroke(shortcut)
|
||||
remove_only(keymap.map, shortcut, cmd)
|
||||
remove_only(keymap.reverse_map, cmd, shortcut)
|
||||
local function remove_only(tbl, k, v)
|
||||
for key, values in pairs(tbl) do
|
||||
if key == k then
|
||||
if v then
|
||||
for i, value in ipairs(values) do
|
||||
if value == v then
|
||||
table.remove(values, i)
|
||||
end
|
||||
end
|
||||
else
|
||||
tbl[key] = nil
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function keymap.unbind(key, cmd)
|
||||
remove_only(keymap.map, key, cmd)
|
||||
remove_only(keymap.reverse_map, cmd, key)
|
||||
end
|
||||
|
||||
|
||||
---Returns all the shortcuts associated to a command unpacked for easy assignment.
|
||||
---@param cmd string
|
||||
---@return ...
|
||||
function keymap.get_binding(cmd)
|
||||
return table.unpack(keymap.reverse_map[cmd] or {})
|
||||
end
|
||||
|
||||
|
||||
---Returns all the shortcuts associated to a command packed in a table.
|
||||
---@param cmd string
|
||||
---@return table<integer, string> | nil shortcuts
|
||||
function keymap.get_bindings(cmd)
|
||||
return keymap.reverse_map[cmd]
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Events listening
|
||||
--------------------------------------------------------------------------------
|
||||
function keymap.on_key_pressed(k, ...)
|
||||
local mk = modkey_map[k]
|
||||
if mk then
|
||||
|
@ -214,19 +101,10 @@ function keymap.on_key_pressed(k, ...)
|
|||
end
|
||||
else
|
||||
local stroke = key_to_stroke(k)
|
||||
local commands, performed = keymap.map[stroke], false
|
||||
local commands, performed = keymap.map[stroke]
|
||||
if commands then
|
||||
for _, cmd in ipairs(commands) do
|
||||
if type(cmd) == "function" then
|
||||
local ok, res = core.try(cmd, ...)
|
||||
if ok then
|
||||
performed = not (res == false)
|
||||
else
|
||||
performed = true
|
||||
end
|
||||
else
|
||||
performed = command.perform(cmd, ...)
|
||||
end
|
||||
performed = command.perform(cmd, ...)
|
||||
if performed then break end
|
||||
end
|
||||
return performed
|
||||
|
@ -235,32 +113,9 @@ function keymap.on_key_pressed(k, ...)
|
|||
return false
|
||||
end
|
||||
|
||||
function keymap.on_mouse_wheel(delta_y, delta_x, ...)
|
||||
local y_direction = delta_y > 0 and "up" or "down"
|
||||
local x_direction = delta_x > 0 and "left" or "right"
|
||||
-- Try sending a "cumulative" event for both scroll directions
|
||||
if delta_y ~= 0 and delta_x ~= 0 then
|
||||
local result = keymap.on_key_pressed("wheel" .. y_direction .. x_direction, delta_y, delta_x, ...)
|
||||
if not result then
|
||||
result = keymap.on_key_pressed("wheelyx", delta_y, delta_x, ...)
|
||||
end
|
||||
if result then return true end
|
||||
end
|
||||
-- Otherwise send each direction as its own separate event
|
||||
local y_result, x_result
|
||||
if delta_y ~= 0 then
|
||||
y_result = keymap.on_key_pressed("wheel" .. y_direction, delta_y, ...)
|
||||
if not y_result then
|
||||
y_result = keymap.on_key_pressed("wheel", delta_y, ...)
|
||||
end
|
||||
end
|
||||
if delta_x ~= 0 then
|
||||
x_result = keymap.on_key_pressed("wheel" .. x_direction, delta_x, ...)
|
||||
if not x_result then
|
||||
x_result = keymap.on_key_pressed("hwheel", delta_x, ...)
|
||||
end
|
||||
end
|
||||
return y_result or x_result
|
||||
function keymap.on_mouse_wheel(delta, ...)
|
||||
return not (keymap.on_key_pressed("wheel" .. (delta > 0 and "up" or "down"), delta, ...)
|
||||
or keymap.on_key_pressed("wheel", delta, ...))
|
||||
end
|
||||
|
||||
function keymap.on_mouse_pressed(button, x, y, clicks)
|
||||
|
@ -279,9 +134,6 @@ function keymap.on_key_released(k)
|
|||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Register default bindings
|
||||
--------------------------------------------------------------------------------
|
||||
if macos then
|
||||
local keymap_macos = require("core.keymap-macos")
|
||||
keymap_macos(keymap)
|
||||
|
@ -295,7 +147,7 @@ keymap.add_direct {
|
|||
["ctrl+n"] = "core:new-doc",
|
||||
["ctrl+shift+c"] = "core:change-project-folder",
|
||||
["ctrl+shift+o"] = "core:open-project-folder",
|
||||
["ctrl+alt+r"] = "core:restart",
|
||||
["ctrl+shift+r"] = "core:restart",
|
||||
["alt+return"] = "core:toggle-fullscreen",
|
||||
["f11"] = "core:toggle-fullscreen",
|
||||
|
||||
|
@ -323,8 +175,6 @@ keymap.add_direct {
|
|||
["alt+8"] = "root:switch-to-tab-8",
|
||||
["alt+9"] = "root:switch-to-tab-9",
|
||||
["wheel"] = "root:scroll",
|
||||
["hwheel"] = "root:horizontal-scroll",
|
||||
["shift+wheel"] = "root:horizontal-scroll",
|
||||
|
||||
["ctrl+f"] = "find-replace:find",
|
||||
["ctrl+r"] = "find-replace:replace",
|
||||
|
@ -366,7 +216,6 @@ keymap.add_direct {
|
|||
["ctrl+l"] = "doc:select-lines",
|
||||
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||
["ctrl+/"] = "doc:toggle-line-comments",
|
||||
["ctrl+shift+/"] = "doc:toggle-block-comments",
|
||||
["ctrl+up"] = "doc:move-lines-up",
|
||||
["ctrl+down"] = "doc:move-lines-down",
|
||||
["ctrl+shift+d"] = "doc:duplicate-lines",
|
||||
|
@ -411,3 +260,4 @@ keymap.add_direct {
|
|||
}
|
||||
|
||||
return keymap
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
|
@ -38,15 +36,12 @@ 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
|
||||
|
||||
core.status_view:show_message("i", style.text, "ctrl+click to copy entry")
|
||||
end
|
||||
|
||||
|
||||
|
@ -82,73 +77,45 @@ function LogView:each_item()
|
|||
end
|
||||
|
||||
|
||||
function LogView:get_scrollable_size()
|
||||
local _, y_off = self:get_content_offset()
|
||||
local last_y, last_h = 0, 0
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
last_y, last_h = y, h
|
||||
end
|
||||
if not config.scroll_past_end then
|
||||
return last_y + last_h - y_off + style.padding.y
|
||||
end
|
||||
return last_y + self.size.y - y_off
|
||||
end
|
||||
|
||||
|
||||
function LogView:on_mouse_pressed(button, px, py, clicks)
|
||||
if LogView.super.on_mouse_pressed(self, button, px, py, clicks) then
|
||||
return true
|
||||
end
|
||||
|
||||
local index, selected
|
||||
for i, item, x, y, w, h in self:each_item() do
|
||||
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
|
||||
index = i
|
||||
selected = item
|
||||
hovered = true
|
||||
self.hovered_item = item
|
||||
break
|
||||
end
|
||||
end
|
||||
if not hovered then self.hovered_item = nil end
|
||||
end
|
||||
|
||||
if selected then
|
||||
if keymap.modkeys["ctrl"] then
|
||||
system.set_clipboard(core.get_log(selected))
|
||||
core.status_view:show_message("i", style.text, "copied entry #"..index.." to clipboard.")
|
||||
else
|
||||
self:expand_item(selected)
|
||||
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
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function LogView:update()
|
||||
local item = core.log_items[#core.log_items]
|
||||
if self.last_item ~= item then
|
||||
local lh = style.font:get_height() + style.padding.y
|
||||
if 0 < self.scroll.to.y then
|
||||
local index = #core.log_items
|
||||
while index > 1 and self.last_item ~= core.log_items[index] do
|
||||
index = index - 1
|
||||
end
|
||||
local diff_index = #core.log_items - index
|
||||
self.scroll.to.y = self.scroll.to.y + diff_index * lh
|
||||
self.scroll.y = self.scroll.to.y
|
||||
else
|
||||
self.yoffset = -lh
|
||||
end
|
||||
self.last_item = item
|
||||
self.scroll.to.y = 0
|
||||
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, nil, "logview")
|
||||
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, nil, "logview")
|
||||
self:move_towards("yoffset", 0)
|
||||
|
||||
LogView.super.update(self)
|
||||
end
|
||||
|
@ -164,62 +131,41 @@ local function draw_text_multiline(font, text, x, y, color)
|
|||
return resx, y
|
||||
end
|
||||
|
||||
-- this is just to get a date string that's consistent
|
||||
local datestr = os.date()
|
||||
|
||||
function LogView:draw()
|
||||
self:draw_background(style.background)
|
||||
|
||||
local th = style.font:get_height()
|
||||
local lh = th + style.padding.y -- for one line
|
||||
local iw = math.max(
|
||||
style.icon_font:get_width(style.log.ERROR.icon),
|
||||
style.icon_font:get_width(style.log.INFO.icon)
|
||||
)
|
||||
for _, item, x, y, w in self:each_item() do
|
||||
x = x + style.padding.x
|
||||
|
||||
local tw = style.font:get_width(datestr)
|
||||
for _, item, x, y, w, h in self:each_item() do
|
||||
if y + h >= self.position.y and y <= self.position.y + self.size.y then
|
||||
core.push_clip_rect(x, y, w, h)
|
||||
x = x + style.padding.x
|
||||
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.icon_font,
|
||||
style.log[item.level].color,
|
||||
style.log[item.level].icon,
|
||||
"center",
|
||||
x, y, iw, 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())
|
||||
|
||||
-- timestamps are always 15% of the width
|
||||
local time = os.date(nil, item.time)
|
||||
common.draw_text(style.font, style.dim, time, "left", x, y, tw, lh)
|
||||
x = x + tw + style.padding.x
|
||||
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)
|
||||
|
||||
w = w - (x - self:get_content_offset())
|
||||
local at = "at " .. common.home_encode(item.at)
|
||||
_, y = common.draw_text(style.font, style.dim, at, "left", x, y, w, lh)
|
||||
|
||||
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)
|
||||
if item.info then
|
||||
_, y = draw_text_multiline(style.font, item.info, x, y, style.dim)
|
||||
end
|
||||
|
||||
core.pop_clip_rect()
|
||||
else
|
||||
local line, has_newline = string.match(item.text, "([^\n]+)(\n?)")
|
||||
if has_newline ~= "" then
|
||||
line = line .. " ..."
|
||||
end
|
||||
_, y = common.draw_text(style.font, style.text, line, "left", x, y, w, lh)
|
||||
end
|
||||
end
|
||||
LogView.super.draw_scrollbar(self)
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -9,6 +9,6 @@ modkeys.map = {
|
|||
["right alt"] = "altgr",
|
||||
}
|
||||
|
||||
modkeys.keys = { "ctrl", "shift", "alt", "altgr" }
|
||||
modkeys.keys = { "ctrl", "alt", "altgr", "shift" }
|
||||
|
||||
return modkeys
|
||||
|
|
|
@ -13,6 +13,6 @@ modkeys.map = {
|
|||
["right alt"] = "altgr",
|
||||
}
|
||||
|
||||
modkeys.keys = { "ctrl", "alt", "option", "altgr", "shift", "cmd" }
|
||||
modkeys.keys = { "cmd", "ctrl", "alt", "option", "altgr", "shift" }
|
||||
|
||||
return modkeys
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
local modkeys = {}
|
||||
|
||||
modkeys.map = {
|
||||
["left amiga"] = "cmd",
|
||||
["right amiga"] = "cmd",
|
||||
["control"] = "ctrl",
|
||||
["left shift"] = "shift",
|
||||
["right shift"] = "shift",
|
||||
["left alt"] = "alt",
|
||||
["right alt"] = "altgr",
|
||||
}
|
||||
|
||||
modkeys.keys = { "cmd", "ctrl", "alt", "altgr", "shift" }
|
||||
|
||||
return modkeys
|
|
@ -11,19 +11,13 @@ local UNDERLINE_MARGIN = common.round(1 * SCALE)
|
|||
|
||||
local noop = function() end
|
||||
|
||||
---@class core.nagview : core.view
|
||||
---@field super core.view
|
||||
local NagView = View:extend()
|
||||
|
||||
function NagView:new()
|
||||
NagView.super.new(self)
|
||||
self.size.y = 0
|
||||
self.show_height = 0
|
||||
self.force_focus = false
|
||||
self.queue = {}
|
||||
self.scrollable = true
|
||||
self.target_height = 0
|
||||
self.on_mouse_pressed_root = nil
|
||||
end
|
||||
|
||||
function NagView:get_title()
|
||||
|
@ -52,20 +46,20 @@ function NagView:get_target_height()
|
|||
return self.target_height + 2 * style.padding.y
|
||||
end
|
||||
|
||||
function NagView:get_scrollable_size()
|
||||
local w, h = system.get_window_size()
|
||||
if self.visible and self:get_target_height() > h then
|
||||
self.size.y = h
|
||||
return self:get_target_height()
|
||||
function NagView:update()
|
||||
NagView.super.update(self)
|
||||
|
||||
if core.active_view == self and self.title then
|
||||
self:move_towards(self.size, "y", self:get_target_height())
|
||||
self:move_towards(self, "underline_progress", 1)
|
||||
else
|
||||
self.size.y = 0
|
||||
self:move_towards(self.size, "y", 0)
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function NagView:dim_window_content()
|
||||
function NagView:draw_overlay()
|
||||
local ox, oy = self:get_content_offset()
|
||||
oy = oy + self.show_height
|
||||
oy = oy + self.size.y
|
||||
local w, h = core.root_view.size.x, core.root_view.size.y - oy
|
||||
core.root_view:defer_draw(function()
|
||||
renderer.draw_rect(ox, oy, w, h, style.nagbar_dim)
|
||||
|
@ -87,11 +81,11 @@ function NagView:each_option()
|
|||
bh = self:get_buttons_height()
|
||||
ox,oy = self:get_content_offset()
|
||||
ox = ox + self.size.x
|
||||
oy = oy + self.show_height - bh - style.padding.y
|
||||
oy = oy + self.size.y - bh - style.padding.y
|
||||
|
||||
for i = #self.options, 1, -1 do
|
||||
opt = self.options[i]
|
||||
bw = style.font:get_width(opt.text) + 2 * BORDER_WIDTH + style.padding.x
|
||||
bw = opt.font:get_width(opt.text) + 2 * BORDER_WIDTH + style.padding.x
|
||||
|
||||
ox = ox - bw - style.padding.x
|
||||
coroutine.yield(i, opt, ox,oy,bw,bh)
|
||||
|
@ -100,8 +94,6 @@ function NagView:each_option()
|
|||
end
|
||||
|
||||
function NagView:on_mouse_moved(mx, my, ...)
|
||||
if not self.visible then return end
|
||||
core.set_active_view(self)
|
||||
NagView.super.on_mouse_moved(self, mx, my, ...)
|
||||
for i, _, x,y,w,h in self:each_option() do
|
||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
||||
|
@ -111,55 +103,18 @@ function NagView:on_mouse_moved(mx, my, ...)
|
|||
end
|
||||
end
|
||||
|
||||
local function register_mouse_pressed(self)
|
||||
if self.on_mouse_pressed_root then return end
|
||||
-- RootView is loaded locally to avoid NagView and RootView being
|
||||
-- mutually recursive
|
||||
local RootView = require "core.rootview"
|
||||
self.on_mouse_pressed_root = RootView.on_mouse_pressed
|
||||
local this = self
|
||||
function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||
if
|
||||
not this:on_mouse_pressed(button, x, y, clicks)
|
||||
then
|
||||
return this.on_mouse_pressed_root(self, button, x, y, clicks)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
self.new_on_mouse_pressed_root = RootView.on_mouse_pressed
|
||||
end
|
||||
|
||||
local function unregister_mouse_pressed(self)
|
||||
local RootView = require "core.rootview"
|
||||
if
|
||||
self.on_mouse_pressed_root
|
||||
and
|
||||
-- just in case prevent overwriting what something else may
|
||||
-- have overwrote after us, but after testing with various
|
||||
-- plugins this doesn't seems to happen, but just in case
|
||||
self.new_on_mouse_pressed_root == RootView.on_mouse_pressed
|
||||
then
|
||||
RootView.on_mouse_pressed = self.on_mouse_pressed_root
|
||||
self.on_mouse_pressed_root = nil
|
||||
self.new_on_mouse_pressed_root = nil
|
||||
end
|
||||
end
|
||||
|
||||
function NagView:on_mouse_pressed(button, mx, my, clicks)
|
||||
if not self.visible then return false end
|
||||
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return true end
|
||||
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
|
||||
for i, _, x,y,w,h in self:each_option() do
|
||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
||||
self:change_hovered(i)
|
||||
command.perform "dialog:select"
|
||||
break
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function NagView:on_text_input(text)
|
||||
if not self.visible then return end
|
||||
if text:lower() == "y" then
|
||||
command.perform "dialog:select-yes"
|
||||
elseif text:lower() == "n" then
|
||||
|
@ -167,39 +122,20 @@ function NagView:on_text_input(text)
|
|||
end
|
||||
end
|
||||
|
||||
function NagView:update()
|
||||
if not self.visible and self.show_height <= 0 then return end
|
||||
NagView.super.update(self)
|
||||
|
||||
if self.visible and core.active_view == self and self.title then
|
||||
self:move_towards(self, "show_height", self:get_target_height(), nil, "nagbar")
|
||||
self:move_towards(self, "underline_progress", 1, nil, "nagbar")
|
||||
else
|
||||
self:move_towards(self, "show_height", 0, nil, "nagbar")
|
||||
if self.show_height <= 0 then
|
||||
self.title = nil
|
||||
self.message = nil
|
||||
self.options = nil
|
||||
self.on_selected = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
function NagView:draw()
|
||||
if self.size.y <= 0 or not self.title then return end
|
||||
|
||||
local function draw_nagview_message(self)
|
||||
self:dim_window_content()
|
||||
self:draw_overlay()
|
||||
self:draw_background(style.nagbar)
|
||||
|
||||
-- draw message's background
|
||||
local ox, oy = self:get_content_offset()
|
||||
renderer.draw_rect(ox, oy, self.size.x, self.show_height, style.nagbar)
|
||||
|
||||
ox = ox + style.padding.x
|
||||
|
||||
core.push_clip_rect(ox, oy, self.size.x, self.show_height)
|
||||
|
||||
-- if there are other items, show it
|
||||
if #self.queue > 0 then
|
||||
local str = string.format("[%d]", #self.queue)
|
||||
ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.show_height)
|
||||
ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.size.y)
|
||||
ox = ox + style.padding.x
|
||||
end
|
||||
|
||||
|
@ -230,29 +166,8 @@ local function draw_nagview_message(self)
|
|||
renderer.draw_rect(lx,ly,uw,UNDERLINE_WIDTH, style.nagbar_text)
|
||||
end
|
||||
|
||||
common.draw_text(style.font, style.nagbar_text, opt.text, "center", fx,fy,fw,fh)
|
||||
common.draw_text(opt.font, style.nagbar_text, opt.text, "center", fx,fy,fw,fh)
|
||||
end
|
||||
|
||||
self:draw_scrollbar()
|
||||
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
function NagView:draw()
|
||||
if (not self.visible and self.show_height <= 0) or not self.title then
|
||||
return
|
||||
end
|
||||
core.root_view:defer_draw(draw_nagview_message, self)
|
||||
end
|
||||
|
||||
function NagView:on_scale_change(new_scale, old_scale)
|
||||
BORDER_WIDTH = common.round(1 * new_scale)
|
||||
UNDERLINE_WIDTH = common.round(2 * new_scale)
|
||||
UNDERLINE_MARGIN = common.round(1 * new_scale)
|
||||
self.target_height = math.max(
|
||||
self:get_message_height(),
|
||||
self:get_buttons_height()
|
||||
)
|
||||
end
|
||||
|
||||
function NagView:get_message_height()
|
||||
|
@ -263,31 +178,23 @@ function NagView:get_message_height()
|
|||
return h
|
||||
end
|
||||
|
||||
|
||||
function NagView:next()
|
||||
local opts = table.remove(self.queue, 1) or {}
|
||||
if opts.title and opts.message and opts.options then
|
||||
self.visible = true
|
||||
self.title = opts.title
|
||||
self.message = opts.message and opts.message .. "\n"
|
||||
self.options = opts.options
|
||||
self.on_selected = opts.on_selected
|
||||
|
||||
self.title = opts.title
|
||||
self.message = opts.message and opts.message .. "\n"
|
||||
self.options = opts.options
|
||||
self.on_selected = opts.on_selected
|
||||
if self.message and self.options then
|
||||
local message_height = self:get_message_height()
|
||||
-- self.target_height is the nagview height needed to display the message and
|
||||
-- the buttons, excluding the top and bottom padding space.
|
||||
self.target_height = math.max(message_height, self:get_buttons_height())
|
||||
self:change_hovered(common.find_index(self.options, "default_yes"))
|
||||
|
||||
self.force_focus = true
|
||||
core.set_active_view(self)
|
||||
-- We add a hook to manage all the mouse_pressed events.
|
||||
register_mouse_pressed(self)
|
||||
else
|
||||
self.force_focus = false
|
||||
core.set_active_view(core.next_active_view or core.last_active_view)
|
||||
self.visible = false
|
||||
unregister_mouse_pressed(self)
|
||||
end
|
||||
self.force_focus = self.message ~= nil
|
||||
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)
|
||||
|
@ -297,7 +204,7 @@ function NagView:show(title, message, options, on_select)
|
|||
opts.options = assert(options, "No options")
|
||||
opts.on_selected = on_select or noop
|
||||
table.insert(self.queue, opts)
|
||||
self:next()
|
||||
if #self.queue > 0 and not self.title then self:next() end
|
||||
end
|
||||
|
||||
return NagView
|
||||
|
|
|
@ -6,7 +6,6 @@ local Object = require "core.object"
|
|||
local EmptyView = require "core.emptyview"
|
||||
local View = require "core.view"
|
||||
|
||||
---@class core.node : core.object
|
||||
local Node = Object:extend()
|
||||
|
||||
function Node:new(type)
|
||||
|
@ -52,15 +51,6 @@ function Node:on_mouse_released(...)
|
|||
end
|
||||
|
||||
|
||||
function Node:on_mouse_left()
|
||||
if self.type == "leaf" then
|
||||
self.active_view:on_mouse_left()
|
||||
else
|
||||
self:propagate("on_mouse_left")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:consume(node)
|
||||
for k, _ in pairs(self) do self[k] = nil end
|
||||
for k, v in pairs(node) do self[k] = v end
|
||||
|
@ -170,12 +160,8 @@ end
|
|||
|
||||
function Node:set_active_view(view)
|
||||
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
|
||||
local last_active_view = self.active_view
|
||||
self.active_view = view
|
||||
core.set_active_view(view)
|
||||
if last_active_view and last_active_view ~= view then
|
||||
last_active_view:on_mouse_left()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -274,8 +260,8 @@ end
|
|||
|
||||
local function close_button_location(x, w)
|
||||
local cw = style.icon_font:get_width("C")
|
||||
local pad = style.padding.x / 2
|
||||
return x + w - cw - pad, cw, pad
|
||||
local pad = style.padding.y
|
||||
return x + w - pad - cw, cw, pad
|
||||
end
|
||||
|
||||
|
||||
|
@ -323,14 +309,15 @@ end
|
|||
function Node:get_scroll_button_rect(index)
|
||||
local w, pad = get_scroll_button_width()
|
||||
local h = style.font:get_height() + style.padding.y * 2
|
||||
local x = self.position.x + (index == 1 and self.size.x - w * 2 or self.size.x - w)
|
||||
local x = self.position.x + (index == 1 and 0 or self.size.x - w)
|
||||
return x, self.position.y, w, h, pad
|
||||
end
|
||||
|
||||
|
||||
function Node:get_tab_rect(idx)
|
||||
local maxw = self.size.x
|
||||
local x0 = self.position.x
|
||||
local sbw = get_scroll_button_width()
|
||||
local maxw = self.size.x - 2 * sbw
|
||||
local x0 = self.position.x + sbw
|
||||
local x1 = x0 + common.clamp(self.tab_width * (idx - 1) - self.tab_shift, 0, maxw)
|
||||
local x2 = x0 + common.clamp(self.tab_width * idx - self.tab_shift, 0, maxw)
|
||||
local h = style.font:get_height() + style.padding.y * 2
|
||||
|
@ -468,10 +455,7 @@ end
|
|||
|
||||
function Node:target_tab_width()
|
||||
local n = self:get_visible_tabs_number()
|
||||
local w = self.size.x
|
||||
if #self.views > n then
|
||||
w = self.size.x - get_scroll_button_width() * 2
|
||||
end
|
||||
local w = self.size.x - get_scroll_button_width() * 2
|
||||
return common.clamp(style.tab_width, w / config.max_tabs, w / n)
|
||||
end
|
||||
|
||||
|
@ -484,100 +468,90 @@ function Node:update()
|
|||
end
|
||||
self:tab_hovered_update(self.hovered.x, self.hovered.y)
|
||||
local tab_width = self:target_tab_width()
|
||||
self:move_towards("tab_shift", tab_width * (self.tab_offset - 1), nil, "tabs")
|
||||
self:move_towards("tab_width", tab_width, nil, "tabs")
|
||||
self:move_towards("tab_shift", tab_width * (self.tab_offset - 1))
|
||||
self:move_towards("tab_width", tab_width)
|
||||
else
|
||||
self.a:update()
|
||||
self.b:update()
|
||||
end
|
||||
end
|
||||
|
||||
function Node:draw_tab_title(view, font, is_active, is_hovered, x, y, w, h)
|
||||
local text = view and view:get_name() or ""
|
||||
local dots_width = font:get_width("…")
|
||||
local align = "center"
|
||||
if font:get_width(text) > w then
|
||||
align = "left"
|
||||
for i = 1, #text do
|
||||
local reduced_text = text:sub(1, #text - i)
|
||||
if font:get_width(reduced_text) + dots_width <= w then
|
||||
text = reduced_text .. "…"
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
local color = style.dim
|
||||
if is_active then color = style.text end
|
||||
if is_hovered then color = style.text end
|
||||
common.draw_text(font, color, text, align, x, y, w, h)
|
||||
end
|
||||
|
||||
function Node:draw_tab_borders(view, is_active, is_hovered, x, y, w, h, standalone)
|
||||
-- Tabs deviders
|
||||
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
||||
local ds = style.divider_size
|
||||
local dots_width = style.font:get_width("…")
|
||||
local color = style.dim
|
||||
local padding_y = style.padding.y
|
||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y*2, style.dim)
|
||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
||||
if standalone then
|
||||
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
|
||||
end
|
||||
-- Full border
|
||||
if is_active then
|
||||
color = style.text
|
||||
renderer.draw_rect(x, y, w, h, style.background)
|
||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
||||
end
|
||||
return x + ds, y, w - ds*2, h
|
||||
end
|
||||
|
||||
function Node:draw_tab(view, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
||||
x, y, w, h = self:draw_tab_borders(view, is_active, is_hovered, x, y, w, h, standalone)
|
||||
-- Close button
|
||||
local cx, cw, cpad = close_button_location(x, w)
|
||||
local cx, cw, cspace = close_button_location(x, w)
|
||||
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
|
||||
if show_close_button then
|
||||
local close_style = is_close_hovered and style.text or style.dim
|
||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, cw, h)
|
||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
||||
end
|
||||
-- Title
|
||||
x = x + cpad
|
||||
w = cx - x
|
||||
core.push_clip_rect(x, y, w, h)
|
||||
self:draw_tab_title(view, style.font, is_active, is_hovered, x, y, w, h)
|
||||
if is_hovered then
|
||||
color = style.text
|
||||
end
|
||||
local padx = style.padding.x
|
||||
-- Normally we should substract "cspace" from text_avail_width and from the
|
||||
-- clipping width. It is the padding space we give to the left and right of the
|
||||
-- close button. However, since we are using dots to terminate filenames, we
|
||||
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
||||
-- close button.
|
||||
local text_avail_width = cx - x - padx
|
||||
core.push_clip_rect(x, y, cx - x, h)
|
||||
x, w = x + padx, w - padx * 2
|
||||
local align = "center"
|
||||
if style.font:get_width(text) > text_avail_width then
|
||||
align = "left"
|
||||
for i = 1, #text do
|
||||
local reduced_text = text:sub(1, #text - i)
|
||||
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
||||
text = reduced_text .. "…"
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
common.draw_text(style.font, color, text, align, x, y, w, h)
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
function Node:draw_tabs()
|
||||
local _, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
||||
local x = self.position.x
|
||||
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
||||
local ds = style.divider_size
|
||||
local dots_width = style.font:get_width("…")
|
||||
core.push_clip_rect(x, y, self.size.x, h)
|
||||
renderer.draw_rect(x, y, self.size.x, h, style.background2)
|
||||
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
|
||||
|
||||
if self.tab_offset > 1 then
|
||||
local button_style = self.hovered_scroll_button == 1 and style.text or style.dim
|
||||
common.draw_text(style.icon_font, button_style, "<", nil, x + scroll_padding, y, 0, h)
|
||||
end
|
||||
|
||||
local tabs_number = self:get_visible_tabs_number()
|
||||
if #self.views > self.tab_offset + tabs_number - 1 then
|
||||
local xrb, yrb, wrb = self:get_scroll_button_rect(2)
|
||||
local button_style = self.hovered_scroll_button == 2 and style.text or style.dim
|
||||
common.draw_text(style.icon_font, button_style, ">", nil, xrb + scroll_padding, yrb, 0, h)
|
||||
end
|
||||
|
||||
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
||||
local view = self.views[i]
|
||||
local x, y, w, h = self:get_tab_rect(i)
|
||||
self:draw_tab(view, view == self.active_view,
|
||||
self:draw_tab(view:get_name(), view == self.active_view,
|
||||
i == self.hovered_tab, i == self.hovered_close,
|
||||
x, y, w, h)
|
||||
end
|
||||
|
||||
if #self.views > tabs_number then
|
||||
local _, pad = get_scroll_button_width()
|
||||
local xrb, yrb, wrb, hrb = self:get_scroll_button_rect(1)
|
||||
renderer.draw_rect(xrb + pad, yrb, wrb * 2, hrb, style.background2)
|
||||
local left_button_style = (self.hovered_scroll_button == 1 and self.tab_offset > 1) and style.text or style.dim
|
||||
common.draw_text(style.icon_font, left_button_style, "<", nil, xrb + scroll_padding, yrb, 0, h)
|
||||
|
||||
xrb, yrb, wrb = self:get_scroll_button_rect(2)
|
||||
local right_button_style = (self.hovered_scroll_button == 2 and #self.views > self.tab_offset + tabs_number - 1) and style.text or style.dim
|
||||
common.draw_text(style.icon_font, right_button_style, ">", nil, xrb + scroll_padding, yrb, 0, h)
|
||||
end
|
||||
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
|
@ -714,7 +688,7 @@ function Node:get_split_type(mouse_x, mouse_y)
|
|||
|
||||
local local_mouse_x = mouse_x - x
|
||||
local local_mouse_y = mouse_y - y
|
||||
|
||||
|
||||
if local_mouse_y < 0 then
|
||||
return "tab"
|
||||
else
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
---@class core.object
|
||||
---@field super core.object
|
||||
local Object = {}
|
||||
Object.__index = Object
|
||||
|
||||
---Can be overrided by child objects to implement a constructor.
|
||||
function Object:new() end
|
||||
|
||||
---@return core.object
|
||||
function Object:new()
|
||||
end
|
||||
|
||||
|
||||
function Object:extend()
|
||||
local cls = {}
|
||||
for k, v in pairs(self) do
|
||||
|
@ -20,17 +19,8 @@ function Object:extend()
|
|||
return cls
|
||||
end
|
||||
|
||||
---Check if the object is strictly of the given type.
|
||||
---@param T any
|
||||
---@return boolean
|
||||
function Object:is(T)
|
||||
return getmetatable(self) == T
|
||||
end
|
||||
|
||||
---Check if the object inherits from the given type.
|
||||
---@param T any
|
||||
---@return boolean
|
||||
function Object:extends(T)
|
||||
function Object:is(T)
|
||||
local mt = getmetatable(self)
|
||||
while mt do
|
||||
if mt == T then
|
||||
|
@ -41,14 +31,12 @@ function Object:extends(T)
|
|||
return false
|
||||
end
|
||||
|
||||
---Metamethod to get a string representation of an object.
|
||||
---@return string
|
||||
|
||||
function Object:__tostring()
|
||||
return "Object"
|
||||
end
|
||||
|
||||
---Metamethod to allow using the object call as a constructor.
|
||||
---@return core.object
|
||||
|
||||
function Object:__call(...)
|
||||
local obj = setmetatable({}, self)
|
||||
obj:new(...)
|
||||
|
|
|
@ -2,81 +2,69 @@
|
|||
-- pattern:gsub(string).
|
||||
regex.__index = function(table, key) return regex[key]; end
|
||||
|
||||
---Looks for the first match of `pattern` in the string `str`.
|
||||
---If it finds a match, it returns the indices of `str` where this occurrence
|
||||
---starts and ends; otherwise, it returns `nil`.
|
||||
---If the pattern has captures, the captured start and end indexes are returned,
|
||||
---after the two initial ones.
|
||||
---
|
||||
---@param pattern string|table The regex pattern to use, either as a simple string or precompiled.
|
||||
---@param str string The string to search for valid matches.
|
||||
---@param offset? integer The position on the subject to start searching.
|
||||
---@param options? integer A bit field of matching options, eg: regex.NOTBOL | regex.NOTEMPTY
|
||||
---
|
||||
---@return integer? start Offset where the first match was found; `nil` if no match.
|
||||
---@return integer? end Offset where the first match ends; `nil` if no match.
|
||||
---@return integer? ... #Captured matches offsets.
|
||||
regex.find_offsets = function(pattern, str, offset, options)
|
||||
if type(pattern) ~= "table" then
|
||||
pattern = regex.compile(pattern)
|
||||
end
|
||||
local res = { regex.cmatch(pattern, str, offset or 1, options or 0) }
|
||||
-- Reduce every end delimiter by 1
|
||||
for i = 2,#res,2 do
|
||||
res[i] = res[i] - 1
|
||||
end
|
||||
return table.unpack(res)
|
||||
regex.match = function(pattern_string, string, offset, options)
|
||||
local pattern = type(pattern_string) == "table" and
|
||||
pattern_string or regex.compile(pattern_string)
|
||||
local s, e = regex.cmatch(pattern, string, offset or 1, options or 0)
|
||||
return s, e and e - 1
|
||||
end
|
||||
|
||||
---Behaves like `string.match`.
|
||||
---Looks for the first match of `pattern` in the string `str`.
|
||||
---If it finds a match, it returns the matched string; otherwise, it returns `nil`.
|
||||
---If the pattern has captures, only the captured strings are returned.
|
||||
---If a capture is empty, its offset is returned instead.
|
||||
---
|
||||
---@param pattern string|table The regex pattern to use, either as a simple string or precompiled.
|
||||
---@param str string The string to search for valid matches.
|
||||
---@param offset? integer The position on the subject to start searching.
|
||||
---@param options? integer A bit field of matching options, eg: regex.NOTBOL | regex.NOTEMPTY
|
||||
---
|
||||
---@return (string|integer)? ... #List of captured matches; the entire match if no matches were specified; if the match is empty, its offset is returned instead.
|
||||
regex.match = function(pattern, str, offset, options)
|
||||
local res = { regex.find(pattern, str, offset, options) }
|
||||
if #res == 0 then return end
|
||||
-- If available, only return captures
|
||||
if #res > 2 then return table.unpack(res, 3) end
|
||||
return string.sub(str, res[1], res[2])
|
||||
-- Will iterate back through any UTF-8 bytes so that we don't replace bits
|
||||
-- mid character.
|
||||
local function previous_character(str, index)
|
||||
local byte
|
||||
repeat
|
||||
index = index - 1
|
||||
byte = string.byte(str, index)
|
||||
until byte < 128 or byte >= 192
|
||||
return index
|
||||
end
|
||||
|
||||
---Behaves like `string.find`.
|
||||
---Looks for the first match of `pattern` in the string `str`.
|
||||
---If it finds a match, it returns the indices of `str` where this occurrence
|
||||
---starts and ends; otherwise, it returns `nil`.
|
||||
---If the pattern has captures, the captured strings are returned,
|
||||
---after the two indexes ones.
|
||||
---If a capture is empty, its offset is returned instead.
|
||||
---
|
||||
---@param pattern string|table The regex pattern to use, either as a simple string or precompiled.
|
||||
---@param str string The string to search for valid matches.
|
||||
---@param offset? integer The position on the subject to start searching.
|
||||
---@param options? integer A bit field of matching options, eg: regex.NOTBOL | regex.NOTEMPTY
|
||||
---
|
||||
---@return integer? start Offset where the first match was found; `nil` if no match.
|
||||
---@return integer? end Offset where the first match ends; `nil` if no match.
|
||||
---@return (string|integer)? ... #List of captured matches; if the match is empty, its offset is returned instead.
|
||||
regex.find = function(pattern, str, offset, options)
|
||||
local res = { regex.find_offsets(pattern, str, offset, options) }
|
||||
local out = { }
|
||||
if #res == 0 then return end
|
||||
out[1] = res[1]
|
||||
out[2] = res[2]
|
||||
for i = 3,#res,2 do
|
||||
if res[i] > res[i+1] then
|
||||
-- Like in string.find, if the group has size 0, return the index
|
||||
table.insert(out, res[i])
|
||||
else
|
||||
table.insert(out, string.sub(str, res[i], res[i+1]))
|
||||
-- Moves to the end of the identified character.
|
||||
local function end_character(str, index)
|
||||
local byte = string.byte(str, index + 1)
|
||||
while byte and byte >= 128 and byte < 192 do
|
||||
index = index + 1
|
||||
byte = string.byte(str, index + 1)
|
||||
end
|
||||
return index
|
||||
end
|
||||
|
||||
-- Build off matching. For now, only support basic replacements, but capture
|
||||
-- groupings should be doable. We can even have custom group replacements and
|
||||
-- transformations and stuff in lua. Currently, this takes group replacements
|
||||
-- as \1 - \9.
|
||||
-- Should work on UTF-8 text.
|
||||
regex.gsub = function(pattern_string, str, replacement)
|
||||
local pattern = type(pattern_string) == "table" and
|
||||
pattern_string or regex.compile(pattern_string)
|
||||
local result, indices = ""
|
||||
local matches, replacements = {}, {}
|
||||
repeat
|
||||
indices = { regex.cmatch(pattern, str) }
|
||||
if #indices > 0 then
|
||||
table.insert(matches, indices)
|
||||
local currentReplacement = replacement
|
||||
if #indices > 2 then
|
||||
for i = 1, (#indices/2 - 1) do
|
||||
currentReplacement = string.gsub(
|
||||
currentReplacement,
|
||||
"\\" .. i,
|
||||
str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))
|
||||
)
|
||||
end
|
||||
end
|
||||
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
|
||||
table.insert(replacements, { indices[1], #currentReplacement+indices[1] })
|
||||
if indices[1] > 1 then
|
||||
result = result ..
|
||||
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
|
||||
else
|
||||
result = result .. currentReplacement
|
||||
end
|
||||
str = str:sub(indices[2])
|
||||
end
|
||||
end
|
||||
return table.unpack(out)
|
||||
until #indices == 0 or indices[1] == indices[2]
|
||||
return result .. str, matches, replacements
|
||||
end
|
||||
|
||||
|
|
|
@ -5,10 +5,7 @@ local Node = require "core.node"
|
|||
local View = require "core.view"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
---@class core.rootview : core.view
|
||||
---@field super core.view
|
||||
---@field root_node core.node
|
||||
---@field mouse core.view.position
|
||||
|
||||
local RootView = View:extend()
|
||||
|
||||
function RootView:new()
|
||||
|
@ -32,15 +29,11 @@ function RootView:defer_draw(fn, ...)
|
|||
end
|
||||
|
||||
|
||||
---@return core.node
|
||||
function RootView:get_active_node()
|
||||
local node = self.root_node:get_node_for_view(core.active_view)
|
||||
if not node then node = self:get_primary_node() end
|
||||
return node
|
||||
return self.root_node:get_node_for_view(core.active_view)
|
||||
end
|
||||
|
||||
|
||||
---@return core.node
|
||||
local function get_primary_node(node)
|
||||
if node.is_primary_node then
|
||||
return node
|
||||
|
@ -51,10 +44,8 @@ local function get_primary_node(node)
|
|||
end
|
||||
|
||||
|
||||
---@return core.node
|
||||
function RootView:get_active_node_default()
|
||||
local node = self.root_node:get_node_for_view(core.active_view)
|
||||
if not node then node = self:get_primary_node() end
|
||||
if node.locked then
|
||||
local default_view = self:get_primary_node().views[1]
|
||||
assert(default_view, "internal error: cannot find original document node.")
|
||||
|
@ -65,14 +56,11 @@ function RootView:get_active_node_default()
|
|||
end
|
||||
|
||||
|
||||
---@return core.node
|
||||
function RootView:get_primary_node()
|
||||
return get_primary_node(self.root_node)
|
||||
end
|
||||
|
||||
|
||||
---@param node core.node
|
||||
---@return core.node
|
||||
local function select_next_primary_node(node)
|
||||
if node.is_primary_node then return end
|
||||
if node.type ~= "leaf" then
|
||||
|
@ -86,14 +74,11 @@ local function select_next_primary_node(node)
|
|||
end
|
||||
|
||||
|
||||
---@return core.node
|
||||
function RootView:select_next_primary_node()
|
||||
return select_next_primary_node(self.root_node)
|
||||
end
|
||||
|
||||
|
||||
---@param doc core.doc
|
||||
---@return core.docview
|
||||
function RootView:open_doc(doc)
|
||||
local node = self:get_active_node_default()
|
||||
for i, view in ipairs(node.views) do
|
||||
|
@ -110,27 +95,17 @@ function RootView:open_doc(doc)
|
|||
end
|
||||
|
||||
|
||||
---@param keep_active boolean
|
||||
function RootView:close_all_docviews(keep_active)
|
||||
self.root_node:close_all_docviews(keep_active)
|
||||
end
|
||||
|
||||
|
||||
---Function to intercept mouse pressed events on the active view.
|
||||
---Do nothing by default.
|
||||
---@param button core.view.mousebutton
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param clicks integer
|
||||
-- Function to intercept mouse pressed events on the active view.
|
||||
-- Do nothing by default.
|
||||
function RootView.on_view_mouse_pressed(button, x, y, clicks)
|
||||
end
|
||||
|
||||
|
||||
---@param button core.view.mousebutton
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param clicks integer
|
||||
---@return boolean
|
||||
function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
|
@ -184,9 +159,6 @@ function RootView:set_show_overlay(overlay, status)
|
|||
end
|
||||
|
||||
|
||||
---@param button core.view.mousebutton
|
||||
---@param x number
|
||||
---@param y number
|
||||
function RootView:on_mouse_released(button, x, y, ...)
|
||||
if self.dragged_divider then
|
||||
self.dragged_divider = nil
|
||||
|
@ -245,10 +217,6 @@ local function resize_child_node(node, axis, value, delta)
|
|||
end
|
||||
|
||||
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param dx number
|
||||
---@param dy number
|
||||
function RootView:on_mouse_moved(x, y, dx, dy)
|
||||
if core.active_view == core.nag_view then
|
||||
core.request_cursor("arrow")
|
||||
|
@ -259,10 +227,10 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
|||
if self.dragged_divider then
|
||||
local node = self.dragged_divider
|
||||
if node.type == "hsplit" then
|
||||
x = common.clamp(x - node.position.x, 0, self.root_node.size.x * 0.95)
|
||||
x = common.clamp(x, 0, self.root_node.size.x * 0.95)
|
||||
resize_child_node(node, "x", x, dx)
|
||||
elseif node.type == "vsplit" then
|
||||
y = common.clamp(y - node.position.y, 0, self.root_node.size.y * 0.95)
|
||||
y = common.clamp(y, 0, self.root_node.size.y * 0.95)
|
||||
resize_child_node(node, "y", y, dy)
|
||||
end
|
||||
node.divider = common.clamp(node.divider, 0.01, 0.99)
|
||||
|
@ -285,13 +253,8 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
|||
|
||||
self.root_node:on_mouse_moved(x, y, dx, dy)
|
||||
|
||||
local last_overlapping_node = self.overlapping_node
|
||||
self.overlapping_node = self.root_node:get_child_overlapping_point(x, y)
|
||||
|
||||
if last_overlapping_node and last_overlapping_node ~= self.overlapping_node then
|
||||
last_overlapping_node:on_mouse_left()
|
||||
end
|
||||
|
||||
|
||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||
local tab_index = self.overlapping_node and self.overlapping_node:get_tab_overlapping_point(x, y)
|
||||
if self.overlapping_node and self.overlapping_node:get_scroll_button_index(x, y) then
|
||||
|
@ -306,23 +269,6 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
|||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_left()
|
||||
if self.overlapping_node then
|
||||
self.overlapping_node:on_mouse_left()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@param filename string
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@return boolean
|
||||
function RootView:on_file_dropped(filename, x, y)
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
return node and node.active_view:on_file_dropped(filename, x, y)
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_wheel(...)
|
||||
local x, y = self.mouse.x, self.mouse.y
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
|
@ -334,9 +280,6 @@ function RootView:on_text_input(...)
|
|||
core.active_view:on_text_input(...)
|
||||
end
|
||||
|
||||
function RootView:on_ime_text_editing(...)
|
||||
core.active_view:on_ime_text_editing(...)
|
||||
end
|
||||
|
||||
function RootView:on_focus_lost(...)
|
||||
-- We force a redraw so documents can redraw without the cursor.
|
||||
|
@ -345,12 +288,12 @@ end
|
|||
|
||||
|
||||
function RootView:interpolate_drag_overlay(overlay)
|
||||
self:move_towards(overlay, "x", overlay.to.x, nil, "tab_drag")
|
||||
self:move_towards(overlay, "y", overlay.to.y, nil, "tab_drag")
|
||||
self:move_towards(overlay, "w", overlay.to.w, nil, "tab_drag")
|
||||
self:move_towards(overlay, "h", overlay.to.h, nil, "tab_drag")
|
||||
self:move_towards(overlay, "x", overlay.to.x)
|
||||
self:move_towards(overlay, "y", overlay.to.y)
|
||||
self:move_towards(overlay, "w", overlay.to.w)
|
||||
self:move_towards(overlay, "h", overlay.to.h)
|
||||
|
||||
self:move_towards(overlay, "opacity", overlay.visible and 100 or 0, nil, "tab_drag")
|
||||
self:move_towards(overlay, "opacity", overlay.visible and 100 or 0)
|
||||
overlay.color[4] = overlay.base_color[4] * overlay.opacity / 100
|
||||
end
|
||||
|
||||
|
@ -438,8 +381,8 @@ function RootView:draw_grabbed_tab()
|
|||
local _,_, w, h = dn.node:get_tab_rect(dn.idx)
|
||||
local x = self.mouse.x - w / 2
|
||||
local y = self.mouse.y - h / 2
|
||||
local view = dn.node.views[dn.idx]
|
||||
self.root_node:draw_tab(view, true, true, false, x, y, w, h, true)
|
||||
local text = dn.node.views[dn.idx] and dn.node.views[dn.idx]:get_name() or ""
|
||||
self.root_node:draw_tab(text, true, true, false, x, y, w, h, true)
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -1,343 +0,0 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local Object = require "core.object"
|
||||
|
||||
---Scrollbar
|
||||
---Use Scrollbar:set_size to set the bounding box of the view the scrollbar belongs to.
|
||||
---Use Scrollbar:update to update the scrollbar animations.
|
||||
---Use Scrollbar:draw to draw the scrollbar.
|
||||
---Use Scrollbar:on_mouse_pressed, Scrollbar:on_mouse_released,
|
||||
---Scrollbar:on_mouse_moved and Scrollbar:on_mouse_left to react to mouse movements;
|
||||
---the scrollbar won't update automatically.
|
||||
---Use Scrollbar:set_percent to set the scrollbar location externally.
|
||||
---
|
||||
---To manage all the orientations, the scrollbar changes the coordinates system
|
||||
---accordingly. The "normal" coordinate system adapts the scrollbar coordinates
|
||||
---as if it's always a vertical scrollbar, positioned at the end of the bounding box.
|
||||
---@class core.scrollbar : core.object
|
||||
local Scrollbar = Object:extend()
|
||||
|
||||
---@class ScrollbarOptions
|
||||
---@field direction "v" | "h" @Vertical or Horizontal
|
||||
---@field alignment "s" | "e" @Start or End (left to right, top to bottom)
|
||||
---@field force_status "expanded" | "contracted" | false @Force the scrollbar status
|
||||
---@field expanded_size number? @Override the default value specified by `style.expanded_scrollbar_size`
|
||||
---@field contracted_size number? @Override the default value specified by `style.scrollbar_size`
|
||||
|
||||
---@param options ScrollbarOptions
|
||||
function Scrollbar:new(options)
|
||||
---Position information of the owner
|
||||
self.rect = {
|
||||
x = 0, y = 0, w = 0, h = 0,
|
||||
---Scrollable size
|
||||
scrollable = 0
|
||||
}
|
||||
self.normal_rect = {
|
||||
across = 0,
|
||||
along = 0,
|
||||
across_size = 0,
|
||||
along_size = 0,
|
||||
scrollable = 0
|
||||
}
|
||||
---@type integer @Position in percent [0-1]
|
||||
self.percent = 0
|
||||
---@type boolean @Scrollbar dragging status
|
||||
self.dragging = false
|
||||
---@type integer @Private. Used to offset the start of the drag from the top of the thumb
|
||||
self.drag_start_offset = 0
|
||||
---What is currently being hovered. `thumb` implies` track`
|
||||
self.hovering = { track = false, thumb = false }
|
||||
---@type "v" | "h"@Vertical or Horizontal
|
||||
self.direction = options.direction or "v"
|
||||
---@type "s" | "e" @Start or End (left to right, top to bottom)
|
||||
self.alignment = options.alignment or "e"
|
||||
---@type number @Private. Used to keep track of animations
|
||||
self.expand_percent = 0
|
||||
---@type "expanded" | "contracted" | false @Force the scrollbar status
|
||||
self.force_status = options.force_status
|
||||
self:set_forced_status(options.force_status)
|
||||
---@type number? @Override the default value specified by `style.expanded_scrollbar_size`
|
||||
self.contracted_size = options.contracted_size
|
||||
---@type number? @Override the default value specified by `style.scrollbar_size`
|
||||
self.expanded_size = options.expanded_size
|
||||
end
|
||||
|
||||
|
||||
---Set the status the scrollbar is forced to keep
|
||||
---@param status "expanded" | "contracted" | false @The status to force
|
||||
function Scrollbar:set_forced_status(status)
|
||||
self.force_status = status
|
||||
if self.force_status == "expanded" then
|
||||
self.expand_percent = 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Scrollbar:real_to_normal(x, y, w, h)
|
||||
x, y, w, h = x or 0, y or 0, w or 0, h or 0
|
||||
if self.direction == "v" then
|
||||
if self.alignment == "s" then
|
||||
x = (self.rect.x + self.rect.w) - x - w
|
||||
end
|
||||
return x, y, w, h
|
||||
else
|
||||
if self.alignment == "s" then
|
||||
y = (self.rect.y + self.rect.h) - y - h
|
||||
end
|
||||
return y, x, h, w
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Scrollbar:normal_to_real(x, y, w, h)
|
||||
x, y, w, h = x or 0, y or 0, w or 0, h or 0
|
||||
if self.direction == "v" then
|
||||
if self.alignment == "s" then
|
||||
x = (self.rect.x + self.rect.w) - x - w
|
||||
end
|
||||
return x, y, w, h
|
||||
else
|
||||
if self.alignment == "s" then
|
||||
x = (self.rect.y + self.rect.h) - x - w
|
||||
end
|
||||
return y, x, h, w
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Scrollbar:_get_thumb_rect_normal()
|
||||
local nr = self.normal_rect
|
||||
local sz = nr.scrollable
|
||||
if sz == math.huge or sz <= nr.along_size
|
||||
then
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
local scrollbar_size = self.contracted_size or style.scrollbar_size
|
||||
local expanded_scrollbar_size = self.expanded_size or style.expanded_scrollbar_size
|
||||
local along_size = math.max(20, nr.along_size * nr.along_size / sz)
|
||||
local across_size = scrollbar_size
|
||||
across_size = across_size + (expanded_scrollbar_size - scrollbar_size) * self.expand_percent
|
||||
return
|
||||
nr.across + nr.across_size - across_size,
|
||||
nr.along + self.percent * nr.scrollable * (nr.along_size - along_size) / (sz - nr.along_size),
|
||||
across_size,
|
||||
along_size
|
||||
end
|
||||
|
||||
---Get the thumb rect (the part of the scrollbar that can be dragged)
|
||||
---@return integer,integer,integer,integer @x, y, w, h
|
||||
function Scrollbar:get_thumb_rect()
|
||||
return self:normal_to_real(self:_get_thumb_rect_normal())
|
||||
end
|
||||
|
||||
|
||||
function Scrollbar:_get_track_rect_normal()
|
||||
local nr = self.normal_rect
|
||||
local sz = nr.scrollable
|
||||
if sz <= nr.along_size or sz == math.huge then
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
local scrollbar_size = self.contracted_size or style.scrollbar_size
|
||||
local expanded_scrollbar_size = self.expanded_size or style.expanded_scrollbar_size
|
||||
local across_size = scrollbar_size
|
||||
across_size = across_size + (expanded_scrollbar_size - scrollbar_size) * self.expand_percent
|
||||
return
|
||||
nr.across + nr.across_size - across_size,
|
||||
nr.along,
|
||||
across_size,
|
||||
nr.along_size
|
||||
end
|
||||
|
||||
---Get the track rect (the "background" of the scrollbar)
|
||||
---@return number,number,number,number @x, y, w, h
|
||||
function Scrollbar:get_track_rect()
|
||||
return self:normal_to_real(self:_get_track_rect_normal())
|
||||
end
|
||||
|
||||
|
||||
function Scrollbar:_overlaps_normal(x, y)
|
||||
local sx, sy, sw, sh = self:_get_thumb_rect_normal()
|
||||
local scrollbar_size = self.contracted_size or style.scrollbar_size
|
||||
local result
|
||||
if x >= sx - scrollbar_size * 3 and x <= sx + sw and y >= sy and y <= sy + sh then
|
||||
result = "thumb"
|
||||
else
|
||||
sx, sy, sw, sh = self:_get_track_rect_normal()
|
||||
if x >= sx - scrollbar_size * 3 and x <= sx + sw and y >= sy and y <= sy + sh then
|
||||
result = "track"
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---Get what part of the scrollbar the coordinates overlap
|
||||
---@return "thumb"|"track"|nil
|
||||
function Scrollbar:overlaps(x, y)
|
||||
x, y = self:real_to_normal(x, y)
|
||||
return self:_overlaps_normal(x, y)
|
||||
end
|
||||
|
||||
|
||||
function Scrollbar:_on_mouse_pressed_normal(button, x, y, clicks)
|
||||
local overlaps = self:_overlaps_normal(x, y)
|
||||
if overlaps then
|
||||
local _, along, _, along_size = self:_get_thumb_rect_normal()
|
||||
self.dragging = true
|
||||
if overlaps == "thumb" then
|
||||
self.drag_start_offset = along - y
|
||||
return true
|
||||
elseif overlaps == "track" then
|
||||
local nr = self.normal_rect
|
||||
self.drag_start_offset = - along_size / 2
|
||||
return common.clamp((y - nr.along - along_size / 2) / (nr.along_size - along_size), 0, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Updates the scrollbar with mouse pressed info.
|
||||
---Won't update the scrollbar position automatically.
|
||||
---Use Scrollbar:set_percent to update it.
|
||||
---
|
||||
---This sets the dragging status if needed.
|
||||
---
|
||||
---Returns a falsy value if the event happened outside the scrollbar.
|
||||
---Returns `true` if the thumb was pressed.
|
||||
---If the track was pressed this returns a value between 0 and 1
|
||||
---representing the percent of the position.
|
||||
---@return boolean|number
|
||||
function Scrollbar:on_mouse_pressed(button, x, y, clicks)
|
||||
if button ~= "left" then return end
|
||||
x, y = self:real_to_normal(x, y)
|
||||
return self:_on_mouse_pressed_normal(button, x, y, clicks)
|
||||
end
|
||||
|
||||
---Updates the scrollbar hover status.
|
||||
---This gets called by other functions and shouldn't be called manually
|
||||
function Scrollbar:_update_hover_status_normal(x, y)
|
||||
local overlaps = self:_overlaps_normal(x, y)
|
||||
self.hovering.thumb = overlaps == "thumb"
|
||||
self.hovering.track = self.hovering.thumb or overlaps == "track"
|
||||
return self.hovering.track or self.hovering.thumb
|
||||
end
|
||||
|
||||
function Scrollbar:_on_mouse_released_normal(button, x, y)
|
||||
self.dragging = false
|
||||
return self:_update_hover_status_normal(x, y)
|
||||
end
|
||||
|
||||
---Updates the scrollbar dragging status
|
||||
function Scrollbar:on_mouse_released(button, x, y)
|
||||
if button ~= "left" then return end
|
||||
x, y = self:real_to_normal(x, y)
|
||||
return self:_on_mouse_released_normal(button, x, y)
|
||||
end
|
||||
|
||||
|
||||
function Scrollbar:_on_mouse_moved_normal(x, y, dx, dy)
|
||||
if self.dragging then
|
||||
local nr = self.normal_rect
|
||||
return common.clamp((y - nr.along + self.drag_start_offset) / nr.along_size, 0, 1)
|
||||
end
|
||||
return self:_update_hover_status_normal(x, y)
|
||||
end
|
||||
|
||||
---Updates the scrollbar with mouse moved info.
|
||||
---Won't update the scrollbar position automatically.
|
||||
---Use Scrollbar:set_percent to update it.
|
||||
---
|
||||
---This updates the hovering status.
|
||||
---
|
||||
---Returns a falsy value if the event happened outside the scrollbar.
|
||||
---Returns `true` if the scrollbar is hovered.
|
||||
---If the scrollbar was being dragged, this returns a value between 0 and 1
|
||||
---representing the percent of the position.
|
||||
---@return boolean|number
|
||||
function Scrollbar:on_mouse_moved(x, y, dx, dy)
|
||||
x, y = self:real_to_normal(x, y)
|
||||
dx, dy = self:real_to_normal(dx, dy) -- TODO: do we need this? (is this even correct?)
|
||||
return self:_on_mouse_moved_normal(x, y, dx, dy)
|
||||
end
|
||||
|
||||
---Updates the scrollbar hovering status
|
||||
function Scrollbar:on_mouse_left()
|
||||
self.hovering.track, self.hovering.thumb = false, false
|
||||
end
|
||||
|
||||
---Updates the bounding box of the view the scrollbar belongs to.
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param w number
|
||||
---@param h number
|
||||
---@param scrollable number @size of the scrollable area
|
||||
function Scrollbar:set_size(x, y, w, h, scrollable)
|
||||
self.rect.x, self.rect.y, self.rect.w, self.rect.h = x, y, w, h
|
||||
self.rect.scrollable = scrollable
|
||||
|
||||
local nr = self.normal_rect
|
||||
nr.across, nr.along, nr.across_size, nr.along_size = self:real_to_normal(x, y, w, h)
|
||||
nr.scrollable = scrollable
|
||||
end
|
||||
|
||||
---Updates the scrollbar location
|
||||
---@param percent number @number between 0 and 1 representing the position of the middle part of the thumb
|
||||
function Scrollbar:set_percent(percent)
|
||||
self.percent = percent
|
||||
end
|
||||
|
||||
---Updates the scrollbar animations
|
||||
function Scrollbar:update()
|
||||
-- TODO: move the animation code to its own class
|
||||
if not self.force_status then
|
||||
local dest = (self.hovering.track or self.dragging) and 1 or 0
|
||||
local diff = math.abs(self.expand_percent - dest)
|
||||
if not config.transitions or diff < 0.05 or config.disabled_transitions["scroll"] then
|
||||
self.expand_percent = dest
|
||||
else
|
||||
local rate = 0.3
|
||||
if config.fps ~= 60 or config.animation_rate ~= 1 then
|
||||
local dt = 60 / config.fps
|
||||
rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt)
|
||||
end
|
||||
self.expand_percent = common.lerp(self.expand_percent, dest, rate)
|
||||
end
|
||||
if diff > 1e-8 then
|
||||
core.redraw = true
|
||||
end
|
||||
elseif self.force_status == "expanded" then
|
||||
self.expand_percent = 1
|
||||
elseif self.force_status == "contracted" then
|
||||
self.expand_percent = 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---Draw the scrollbar track
|
||||
function Scrollbar:draw_track()
|
||||
if not (self.hovering.track or self.dragging)
|
||||
and self.expand_percent == 0 then
|
||||
return
|
||||
end
|
||||
local color = { table.unpack(style.scrollbar_track) }
|
||||
color[4] = color[4] * self.expand_percent
|
||||
local x, y, w, h = self:get_track_rect()
|
||||
renderer.draw_rect(x, y, w, h, color)
|
||||
end
|
||||
|
||||
---Draw the scrollbar thumb
|
||||
function Scrollbar:draw_thumb()
|
||||
local highlight = self.hovering.thumb or self.dragging
|
||||
local color = highlight and style.scrollbar2 or style.scrollbar
|
||||
local x, y, w, h = self:get_thumb_rect()
|
||||
renderer.draw_rect(x, y, w, h, color)
|
||||
end
|
||||
|
||||
---Draw both the scrollbar track and thumb
|
||||
function Scrollbar:draw()
|
||||
self:draw_track()
|
||||
self:draw_thumb()
|
||||
end
|
||||
|
||||
|
||||
return Scrollbar
|
|
@ -1,57 +1,34 @@
|
|||
-- this file is used by lite-xl to setup the Lua environment when starting
|
||||
VERSION = "2.1.4r1"
|
||||
MOD_VERSION = "3"
|
||||
VERSION = "2.0.3"
|
||||
MOD_VERSION = "2"
|
||||
|
||||
SCALE = tonumber(os.getenv("LITE_SCALE") or os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or 1
|
||||
SCALE = tonumber(os.getenv("LITE_SCALE") or os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or SCALE
|
||||
PATHSEP = package.config:sub(1, 1)
|
||||
|
||||
EXEDIR = EXEFILE:match("^(.+)[/\\][^/\\]+$")
|
||||
if MACOS_RESOURCES then
|
||||
DATADIR = MACOS_RESOURCES
|
||||
else
|
||||
local prefix = os.getenv('LITE_PREFIX') or EXEDIR:match("^(.+)[/\\]bin$")
|
||||
DATADIR = prefix and (prefix .. PATHSEP .. 'share' .. PATHSEP .. 'lite-xl') or (EXEDIR .. PATHSEP .. 'data')
|
||||
local prefix = EXEDIR:match("^(.+)[/\\]bin$")
|
||||
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
|
||||
end
|
||||
USERDIR = (system.get_file_info(EXEDIR .. PATHSEP .. 'user') and (EXEDIR .. PATHSEP .. 'user'))
|
||||
or os.getenv("LITE_USERDIR")
|
||||
or ((os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. PATHSEP .. "lite-xl"))
|
||||
or (HOME and (HOME .. PATHSEP .. '.config' .. PATHSEP .. 'lite-xl'))
|
||||
USERDIR = (os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl")
|
||||
or (HOME and (HOME .. '/.config/lite-xl') or (EXEDIR .. '/user'))
|
||||
|
||||
package.path = DATADIR .. '/?.lua;'
|
||||
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
|
||||
|
||||
local suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so')
|
||||
package.cpath =
|
||||
USERDIR .. '/?.' .. ARCH .. "." .. suffix .. ";" ..
|
||||
USERDIR .. '/?/init.' .. ARCH .. "." .. suffix .. ";" ..
|
||||
USERDIR .. '/?.' .. suffix .. ";" ..
|
||||
USERDIR .. '/?/init.' .. suffix .. ";" ..
|
||||
DATADIR .. '/?.' .. ARCH .. "." .. suffix .. ";" ..
|
||||
DATADIR .. '/?/init.' .. ARCH .. "." .. suffix .. ";" ..
|
||||
DATADIR .. '/?.' .. suffix .. ";" ..
|
||||
DATADIR .. '/?/init.' .. suffix .. ";"
|
||||
|
||||
local dynamic_suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so')
|
||||
package.cpath = DATADIR .. '/?.' .. dynamic_suffix .. ";" .. USERDIR .. '/?.' .. dynamic_suffix
|
||||
package.native_plugins = {}
|
||||
package.searchers = { package.searchers[1], package.searchers[2], function(modname)
|
||||
local path, err = package.searchpath(modname, package.cpath)
|
||||
if not path then return err end
|
||||
local path = package.searchpath(modname, package.cpath)
|
||||
if not path then return nil end
|
||||
return system.load_native_plugin, path
|
||||
end }
|
||||
|
||||
table.pack = table.pack or pack or function(...) return {...} end
|
||||
table.unpack = table.unpack or unpack
|
||||
|
||||
bit32 = bit32 or require "core.bit"
|
||||
|
||||
require "core.utf8string"
|
||||
|
||||
-- Because AppImages change the working directory before running the executable,
|
||||
-- we need to change it back to the original one.
|
||||
-- https://github.com/AppImage/AppImageKit/issues/172
|
||||
-- https://github.com/AppImage/AppImageKit/pull/191
|
||||
local appimage_owd = os.getenv("OWD")
|
||||
if os.getenv("APPIMAGE") and appimage_owd then
|
||||
system.chdir(appimage_owd)
|
||||
end
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,6 @@ local style = {}
|
|||
style.padding = { x = common.round(14 * SCALE), y = common.round(7 * SCALE) }
|
||||
style.divider_size = common.round(1 * SCALE)
|
||||
style.scrollbar_size = common.round(4 * SCALE)
|
||||
style.expanded_scrollbar_size = common.round(12 * SCALE)
|
||||
style.caret_width = common.round(2 * SCALE)
|
||||
style.tab_width = common.round(170 * SCALE)
|
||||
|
||||
|
@ -28,7 +27,43 @@ style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 16 * SCALE,
|
|||
style.icon_big_font = style.icon_font:copy(23 * SCALE)
|
||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 15 * SCALE)
|
||||
|
||||
style.background = { common.color "#2e2e32" } -- Docview
|
||||
style.background2 = { common.color "#252529" } -- Treeview
|
||||
style.background3 = { common.color "#252529" } -- Command view
|
||||
style.text = { common.color "#97979c" }
|
||||
style.caret = { common.color "#93DDFA" }
|
||||
style.accent = { common.color "#e1e1e6" }
|
||||
-- style.dim - text color for nonactive tabs, tabs divider, prefix in log and
|
||||
-- search result, hotkeys for context menu and command view
|
||||
style.dim = { common.color "#525257" }
|
||||
style.divider = { common.color "#202024" } -- Line between nodes
|
||||
style.selection = { common.color "#48484f" }
|
||||
style.line_number = { common.color "#525259" }
|
||||
style.line_number2 = { common.color "#83838f" } -- With cursor
|
||||
style.line_highlight = { common.color "#343438" }
|
||||
style.scrollbar = { common.color "#414146" }
|
||||
style.scrollbar2 = { common.color "#4b4b52" } -- Hovered
|
||||
style.nagbar = { common.color "#FF0000" }
|
||||
style.nagbar_text = { common.color "#FFFFFF" }
|
||||
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
|
||||
style.drag_overlay = { common.color "rgba(255,255,255,0.1)" }
|
||||
style.drag_overlay_tab = { common.color "#93DDFA" }
|
||||
style.good = { common.color "#72b886" }
|
||||
style.warn = { common.color "#FFA94D" }
|
||||
style.error = { common.color "#FF3333" }
|
||||
style.modified = { common.color "#1c7c9c" }
|
||||
|
||||
style.syntax = {}
|
||||
style.syntax["normal"] = { common.color "#e1e1e6" }
|
||||
style.syntax["symbol"] = { common.color "#e1e1e6" }
|
||||
style.syntax["comment"] = { common.color "#676b6f" }
|
||||
style.syntax["keyword"] = { common.color "#E58AC9" } -- local function end if case
|
||||
style.syntax["keyword2"] = { common.color "#F77483" } -- self int float
|
||||
style.syntax["number"] = { common.color "#FFA94D" }
|
||||
style.syntax["literal"] = { common.color "#FFA94D" } -- true false nil
|
||||
style.syntax["string"] = { common.color "#f7c95c" }
|
||||
style.syntax["operator"] = { common.color "#93DDFA" } -- = + - / < >
|
||||
style.syntax["function"] = { common.color "#93DDFA" }
|
||||
|
||||
-- This can be used to override fonts per syntax group.
|
||||
-- The syntax highlighter will take existing values from this table and
|
||||
|
@ -37,6 +72,4 @@ style.syntax = {}
|
|||
style.syntax_fonts = {}
|
||||
-- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options)
|
||||
|
||||
style.log = {}
|
||||
|
||||
return style
|
||||
|
|
|
@ -7,44 +7,21 @@ local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} }
|
|||
|
||||
|
||||
function syntax.add(t)
|
||||
if type(t.space_handling) ~= "boolean" then t.space_handling = true end
|
||||
|
||||
if t.patterns then
|
||||
-- the rule %s+ gives us a performance gain for the tokenizer in lines with
|
||||
-- long amounts of consecutive spaces, can be disabled by plugins where it
|
||||
-- causes conflicts by declaring the table property: space_handling = false
|
||||
if t.space_handling then
|
||||
table.insert(t.patterns, { pattern = "%s+", type = "normal" })
|
||||
end
|
||||
|
||||
-- this rule gives us additional performance gain by matching every word
|
||||
-- that was not matched by the syntax patterns as a single token, preventing
|
||||
-- the tokenizer from iterating over each character individually which is a
|
||||
-- lot slower since iteration occurs in lua instead of C and adding to that
|
||||
-- it will also try to match every pattern to a single char (same as spaces)
|
||||
table.insert(t.patterns, { pattern = "%w+%f[%s]", type = "normal" })
|
||||
end
|
||||
|
||||
table.insert(syntax.items, t)
|
||||
end
|
||||
|
||||
|
||||
local function find(string, field)
|
||||
local best_match = 0
|
||||
local best_syntax
|
||||
for i = #syntax.items, 1, -1 do
|
||||
local t = syntax.items[i]
|
||||
local s, e = common.match_pattern(string, t[field] or {})
|
||||
if s and e - s > best_match then
|
||||
best_match = e - s
|
||||
best_syntax = t
|
||||
if common.match_pattern(string, t[field] or {}) then
|
||||
return t
|
||||
end
|
||||
end
|
||||
return best_syntax
|
||||
end
|
||||
|
||||
function syntax.get(filename, header)
|
||||
return (filename and find(filename, "files"))
|
||||
return find(filename, "files")
|
||||
or (header and find(header, "headers"))
|
||||
or plain_text_syntax
|
||||
end
|
||||
|
|
|
@ -3,14 +3,6 @@ local common = require "core.common"
|
|||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
local icon_colors = {
|
||||
bg = { common.color "#2e2e32ff" },
|
||||
color6 = { common.color "#e1e1e6ff" },
|
||||
color7 = { common.color "#ffa94dff" },
|
||||
color8 = { common.color "#93ddfaff" },
|
||||
color9 = { common.color "#f7c95cff" }
|
||||
};
|
||||
|
||||
local restore_command = {
|
||||
symbol = "w", action = function() system.set_window_mode("normal") end
|
||||
}
|
||||
|
@ -25,8 +17,6 @@ local title_commands = {
|
|||
{symbol = "X", action = function() core.quit() end},
|
||||
}
|
||||
|
||||
---@class core.titleview : core.view
|
||||
---@field super core.view
|
||||
local TitleView = View:extend()
|
||||
|
||||
local function title_view_height()
|
||||
|
@ -51,10 +41,6 @@ function TitleView:configure_hit_test(borderless)
|
|||
end
|
||||
end
|
||||
|
||||
function TitleView:on_scale_change()
|
||||
self:configure_hit_test(self.visible)
|
||||
end
|
||||
|
||||
function TitleView:update()
|
||||
self.size.y = self.visible and title_view_height() or 0
|
||||
title_commands[2] = core.window_mode == "maximized" and restore_command or maximize_command
|
||||
|
@ -67,11 +53,7 @@ function TitleView:draw_window_title()
|
|||
local ox, oy = self:get_content_offset()
|
||||
local color = style.text
|
||||
local x, y = ox + style.padding.x, oy + style.padding.y
|
||||
common.draw_text(style.icon_font, icon_colors.bg, "5", nil, x, y, 0, h)
|
||||
common.draw_text(style.icon_font, icon_colors.color6, "6", nil, x, y, 0, h)
|
||||
common.draw_text(style.icon_font, icon_colors.color7, "7", nil, x, y, 0, h)
|
||||
common.draw_text(style.icon_font, icon_colors.color8, "8", nil, x, y, 0, h)
|
||||
x = common.draw_text(style.icon_font, icon_colors.color9, "9 ", nil, x, y, 0, h)
|
||||
x = common.draw_text(style.icon_font, color, "M ", nil, x, y, 0, h)
|
||||
local title = core.compose_window_title(core.window_title)
|
||||
common.draw_text(style.font, color, title, nil, x, y, 0, h)
|
||||
end
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
local core = require "core"
|
||||
local syntax = require "core.syntax"
|
||||
local config = require "core.config"
|
||||
local common = require "core.common"
|
||||
|
||||
local tokenizer = {}
|
||||
local bad_patterns = {}
|
||||
|
||||
local function push_token(t, type, text)
|
||||
if not text or #text == 0 then return end
|
||||
type = type or "normal"
|
||||
local prev_type = t[#t-1]
|
||||
local prev_text = t[#t]
|
||||
if prev_type and (prev_type == type or (prev_text:ufind("^%s*$") and type ~= "incomplete")) then
|
||||
if prev_type and (prev_type == type or prev_text:find("^%s*$")) then
|
||||
t[#t-1] = type
|
||||
t[#t] = prev_text .. text
|
||||
else
|
||||
|
@ -42,47 +38,41 @@ local function push_tokens(t, syn, pattern, full_text, find_results)
|
|||
local fin = find_results[i + 1] - 1
|
||||
local type = pattern.type[i - 2]
|
||||
-- ↑ (i - 2) to convert from [3; n] to [1; n]
|
||||
local text = full_text:usub(start, fin)
|
||||
local text = full_text:sub(start, fin)
|
||||
push_token(t, syn.symbols[text] or type, text)
|
||||
end
|
||||
else
|
||||
local start, fin = find_results[1], find_results[2]
|
||||
local text = full_text:usub(start, fin)
|
||||
local text = full_text:sub(start, fin)
|
||||
push_token(t, syn.symbols[text] or pattern.type, text)
|
||||
end
|
||||
end
|
||||
|
||||
-- State is a string of bytes, where the count of bytes represents the depth
|
||||
-- of the subsyntax we are currently in. Each individual byte represents the
|
||||
-- index of the pattern for the current subsyntax in relation to its parent
|
||||
-- syntax. Using a string of bytes allows us to have as many subsyntaxes as
|
||||
-- bytes can be stored on a string while keeping some level of performance in
|
||||
-- comparison to a Lua table. The only limitation is that a syntax would not
|
||||
-- be able to contain more than 255 patterns.
|
||||
--
|
||||
-- Lets say a state contains 2 bytes byte #1 with value `3` and byte #2 with
|
||||
-- a value of `5`. This would mean that on the parent syntax at index `3` a
|
||||
-- pattern subsyntax that matched current text was found, then inside that
|
||||
-- subsyntax another subsyntax pattern at index `5` that matched current text
|
||||
-- was also found.
|
||||
|
||||
-- Calling `push_subsyntax` appends the current subsyntax pattern index to the
|
||||
-- state and increases the stack depth. Calling `pop_subsyntax` clears the
|
||||
-- last appended subsyntax and decreases the stack.
|
||||
-- State is a 32-bit number that is four separate bytes, illustrating how many
|
||||
-- differnet delimiters we have open, and which subsyntaxes we have active.
|
||||
-- At most, there are 3 subsyntaxes active at the same time. Beyond that,
|
||||
-- does not support further highlighting.
|
||||
|
||||
-- You can think of it as a maximum 4 integer (0-255) stack. It always has
|
||||
-- 1 integer in it. Calling `push_subsyntax` increases the stack depth. Calling
|
||||
-- `pop_subsyntax` decreases it. The integers represent the index of a pattern
|
||||
-- that we're following in the syntax. The top of the stack can be any valid
|
||||
-- pattern index, any integer lower in the stack must represent a pattern that
|
||||
-- specifies a subsyntax.
|
||||
|
||||
-- If you do not have subsyntaxes in your syntax, the three most
|
||||
-- singificant numbers will always be 0, the stack will only ever be length 1
|
||||
-- and the state variable will only ever range from 0-255.
|
||||
local function retrieve_syntax_state(incoming_syntax, state)
|
||||
local current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||
incoming_syntax, nil, state:byte(1) or 0, 1
|
||||
if
|
||||
current_pattern_idx > 0
|
||||
and
|
||||
current_syntax.patterns[current_pattern_idx]
|
||||
then
|
||||
-- If the state is not empty we iterate over each byte, and find which
|
||||
incoming_syntax, nil, state, 0
|
||||
if state > 0 and (state > 255 or current_syntax.patterns[state].syntax) then
|
||||
-- If we have higher bits, then decode them one at a time, and find which
|
||||
-- syntax we're using. Rather than walking the bytes, and calling into
|
||||
-- `syntax` each time, we could probably cache this in a single table.
|
||||
for i = 1, #state do
|
||||
local target = state:byte(i)
|
||||
for i = 0, 2 do
|
||||
local target = bit32.extract(state, i*8, 8)
|
||||
if target ~= 0 then
|
||||
if current_syntax.patterns[target].syntax then
|
||||
subsyntax_info = current_syntax.patterns[target]
|
||||
|
@ -102,87 +92,32 @@ local function retrieve_syntax_state(incoming_syntax, state)
|
|||
return current_syntax, subsyntax_info, current_pattern_idx, current_level
|
||||
end
|
||||
|
||||
---Return the list of syntaxes used in the specified state.
|
||||
---@param base_syntax table @The initial base syntax (the syntax of the file)
|
||||
---@param state string @The state of the tokenizer to extract from
|
||||
---@return table @Array of syntaxes starting from the innermost one
|
||||
function tokenizer.extract_subsyntaxes(base_syntax, state)
|
||||
local current_syntax
|
||||
local t = {}
|
||||
repeat
|
||||
current_syntax = retrieve_syntax_state(base_syntax, state)
|
||||
table.insert(t, current_syntax)
|
||||
state = string.sub(state, 2)
|
||||
until #state == 0
|
||||
return t
|
||||
end
|
||||
|
||||
local function report_bad_pattern(log_fn, syntax, pattern_idx, msg, ...)
|
||||
if not bad_patterns[syntax] then
|
||||
bad_patterns[syntax] = { }
|
||||
end
|
||||
if bad_patterns[syntax][pattern_idx] then return end
|
||||
bad_patterns[syntax][pattern_idx] = true
|
||||
log_fn("Malformed pattern #%d <%s> in %s language plugin.\n" .. msg,
|
||||
pattern_idx,
|
||||
syntax.patterns[pattern_idx].pattern or syntax.patterns[pattern_idx].regex,
|
||||
syntax.name or "unnamed", ...)
|
||||
end
|
||||
|
||||
---@param incoming_syntax table
|
||||
---@param text string
|
||||
---@param state string
|
||||
function tokenizer.tokenize(incoming_syntax, text, state, resume)
|
||||
local res
|
||||
function tokenizer.tokenize(incoming_syntax, text, state)
|
||||
local res = {}
|
||||
local i = 1
|
||||
|
||||
if #incoming_syntax.patterns == 0 then
|
||||
return { "normal", text }
|
||||
end
|
||||
|
||||
state = state or string.char(0)
|
||||
|
||||
if resume then
|
||||
res = resume.res
|
||||
-- Remove "incomplete" tokens
|
||||
while res[#res-1] == "incomplete" do
|
||||
table.remove(res, #res)
|
||||
table.remove(res, #res)
|
||||
end
|
||||
i = resume.i
|
||||
state = resume.state
|
||||
end
|
||||
|
||||
res = res or {}
|
||||
|
||||
state = state or 0
|
||||
-- incoming_syntax : the parent syntax of the file.
|
||||
-- state : a string of bytes representing syntax state (see above)
|
||||
|
||||
-- state : a 32-bit number representing syntax state (see above)
|
||||
|
||||
-- current_syntax : the syntax we're currently in.
|
||||
-- subsyntax_info : info about the delimiters of this subsyntax.
|
||||
-- current_pattern_idx: the index of the pattern we're on for this syntax.
|
||||
-- current_level : how many subsyntaxes deep we are.
|
||||
local current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||
retrieve_syntax_state(incoming_syntax, state)
|
||||
|
||||
|
||||
-- Should be used to set the state variable. Don't modify it directly.
|
||||
local function set_subsyntax_pattern_idx(pattern_idx)
|
||||
current_pattern_idx = pattern_idx
|
||||
local state_len = #state
|
||||
if current_level > state_len then
|
||||
state = state .. string.char(pattern_idx)
|
||||
elseif state_len == 1 then
|
||||
state = string.char(pattern_idx)
|
||||
else
|
||||
state = ("%s%s%s"):format(
|
||||
state:sub(1,current_level-1),
|
||||
string.char(pattern_idx),
|
||||
state:sub(current_level+1)
|
||||
)
|
||||
end
|
||||
state = bit32.replace(state, pattern_idx, current_level*8, 8)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
local function push_subsyntax(entering_syntax, pattern_idx)
|
||||
set_subsyntax_pattern_idx(pattern_idx)
|
||||
current_level = current_level + 1
|
||||
|
@ -191,96 +126,45 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
|
|||
entering_syntax.syntax or syntax.get(entering_syntax.syntax)
|
||||
current_pattern_idx = 0
|
||||
end
|
||||
|
||||
|
||||
local function pop_subsyntax()
|
||||
current_level = current_level - 1
|
||||
state = string.sub(state, 1, current_level)
|
||||
set_subsyntax_pattern_idx(0)
|
||||
current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||
current_level = current_level - 1
|
||||
set_subsyntax_pattern_idx(0)
|
||||
current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||
retrieve_syntax_state(incoming_syntax, state)
|
||||
end
|
||||
|
||||
|
||||
local function find_text(text, p, offset, at_start, close)
|
||||
local target, res = p.pattern or p.regex, { 1, offset - 1 }
|
||||
local p_idx = close and 2 or 1
|
||||
local code = type(target) == "table" and target[p_idx] or target
|
||||
|
||||
if p.whole_line == nil then p.whole_line = { } end
|
||||
if p.whole_line[p_idx] == nil then
|
||||
-- Match patterns that start with '^'
|
||||
p.whole_line[p_idx] = code:umatch("^%^") and true or false
|
||||
if p.whole_line[p_idx] then
|
||||
-- Remove '^' from the beginning of the pattern
|
||||
if type(target) == "table" then
|
||||
target[p_idx] = code:usub(2)
|
||||
code = target[p_idx]
|
||||
else
|
||||
p.pattern = p.pattern and code:usub(2)
|
||||
p.regex = p.regex and code:usub(2)
|
||||
code = p.pattern or p.regex
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local target, res = p.pattern or p.regex, { 1, offset - 1 }, p.regex
|
||||
local code = type(target) == "table" and target[close and 2 or 1] or target
|
||||
if p.regex and type(p.regex) ~= "table" then
|
||||
p._regex = p._regex or regex.compile(p.regex)
|
||||
code = p._regex
|
||||
end
|
||||
|
||||
end
|
||||
repeat
|
||||
local next = res[2] + 1
|
||||
-- If the pattern contained '^', allow matching only the whole line
|
||||
if p.whole_line[p_idx] and next > 1 then
|
||||
return
|
||||
-- go to the start of the next utf-8 character
|
||||
while text:byte(next) and common.is_utf8_cont(text, next) do
|
||||
next = next + 1
|
||||
end
|
||||
res = p.pattern and { text:ufind((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) }
|
||||
or { regex.find(code, text, text:ucharpos(next), (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) }
|
||||
if p.regex and #res > 0 then -- set correct utf8 len for regex result
|
||||
local char_pos_1 = res[1] > next and string.ulen(text:sub(1, res[1])) or next
|
||||
local char_pos_2 = string.ulen(text:sub(1, res[2]))
|
||||
for i=3,#res do
|
||||
res[i] = string.ulen(text:sub(1, res[i] - 1)) + 1
|
||||
end
|
||||
res[1] = char_pos_1
|
||||
res[2] = char_pos_2
|
||||
end
|
||||
if res[1] and target[3] then
|
||||
-- Check to see if the escaped character is there,
|
||||
-- and if it is not itself escaped.
|
||||
res = p.pattern and { text:find(at_start and "^" .. code or code, next) }
|
||||
or { regex.match(code, text, next, at_start and regex.ANCHORED or 0) }
|
||||
if res[1] and close and target[3] then
|
||||
local count = 0
|
||||
for i = res[1] - 1, 1, -1 do
|
||||
if text:ubyte(i) ~= target[3]:ubyte() then break end
|
||||
if text:byte(i) ~= target[3]:byte() then break end
|
||||
count = count + 1
|
||||
end
|
||||
if count % 2 == 0 then
|
||||
-- The match is not escaped, so confirm it
|
||||
break
|
||||
elseif not close then
|
||||
-- The *open* match is escaped, so avoid it
|
||||
return
|
||||
end
|
||||
-- Check to see if the escaped character is there,
|
||||
-- and if it is not itself escaped.
|
||||
if count % 2 == 0 then break end
|
||||
end
|
||||
until not res[1] or not close or not target[3]
|
||||
return table.unpack(res)
|
||||
end
|
||||
|
||||
local text_len = text:ulen()
|
||||
local start_time = system.get_time()
|
||||
local starting_i = i
|
||||
while text_len ~= nil and i <= text_len do
|
||||
-- Every 200 chars, check if we're out of time
|
||||
if i - starting_i > 200 then
|
||||
starting_i = i
|
||||
if system.get_time() - start_time > 0.5 / config.fps then
|
||||
-- We're out of time
|
||||
push_token(res, "incomplete", string.usub(text, i))
|
||||
return res, string.char(0), {
|
||||
res = res,
|
||||
i = i,
|
||||
state = state
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
while i <= #text do
|
||||
-- continue trying to match the end pattern of a pair if we have a state set
|
||||
if current_pattern_idx > 0 then
|
||||
local p = current_syntax.patterns[current_pattern_idx]
|
||||
|
@ -292,12 +176,12 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
|
|||
-- precedence over ending the delimiter in the subsyntax.
|
||||
if subsyntax_info then
|
||||
local ss, se = find_text(text, subsyntax_info, i, false, true)
|
||||
-- If we find that we end the subsyntax before the
|
||||
-- If we find that we end the subsyntax before the
|
||||
-- delimiter, push the token, and signal we shouldn't
|
||||
-- treat the bit after as a token to be normally parsed
|
||||
-- (as it's the syntax delimiter).
|
||||
if ss and (s == nil or ss < s) then
|
||||
push_token(res, p.type, text:usub(i, ss - 1))
|
||||
push_token(res, p.type, text:sub(i, ss - 1))
|
||||
i = ss
|
||||
cont = false
|
||||
end
|
||||
|
@ -306,11 +190,11 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
|
|||
-- continue on as normal.
|
||||
if cont then
|
||||
if s then
|
||||
push_token(res, p.type, text:usub(i, e))
|
||||
push_token(res, p.type, text:sub(i, e))
|
||||
set_subsyntax_pattern_idx(0)
|
||||
i = e + 1
|
||||
else
|
||||
push_token(res, p.type, text:usub(i))
|
||||
push_token(res, p.type, text:sub(i))
|
||||
break
|
||||
end
|
||||
end
|
||||
|
@ -318,15 +202,13 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
|
|||
-- General end of syntax check. Applies in the case where
|
||||
-- we're ending early in the middle of a delimiter, or
|
||||
-- just normally, upon finding a token.
|
||||
while subsyntax_info do
|
||||
if subsyntax_info then
|
||||
local s, e = find_text(text, subsyntax_info, i, true, true)
|
||||
if s then
|
||||
push_token(res, subsyntax_info.type, text:usub(i, e))
|
||||
push_token(res, subsyntax_info.type, text:sub(i, e))
|
||||
-- On finding unescaped delimiter, pop it.
|
||||
pop_subsyntax()
|
||||
i = e + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -335,28 +217,6 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
|
|||
for n, p in ipairs(current_syntax.patterns) do
|
||||
local find_results = { find_text(text, p, i, true, false) }
|
||||
if find_results[1] then
|
||||
-- Check for patterns successfully matching nothing
|
||||
if find_results[1] > find_results[2] then
|
||||
report_bad_pattern(core.warn, current_syntax, n,
|
||||
"Pattern successfully matched, but nothing was captured.")
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- Check for patterns with mismatching number of `types`
|
||||
local type_is_table = type(p.type) == "table"
|
||||
local n_types = type_is_table and #p.type or 1
|
||||
if #find_results == 2 and type_is_table then
|
||||
report_bad_pattern(core.warn, current_syntax, n,
|
||||
"Token type is a table, but a string was expected.")
|
||||
p.type = p.type[1]
|
||||
elseif #find_results - 1 > n_types then
|
||||
report_bad_pattern(core.error, current_syntax, n,
|
||||
"Not enough token types: got %d needed %d.", n_types, #find_results - 1)
|
||||
elseif #find_results - 1 < n_types then
|
||||
report_bad_pattern(core.warn, current_syntax, n,
|
||||
"Too many token types: got %d needed %d.", n_types, #find_results - 1)
|
||||
end
|
||||
|
||||
-- matched pattern; make and add tokens
|
||||
push_tokens(res, current_syntax, p, text, find_results)
|
||||
-- update state if this was a start|end pattern pair
|
||||
|
@ -364,7 +224,7 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
|
|||
-- If we have a subsyntax, push that onto the subsyntax stack.
|
||||
if p.syntax then
|
||||
push_subsyntax(p, n)
|
||||
else
|
||||
else
|
||||
set_subsyntax_pattern_idx(n)
|
||||
end
|
||||
end
|
||||
|
@ -372,14 +232,18 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
|
|||
i = find_results[2] + 1
|
||||
matched = true
|
||||
break
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
-- consume character if we didn't match
|
||||
if not matched then
|
||||
push_token(res, "normal", text:usub(i, i))
|
||||
i = i + 1
|
||||
local n = 0
|
||||
-- reach the next character
|
||||
while text:byte(i + n + 1) and common.is_utf8_cont(text, i + n + 1) do
|
||||
n = n + 1
|
||||
end
|
||||
push_token(res, "normal", text:sub(i, i + n))
|
||||
i = i + n + 1
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
--------------------------------------------------------------------------------
|
||||
-- inject utf8 functions to strings
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local utf8 = require "utf8extra"
|
||||
|
||||
string.ubyte = utf8.byte
|
||||
string.uchar = utf8.char
|
||||
string.ufind = utf8.find
|
||||
string.ugmatch = utf8.gmatch
|
||||
string.ugsub = utf8.gsub
|
||||
string.ulen = utf8.len
|
||||
string.ulower = utf8.lower
|
||||
string.umatch = utf8.match
|
||||
string.ureverse = utf8.reverse
|
||||
string.usub = utf8.sub
|
||||
string.uupper = utf8.upper
|
||||
|
||||
string.uescape = utf8.escape
|
||||
string.ucharpos = utf8.charpos
|
||||
string.unext = utf8.next
|
||||
string.uinsert = utf8.insert
|
||||
string.uremove = utf8.remove
|
||||
string.uwidth = utf8.width
|
||||
string.uwidthindex = utf8.widthindex
|
||||
string.utitle = utf8.title
|
||||
string.ufold = utf8.fold
|
||||
string.uncasecmp = utf8.ncasecmp
|
||||
|
||||
string.uoffset = utf8.offset
|
||||
string.ucodepoint = utf8.codepoint
|
||||
string.ucodes = utf8.codes
|
|
@ -1,51 +1,10 @@
|
|||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local common = require "core.common"
|
||||
local Object = require "core.object"
|
||||
local Scrollbar = require "core.scrollbar"
|
||||
|
||||
---@class core.view.position
|
||||
---@field x number
|
||||
---@field y number
|
||||
|
||||
---@class core.view.scroll
|
||||
---@field x number
|
||||
---@field y number
|
||||
---@field to core.view.position
|
||||
|
||||
---@class core.view.thumbtrack
|
||||
---@field thumb number
|
||||
---@field track number
|
||||
|
||||
---@class core.view.thumbtrackwidth
|
||||
---@field thumb number
|
||||
---@field track number
|
||||
---@field to core.view.thumbtrack
|
||||
|
||||
---@class core.view.scrollbar
|
||||
---@field x core.view.thumbtrack
|
||||
---@field y core.view.thumbtrack
|
||||
---@field w core.view.thumbtrackwidth
|
||||
---@field h core.view.thumbtrack
|
||||
|
||||
---@alias core.view.cursor "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'"
|
||||
|
||||
---@alias core.view.mousebutton "'left'" | "'right'"
|
||||
|
||||
---@alias core.view.context "'application'" | "'session'"
|
||||
|
||||
---Base view.
|
||||
---@class core.view : core.object
|
||||
---@field context core.view.context
|
||||
---@field super core.object
|
||||
---@field position core.view.position
|
||||
---@field size core.view.position
|
||||
---@field scroll core.view.scroll
|
||||
---@field cursor core.view.cursor
|
||||
---@field scrollable boolean
|
||||
---@field v_scrollbar core.scrollbar
|
||||
---@field h_scrollbar core.scrollbar
|
||||
---@field current_scale number
|
||||
local View = Object:extend()
|
||||
|
||||
-- context can be "application" or "session". The instance of objects
|
||||
|
@ -59,18 +18,14 @@ function View:new()
|
|||
self.scroll = { x = 0, y = 0, to = { x = 0, y = 0 } }
|
||||
self.cursor = "arrow"
|
||||
self.scrollable = false
|
||||
self.v_scrollbar = Scrollbar({direction = "v", alignment = "e"})
|
||||
self.h_scrollbar = Scrollbar({direction = "h", alignment = "e"})
|
||||
self.current_scale = SCALE
|
||||
end
|
||||
|
||||
function View:move_towards(t, k, dest, rate, name)
|
||||
function View:move_towards(t, k, dest, rate)
|
||||
if type(t) ~= "table" then
|
||||
return self:move_towards(self, t, k, dest, rate, name)
|
||||
return self:move_towards(self, t, k, dest, rate)
|
||||
end
|
||||
local val = t[k]
|
||||
local diff = math.abs(val - dest)
|
||||
if not config.transitions or diff < 0.5 or config.disabled_transitions[name] then
|
||||
if not config.transitions or math.abs(val - dest) < 0.5 then
|
||||
t[k] = dest
|
||||
else
|
||||
rate = rate or 0.5
|
||||
|
@ -80,7 +35,7 @@ function View:move_towards(t, k, dest, rate, name)
|
|||
end
|
||||
t[k] = common.lerp(val, dest, rate)
|
||||
end
|
||||
if diff > 1e-8 then
|
||||
if val ~= dest then
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
|
@ -91,153 +46,66 @@ function View:try_close(do_close)
|
|||
end
|
||||
|
||||
|
||||
---@return string
|
||||
function View:get_name()
|
||||
return "---"
|
||||
end
|
||||
|
||||
|
||||
---@return number
|
||||
function View:get_scrollable_size()
|
||||
return math.huge
|
||||
end
|
||||
|
||||
---@return number
|
||||
function View:get_h_scrollable_size()
|
||||
return 0
|
||||
|
||||
function View:get_scrollbar_rect()
|
||||
local sz = self:get_scrollable_size()
|
||||
if sz <= self.size.y or sz == math.huge then
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
local h = math.max(20, self.size.y * self.size.y / sz)
|
||||
return
|
||||
self.position.x + self.size.x - style.scrollbar_size,
|
||||
self.position.y + self.scroll.y * (self.size.y - h) / (sz - self.size.y),
|
||||
style.scrollbar_size,
|
||||
h
|
||||
end
|
||||
|
||||
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@return boolean
|
||||
function View:scrollbar_overlaps_point(x, y)
|
||||
return not (not (self.v_scrollbar:overlaps(x, y) or self.h_scrollbar:overlaps(x, y)))
|
||||
local sx, sy, sw, sh = self:get_scrollbar_rect()
|
||||
return x >= sx - sw * 3 and x < sx + sw and y >= sy and y < sy + sh
|
||||
end
|
||||
|
||||
|
||||
---@return boolean
|
||||
function View:scrollbar_dragging()
|
||||
return self.v_scrollbar.dragging or self.h_scrollbar.dragging
|
||||
end
|
||||
|
||||
|
||||
---@return boolean
|
||||
function View:scrollbar_hovering()
|
||||
return self.v_scrollbar.hovering.track or self.h_scrollbar.hovering.track
|
||||
end
|
||||
|
||||
|
||||
---@param button core.view.mousebutton
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param clicks integer
|
||||
---return boolean
|
||||
function View:on_mouse_pressed(button, x, y, clicks)
|
||||
if not self.scrollable then return end
|
||||
local result = self.v_scrollbar:on_mouse_pressed(button, x, y, clicks)
|
||||
if result then
|
||||
if result ~= true then
|
||||
self.scroll.to.y = result * self:get_scrollable_size()
|
||||
end
|
||||
return true
|
||||
end
|
||||
result = self.h_scrollbar:on_mouse_pressed(button, x, y, clicks)
|
||||
if result then
|
||||
if result ~= true then
|
||||
self.scroll.to.x = result * self:get_h_scrollable_size()
|
||||
end
|
||||
if self:scrollbar_overlaps_point(x, y) then
|
||||
self.dragging_scrollbar = true
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@param button core.view.mousebutton
|
||||
---@param x number
|
||||
---@param y number
|
||||
function View:on_mouse_released(button, x, y)
|
||||
if not self.scrollable then return end
|
||||
self.v_scrollbar:on_mouse_released(button, x, y)
|
||||
self.h_scrollbar:on_mouse_released(button, x, y)
|
||||
self.dragging_scrollbar = false
|
||||
end
|
||||
|
||||
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param dx number
|
||||
---@param dy number
|
||||
function View:on_mouse_moved(x, y, dx, dy)
|
||||
if not self.scrollable then return end
|
||||
local result
|
||||
if self.h_scrollbar.dragging then goto skip_v_scrollbar end
|
||||
result = self.v_scrollbar:on_mouse_moved(x, y, dx, dy)
|
||||
if result then
|
||||
if result ~= true then
|
||||
self.scroll.to.y = result * self:get_scrollable_size()
|
||||
if not config.animate_drag_scroll then
|
||||
self:clamp_scroll_position()
|
||||
self.scroll.y = self.scroll.to.y
|
||||
end
|
||||
end
|
||||
-- hide horizontal scrollbar
|
||||
self.h_scrollbar:on_mouse_left()
|
||||
return true
|
||||
end
|
||||
::skip_v_scrollbar::
|
||||
result = self.h_scrollbar:on_mouse_moved(x, y, dx, dy)
|
||||
if result then
|
||||
if result ~= true then
|
||||
self.scroll.to.x = result * self:get_h_scrollable_size()
|
||||
if not config.animate_drag_scroll then
|
||||
self:clamp_scroll_position()
|
||||
self.scroll.x = self.scroll.to.x
|
||||
end
|
||||
end
|
||||
return true
|
||||
if self.dragging_scrollbar then
|
||||
local delta = self:get_scrollable_size() / self.size.y * dy
|
||||
self.scroll.to.y = self.scroll.to.y + delta
|
||||
end
|
||||
self.hovered_scrollbar = self:scrollbar_overlaps_point(x, y)
|
||||
end
|
||||
|
||||
|
||||
function View:on_mouse_left()
|
||||
if not self.scrollable then return end
|
||||
self.v_scrollbar:on_mouse_left()
|
||||
self.h_scrollbar:on_mouse_left()
|
||||
end
|
||||
|
||||
|
||||
---@param filename string
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@return boolean
|
||||
function View:on_file_dropped(filename, x, y)
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
---@param text string
|
||||
function View:on_text_input(text)
|
||||
-- no-op
|
||||
end
|
||||
|
||||
function View:on_mouse_wheel(y)
|
||||
|
||||
function View:on_ime_text_editing(text, start, length)
|
||||
-- no-op
|
||||
end
|
||||
|
||||
|
||||
---@param y number @Vertical scroll delta; positive is "up"
|
||||
---@param x number @Horizontal scroll delta; positive is "left"
|
||||
---@return boolean @Capture event
|
||||
function View:on_mouse_wheel(y, x)
|
||||
-- no-op
|
||||
end
|
||||
|
||||
---Can be overriden to listen for scale change events to apply
|
||||
---any neccesary changes in sizes, padding, etc...
|
||||
---@param new_scale number
|
||||
---@param prev_scale number
|
||||
function View:on_scale_change(new_scale, prev_scale) end
|
||||
|
||||
function View:get_content_bounds()
|
||||
local x = self.scroll.x
|
||||
local y = self.scroll.y
|
||||
|
@ -245,8 +113,6 @@ function View:get_content_bounds()
|
|||
end
|
||||
|
||||
|
||||
---@return number x
|
||||
---@return number y
|
||||
function View:get_content_offset()
|
||||
local x = common.round(self.position.x - self.scroll.x)
|
||||
local y = common.round(self.position.y - self.scroll.y)
|
||||
|
@ -257,40 +123,16 @@ end
|
|||
function View:clamp_scroll_position()
|
||||
local max = self:get_scrollable_size() - self.size.y
|
||||
self.scroll.to.y = common.clamp(self.scroll.to.y, 0, max)
|
||||
|
||||
max = self:get_h_scrollable_size() - self.size.x
|
||||
self.scroll.to.x = common.clamp(self.scroll.to.x, 0, max)
|
||||
end
|
||||
|
||||
|
||||
function View:update_scrollbar()
|
||||
local v_scrollable = self:get_scrollable_size()
|
||||
self.v_scrollbar:set_size(self.position.x, self.position.y, self.size.x, self.size.y, v_scrollable)
|
||||
self.v_scrollbar:set_percent(self.scroll.y/v_scrollable)
|
||||
self.v_scrollbar:update()
|
||||
|
||||
local h_scrollable = self:get_h_scrollable_size()
|
||||
self.h_scrollbar:set_size(self.position.x, self.position.y, self.size.x, self.size.y, h_scrollable)
|
||||
self.h_scrollbar:set_percent(self.scroll.x/h_scrollable)
|
||||
self.h_scrollbar:update()
|
||||
end
|
||||
|
||||
|
||||
function View:update()
|
||||
if self.current_scale ~= SCALE then
|
||||
self:on_scale_change(SCALE, self.current_scale)
|
||||
self.current_scale = SCALE
|
||||
end
|
||||
|
||||
self:clamp_scroll_position()
|
||||
self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3, "scroll")
|
||||
self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3, "scroll")
|
||||
if not self.scrollable then return end
|
||||
self:update_scrollbar()
|
||||
self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3)
|
||||
self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3)
|
||||
end
|
||||
|
||||
|
||||
---@param color renderer.color
|
||||
function View:draw_background(color)
|
||||
local x, y = self.position.x, self.position.y
|
||||
local w, h = self.size.x, self.size.y
|
||||
|
@ -299,8 +141,10 @@ end
|
|||
|
||||
|
||||
function View:draw_scrollbar()
|
||||
self.v_scrollbar:draw()
|
||||
self.h_scrollbar:draw()
|
||||
local x, y, w, h = self:get_scrollbar_rect()
|
||||
local highlight = self.hovered_scrollbar or self.dragging_scrollbar
|
||||
local color = highlight and style.scrollbar2 or style.scrollbar
|
||||
renderer.draw_rect(x, y, w, h, color)
|
||||
end
|
||||
|
||||
|
||||
|
|
Binary file not shown.
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
|
@ -10,66 +10,14 @@ local RootView = require "core.rootview"
|
|||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
config.plugins.autocomplete = common.merge({
|
||||
-- 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,
|
||||
-- Maximum amount of symbols to cache per document
|
||||
max_symbols = 4000,
|
||||
-- Font size of the description box
|
||||
desc_font_size = 12,
|
||||
-- The config specification used by gui generators
|
||||
config_spec = {
|
||||
name = "Autocomplete",
|
||||
{
|
||||
label = "Minimum Length",
|
||||
description = "Amount of characters that need to be written for autocomplete to popup.",
|
||||
path = "min_len",
|
||||
type = "number",
|
||||
default = 3,
|
||||
min = 1,
|
||||
max = 5
|
||||
},
|
||||
{
|
||||
label = "Maximum Height",
|
||||
description = "The maximum amount of visible items.",
|
||||
path = "max_height",
|
||||
type = "number",
|
||||
default = 6,
|
||||
min = 1,
|
||||
max = 20
|
||||
},
|
||||
{
|
||||
label = "Maximum Suggestions",
|
||||
description = "The maximum amount of scrollable items.",
|
||||
path = "max_suggestions",
|
||||
type = "number",
|
||||
default = 100,
|
||||
min = 10,
|
||||
max = 10000
|
||||
},
|
||||
{
|
||||
label = "Maximum Symbols",
|
||||
description = "Maximum amount of symbols to cache per document.",
|
||||
path = "max_symbols",
|
||||
type = "number",
|
||||
default = 4000,
|
||||
min = 1000,
|
||||
max = 10000
|
||||
},
|
||||
{
|
||||
label = "Description Font Size",
|
||||
description = "Font size of the description box.",
|
||||
path = "desc_font_size",
|
||||
type = "number",
|
||||
default = 12,
|
||||
min = 8
|
||||
}
|
||||
}
|
||||
}, config.plugins.autocomplete)
|
||||
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 = {}
|
||||
|
||||
|
@ -85,7 +33,7 @@ local triggered_manually = false
|
|||
|
||||
local mt = { __tostring = function(t) return t.text end }
|
||||
|
||||
function autocomplete.add(t, manually_triggered)
|
||||
function autocomplete.add(t, triggered_manually)
|
||||
local items = {}
|
||||
for text, info in pairs(t.items) do
|
||||
if type(info) == "table" then
|
||||
|
@ -95,10 +43,9 @@ function autocomplete.add(t, manually_triggered)
|
|||
{
|
||||
text = text,
|
||||
info = info.info,
|
||||
desc = info.desc, -- Description shown on item selected
|
||||
onhover = info.onhover, -- A callback called once when item is hovered
|
||||
onselect = info.onselect, -- A callback called when item is selected
|
||||
data = info.data -- Optional data that can be used on cb
|
||||
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
|
||||
)
|
||||
|
@ -109,7 +56,7 @@ function autocomplete.add(t, manually_triggered)
|
|||
end
|
||||
end
|
||||
|
||||
if not manually_triggered then
|
||||
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 }
|
||||
|
@ -119,43 +66,26 @@ end
|
|||
--
|
||||
-- Thread that scans open document symbols and cache them
|
||||
--
|
||||
local max_symbols = config.plugins.autocomplete.max_symbols
|
||||
local max_symbols = config.max_symbols
|
||||
|
||||
core.add_thread(function()
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
|
||||
local function get_syntax_symbols(symbols, doc)
|
||||
if doc.syntax then
|
||||
for sym in pairs(doc.syntax.symbols) do
|
||||
symbols[sym] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function get_symbols(doc)
|
||||
local s = {}
|
||||
get_syntax_symbols(s, doc)
|
||||
if doc.disable_symbols then return s end
|
||||
if doc.disable_symbols then return {} end
|
||||
local i = 1
|
||||
local s = {}
|
||||
local symbols_count = 0
|
||||
while i <= #doc.lines do
|
||||
while i < #doc.lines do
|
||||
for sym in doc.lines[i]:gmatch(config.symbol_pattern) do
|
||||
if not s[sym] then
|
||||
symbols_count = symbols_count + 1
|
||||
if symbols_count > max_symbols then
|
||||
s = nil
|
||||
doc.disable_symbols = true
|
||||
local filename_message
|
||||
if doc.filename then
|
||||
filename_message = "document " .. doc.filename
|
||||
else
|
||||
filename_message = "unnamed document"
|
||||
end
|
||||
core.status_view:show_message("!", style.accent,
|
||||
"Too many symbols in "..filename_message..
|
||||
": stopping auto-complete for this document according to "..
|
||||
"config.plugins.autocomplete.max_symbols."
|
||||
)
|
||||
"Too many symbols in document "..doc.filename..
|
||||
": stopping auto-complete for this document according to config.max_symbols.")
|
||||
collectgarbage('collect')
|
||||
return {}
|
||||
end
|
||||
|
@ -202,7 +132,6 @@ core.add_thread(function()
|
|||
for _, doc in ipairs(core.docs) do
|
||||
if not cache_is_valid(doc) then
|
||||
valid = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -212,14 +141,12 @@ end)
|
|||
|
||||
|
||||
local partial = ""
|
||||
local suggestions_offset = 1
|
||||
local suggestions_idx = 1
|
||||
local suggestions = {}
|
||||
local last_line, last_col
|
||||
|
||||
|
||||
local function reset_suggestions()
|
||||
suggestions_offset = 1
|
||||
suggestions_idx = 1
|
||||
suggestions = {}
|
||||
|
||||
|
@ -232,6 +159,16 @@ local function reset_suggestions()
|
|||
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 ""
|
||||
|
@ -262,8 +199,6 @@ local function update_suggestions()
|
|||
j = j + 1
|
||||
end
|
||||
end
|
||||
suggestions_idx = 1
|
||||
suggestions_offset = 1
|
||||
end
|
||||
|
||||
local function get_partial_symbol()
|
||||
|
@ -274,161 +209,67 @@ local function get_partial_symbol()
|
|||
end
|
||||
|
||||
local function get_active_view()
|
||||
if core.active_view:is(DocView) then
|
||||
if getmetatable(core.active_view) == DocView then
|
||||
return core.active_view
|
||||
end
|
||||
end
|
||||
|
||||
local last_max_width = 0
|
||||
local function get_suggestions_rect(av)
|
||||
if #suggestions == 0 then
|
||||
last_max_width = 0
|
||||
return 0, 0, 0, 0
|
||||
end
|
||||
|
||||
local line, col = av.doc:get_selection()
|
||||
local x, y = av:get_line_screen_position(line, col - #partial)
|
||||
local x, y = av:get_line_screen_position(line)
|
||||
x = x + av:get_col_x_offset(line, col - #partial)
|
||||
y = y + av:get_line_height() + style.padding.y
|
||||
local font = av:get_font()
|
||||
local th = font:get_height()
|
||||
|
||||
local ah = config.plugins.autocomplete.max_height
|
||||
|
||||
local max_items = math.min(ah, #suggestions)
|
||||
|
||||
local show_count = math.min(#suggestions, ah)
|
||||
local start_index = math.max(suggestions_idx-(ah-1), 1)
|
||||
|
||||
local max_width = 0
|
||||
for i = start_index, start_index + show_count - 1 do
|
||||
local s = suggestions[i]
|
||||
for _, s in ipairs(suggestions) do
|
||||
local w = font:get_width(s.text)
|
||||
if s.info then
|
||||
w = w + style.font:get_width(s.info) + style.padding.x
|
||||
end
|
||||
max_width = math.max(max_width, w)
|
||||
end
|
||||
max_width = math.max(last_max_width, max_width)
|
||||
last_max_width = max_width
|
||||
|
||||
max_width = max_width + style.padding.x * 2
|
||||
x = x - style.padding.x
|
||||
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 > core.root_view.size.x then
|
||||
max_width = core.root_view.size.x
|
||||
end
|
||||
if max_width < 150 * SCALE then
|
||||
max_width = 150 * SCALE
|
||||
end
|
||||
|
||||
-- if portion not visiable to right, reposition to DocView right margin
|
||||
if x + max_width > core.root_view.size.x then
|
||||
x = (av.size.x + av.position.x) - max_width
|
||||
if max_width < 150 then
|
||||
max_width = 150
|
||||
end
|
||||
|
||||
return
|
||||
x,
|
||||
x - style.padding.x,
|
||||
y - style.padding.y,
|
||||
max_width,
|
||||
max_width + style.padding.x * 2,
|
||||
max_items * (th + style.padding.y) + style.padding.y
|
||||
end
|
||||
|
||||
local function wrap_line(line, max_chars)
|
||||
if #line > max_chars then
|
||||
local lines = {}
|
||||
local line_len = #line
|
||||
local new_line = ""
|
||||
local prev_char = ""
|
||||
local position = 0
|
||||
local indent = line:match("^%s+")
|
||||
for char in line:gmatch(".") do
|
||||
position = position + 1
|
||||
if #new_line < max_chars then
|
||||
new_line = new_line .. char
|
||||
prev_char = char
|
||||
if position >= line_len then
|
||||
table.insert(lines, new_line)
|
||||
end
|
||||
else
|
||||
if
|
||||
not prev_char:match("%s")
|
||||
and
|
||||
not string.sub(line, position+1, 1):match("%s")
|
||||
and
|
||||
position < line_len
|
||||
then
|
||||
new_line = new_line .. "-"
|
||||
end
|
||||
table.insert(lines, new_line)
|
||||
if indent then
|
||||
new_line = indent .. char
|
||||
else
|
||||
new_line = char
|
||||
end
|
||||
end
|
||||
end
|
||||
return lines
|
||||
end
|
||||
return line
|
||||
end
|
||||
|
||||
local previous_scale = SCALE
|
||||
local desc_font = style.code_font:copy(
|
||||
config.plugins.autocomplete.desc_font_size * SCALE
|
||||
)
|
||||
local function draw_description_box(text, av, sx, sy, sw, sh)
|
||||
if previous_scale ~= SCALE then
|
||||
desc_font = style.code_font:copy(
|
||||
config.plugins.autocomplete.desc_font_size * SCALE
|
||||
)
|
||||
previous_scale = SCALE
|
||||
end
|
||||
|
||||
local font = desc_font
|
||||
local lh = font:get_height()
|
||||
local y = sy + style.padding.y
|
||||
local x = sx + sw + style.padding.x / 4
|
||||
local width = 0
|
||||
local char_width = font:get_width(" ")
|
||||
local draw_left = false;
|
||||
|
||||
local max_chars = 0
|
||||
if sx - av.position.x < av.size.x - (sx - av.position.x) - sw then
|
||||
max_chars = (((av.size.x+av.position.x) - x) / char_width) - 5
|
||||
else
|
||||
draw_left = true;
|
||||
max_chars = (
|
||||
(sx - av.position.x - (style.padding.x / 4) - style.scrollbar_size)
|
||||
/ char_width
|
||||
) - 5
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for line in string.gmatch(text.."\n", "(.-)\n") do
|
||||
local wrapper_lines = wrap_line(line, max_chars)
|
||||
if type(wrapper_lines) == "table" then
|
||||
for _, wrapped_line in pairs(wrapper_lines) do
|
||||
width = math.max(width, font:get_width(wrapped_line))
|
||||
table.insert(lines, wrapped_line)
|
||||
end
|
||||
else
|
||||
width = math.max(width, font:get_width(line))
|
||||
table.insert(lines, line)
|
||||
end
|
||||
width = math.max(width, style.font:get_width(line))
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
if draw_left then
|
||||
x = sx - (style.padding.x / 4) - width - (style.padding.x * 2)
|
||||
end
|
||||
|
||||
local height = #lines * font:get_height()
|
||||
local height = #lines * style.font:get_height()
|
||||
|
||||
-- draw background rect
|
||||
renderer.draw_rect(
|
||||
x,
|
||||
sx + sw + style.padding.x / 4,
|
||||
sy,
|
||||
width + style.padding.x * 2,
|
||||
height + style.padding.y * 2,
|
||||
|
@ -436,10 +277,13 @@ local function draw_description_box(text, av, sx, sy, sw, sh)
|
|||
)
|
||||
|
||||
-- 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(
|
||||
font, style.text, line, "left",
|
||||
x + style.padding.x, y, width, lh
|
||||
style.font, style.text, line, "left", x + style.padding.x, y, width, lh
|
||||
)
|
||||
y = y + lh
|
||||
end
|
||||
|
@ -460,38 +304,26 @@ 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
|
||||
local show_count = math.min(#suggestions, ah)
|
||||
local start_index = suggestions_offset
|
||||
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 info_size = s.info and (style.font:get_width(s.info) + style.padding.x) or style.padding.x
|
||||
|
||||
local color = (i == suggestions_idx) and style.accent or style.text
|
||||
-- Push clip to avoid that the suggestion text gets drawn over suggestion type/icon
|
||||
core.push_clip_rect(rx + style.padding.x, y, rw - info_size - style.padding.x, lh)
|
||||
local x_adv = common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
||||
core.pop_clip_rect()
|
||||
-- If the text wasn't fully visible, draw an ellipsis
|
||||
if x_adv > rx + rw - info_size then
|
||||
local ellipsis_size = font:get_width("…")
|
||||
local ell_x = rx + rw - info_size - ellipsis_size
|
||||
renderer.draw_rect(ell_x, y, ellipsis_size, lh, style.background3)
|
||||
common.draw_text(font, color, "…", "left", ell_x, y, ellipsis_size, lh)
|
||||
end
|
||||
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
||||
if s.info then
|
||||
color = (i == suggestions_idx) and style.text or style.dim
|
||||
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.onhover then
|
||||
s.onhover(suggestions_idx, s)
|
||||
s.onhover = nil
|
||||
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)
|
||||
|
@ -615,11 +447,8 @@ function autocomplete.open(on_close)
|
|||
end
|
||||
|
||||
local av = get_active_view()
|
||||
if av then
|
||||
partial = get_partial_symbol()
|
||||
last_line, last_col = av.doc:get_selection()
|
||||
update_suggestions()
|
||||
end
|
||||
last_line, last_col = av.doc:get_selection()
|
||||
update_suggestions()
|
||||
end
|
||||
|
||||
function autocomplete.close()
|
||||
|
@ -651,62 +480,26 @@ end
|
|||
-- Commands
|
||||
--
|
||||
local function predicate()
|
||||
local active_docview = get_active_view()
|
||||
return active_docview and #suggestions > 0, active_docview
|
||||
return get_active_view() and #suggestions > 0
|
||||
end
|
||||
|
||||
command.add(predicate, {
|
||||
["autocomplete:complete"] = function(dv)
|
||||
local doc = dv.doc
|
||||
local item = suggestions[suggestions_idx]
|
||||
local text = item.text
|
||||
local inserted = false
|
||||
if item.onselect then
|
||||
inserted = item.onselect(suggestions_idx, item)
|
||||
end
|
||||
if not inserted then
|
||||
local current_partial = get_partial_symbol()
|
||||
local sz = #current_partial
|
||||
|
||||
for _, line1, col1, line2, _ in doc:get_selections(true) do
|
||||
local n = col1 - 1
|
||||
local line = doc.lines[line1]
|
||||
for i = 1, sz + 1 do
|
||||
local j = sz - i
|
||||
local subline = line:sub(n - j, n)
|
||||
local subpartial = current_partial:sub(i, -1)
|
||||
if subpartial == subline then
|
||||
doc:remove(line1, col1, line2, n - j)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
doc:text_input(item.text)
|
||||
end
|
||||
["autocomplete:complete"] = function()
|
||||
local doc = core.active_view.doc
|
||||
local line, col = doc:get_selection()
|
||||
local text = suggestions[suggestions_idx].text
|
||||
doc:insert(line, col, text)
|
||||
doc:remove(line, col, line, col - #partial)
|
||||
doc:set_selection(line, col + #text - #partial)
|
||||
reset_suggestions()
|
||||
end,
|
||||
|
||||
["autocomplete:previous"] = function()
|
||||
suggestions_idx = (suggestions_idx - 2) % #suggestions + 1
|
||||
|
||||
local ah = math.min(config.plugins.autocomplete.max_height, #suggestions)
|
||||
if suggestions_offset > suggestions_idx then
|
||||
suggestions_offset = suggestions_idx
|
||||
elseif suggestions_offset + ah < suggestions_idx + 1 then
|
||||
suggestions_offset = suggestions_idx - ah + 1
|
||||
end
|
||||
suggestions_idx = math.max(suggestions_idx - 1, 1)
|
||||
end,
|
||||
|
||||
["autocomplete:next"] = function()
|
||||
suggestions_idx = (suggestions_idx % #suggestions) + 1
|
||||
|
||||
local ah = math.min(config.plugins.autocomplete.max_height, #suggestions)
|
||||
if suggestions_offset + ah < suggestions_idx + 1 then
|
||||
suggestions_offset = suggestions_idx - ah + 1
|
||||
elseif suggestions_offset > suggestions_idx then
|
||||
suggestions_offset = suggestions_idx
|
||||
end
|
||||
suggestions_idx = math.min(suggestions_idx + 1, #suggestions)
|
||||
end,
|
||||
|
||||
["autocomplete:cycle"] = function()
|
||||
|
|
|
@ -1,109 +1,43 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local Doc = require "core.doc"
|
||||
local Node = require "core.node"
|
||||
local common = require "core.common"
|
||||
local dirwatch = require "core.dirwatch"
|
||||
|
||||
config.plugins.autoreload = common.merge({
|
||||
always_show_nagview = false,
|
||||
config_spec = {
|
||||
name = "Autoreload",
|
||||
{
|
||||
label = "Always Show Nagview",
|
||||
description = "Alerts you if an opened file changes externally even if you haven't modified it.",
|
||||
path = "always_show_nagview",
|
||||
type = "toggle",
|
||||
default = false
|
||||
}
|
||||
}
|
||||
}, config.plugins.autoreload)
|
||||
|
||||
local watch = dirwatch.new()
|
||||
local times = setmetatable({}, { __mode = "k" })
|
||||
local visible = setmetatable({}, { __mode = "k" })
|
||||
|
||||
local function get_project_doc_watch(doc)
|
||||
for i, v in ipairs(core.project_directories) do
|
||||
if doc.abs_filename:find(v.name, 1, true) == 1 then return v.watch end
|
||||
end
|
||||
return watch
|
||||
end
|
||||
|
||||
local function update_time(doc)
|
||||
times[doc] = system.get_file_info(doc.filename).modified
|
||||
local info = system.get_file_info(doc.filename)
|
||||
times[doc] = info.modified
|
||||
end
|
||||
|
||||
local function reload_doc(doc)
|
||||
doc:reload()
|
||||
local fp = io.open(doc.filename, "r")
|
||||
local text = fp:read("*a")
|
||||
fp:close()
|
||||
|
||||
local sel = { doc:get_selection() }
|
||||
doc:remove(1, 1, math.huge, math.huge)
|
||||
doc:insert(1, 1, text:gsub("\r", ""):gsub("\n$", ""))
|
||||
doc:set_selection(table.unpack(sel))
|
||||
|
||||
update_time(doc)
|
||||
core.redraw = true
|
||||
doc:clean()
|
||||
core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename)
|
||||
end
|
||||
|
||||
local function check_prompt_reload(doc)
|
||||
if doc and doc.deferred_reload then
|
||||
core.nag_view:show("File Changed", doc.filename .. " has changed. Reload this file?", {
|
||||
{ text = "Yes", default_yes = true },
|
||||
{ text = "No", default_no = true }
|
||||
}, function(item)
|
||||
if item.text == "Yes" then reload_doc(doc) end
|
||||
doc.deferred_reload = false
|
||||
end)
|
||||
end
|
||||
end
|
||||
local on_modify = core.on_dirmonitor_modify
|
||||
|
||||
local function doc_changes_visiblity(doc, visibility)
|
||||
if doc and visible[doc] ~= visibility and doc.abs_filename then
|
||||
visible[doc] = visibility
|
||||
if visibility then check_prompt_reload(doc) end
|
||||
get_project_doc_watch(doc):watch(doc.abs_filename, visibility)
|
||||
end
|
||||
end
|
||||
|
||||
local on_check = dirwatch.check
|
||||
function dirwatch:check(change_callback, ...)
|
||||
on_check(self, function(dir)
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc.abs_filename and (dir == common.dirname(doc.abs_filename) or dir == doc.abs_filename) then
|
||||
local info = system.get_file_info(doc.filename or "")
|
||||
if info and info.type == "file" and times[doc] ~= info.modified then
|
||||
if not doc:is_dirty() and not config.plugins.autoreload.always_show_nagview then
|
||||
reload_doc(doc)
|
||||
else
|
||||
doc.deferred_reload = true
|
||||
if doc == core.active_view.doc then check_prompt_reload(doc) end
|
||||
end
|
||||
end
|
||||
end
|
||||
core.on_dirmonitor_modify = function(dir, filepath)
|
||||
local abs_filename = dir.name .. PATHSEP .. filepath
|
||||
for _, doc in ipairs(core.docs) do
|
||||
local info = system.get_file_info(doc.filename or "")
|
||||
if doc.abs_filename == abs_filename and info and times[doc] ~= info.modified then
|
||||
reload_doc(doc)
|
||||
break
|
||||
end
|
||||
change_callback(dir)
|
||||
end, ...)
|
||||
end
|
||||
|
||||
local core_set_active_view = core.set_active_view
|
||||
function core.set_active_view(view)
|
||||
core_set_active_view(view)
|
||||
doc_changes_visiblity(view.doc, true)
|
||||
end
|
||||
|
||||
local node_set_active_view = Node.set_active_view
|
||||
function Node:set_active_view(view)
|
||||
if self.active_view then doc_changes_visiblity(self.active_view.doc, false) end
|
||||
node_set_active_view(self, view)
|
||||
doc_changes_visiblity(self.active_view.doc, true)
|
||||
end
|
||||
|
||||
core.add_thread(function()
|
||||
while true do
|
||||
-- because we already hook this function above; we only
|
||||
-- need to check the file.
|
||||
watch:check(function() end)
|
||||
coroutine.yield(0.05)
|
||||
end
|
||||
end)
|
||||
on_modify(dir, filepath)
|
||||
end
|
||||
|
||||
-- patch `Doc.save|load` to store modified time
|
||||
local load = Doc.load
|
||||
|
@ -117,8 +51,6 @@ end
|
|||
|
||||
Doc.save = function(self, ...)
|
||||
local res = save(self, ...)
|
||||
-- if starting with an unsaved document with a filename.
|
||||
if not times[self] then get_project_doc_watch(self):watch(self.abs_filename, true) end
|
||||
update_time(self)
|
||||
return res
|
||||
end
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local ContextMenu = require "core.contextmenu"
|
||||
local RootView = require "core.rootview"
|
||||
local config = require "core.config"
|
||||
|
||||
local menu = ContextMenu()
|
||||
local on_view_mouse_pressed = RootView.on_view_mouse_pressed
|
||||
|
@ -33,9 +32,9 @@ function RootView:draw(...)
|
|||
menu:draw()
|
||||
end
|
||||
|
||||
command.add("core.docview!", {
|
||||
["context:show"] = function(dv)
|
||||
menu:show(dv.position.x, dv.position.y)
|
||||
command.add(nil, {
|
||||
["context:show"] = function()
|
||||
menu:show(core.active_view.position.x, core.active_view.position.y)
|
||||
end
|
||||
})
|
||||
|
||||
|
@ -43,43 +42,36 @@ keymap.add {
|
|||
["menu"] = "context:show"
|
||||
}
|
||||
|
||||
command.add(function() return menu.show_context_menu == true end, {
|
||||
["context:focus-previous"] = function()
|
||||
menu:focus_previous()
|
||||
end,
|
||||
["context:focus-next"] = function()
|
||||
menu:focus_next()
|
||||
end,
|
||||
["context:hide"] = function()
|
||||
menu:hide()
|
||||
end,
|
||||
["context:on-selected"] = function()
|
||||
menu:call_selected_item()
|
||||
end,
|
||||
})
|
||||
keymap.add { ["return"] = "context:on-selected" }
|
||||
keymap.add { ["up"] = "context:focus-previous" }
|
||||
keymap.add { ["down"] = "context:focus-next" }
|
||||
keymap.add { ["escape"] = "context:hide" }
|
||||
|
||||
|
||||
local cmds = {
|
||||
{ text = "Cut", command = "doc:cut" },
|
||||
{ text = "Copy", command = "doc:copy" },
|
||||
{ text = "Paste", command = "doc:paste" },
|
||||
ContextMenu.DIVIDER,
|
||||
{ text = "Find", command = "find-replace:find" },
|
||||
{ text = "Replace", command = "find-replace:replace" }
|
||||
}
|
||||
|
||||
if config.plugins.scale ~= false and require("plugins.scale") then
|
||||
table.move(cmds, 4, 6, 7)
|
||||
cmds[4] = { text = "Font +", command = "scale:increase" }
|
||||
cmds[5] = { text = "Font -", command = "scale:decrease" }
|
||||
cmds[6] = { text = "Font Reset", command = "scale:reset" }
|
||||
local function copy_log()
|
||||
local item = core.active_view.hovered_item
|
||||
if item then
|
||||
system.set_clipboard(core.get_log(item))
|
||||
end
|
||||
end
|
||||
|
||||
menu:register("core.docview", cmds)
|
||||
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 = "Cut", command = "doc:cut" },
|
||||
{ text = "Copy", command = "doc:copy" },
|
||||
{ text = "Paste", command = "doc:paste" },
|
||||
{ text = "Font +", command = "scale:increase" },
|
||||
{ text = "Font -", command = "scale:decrease" },
|
||||
{ text = "Font Reset", command = "scale:reset" },
|
||||
ContextMenu.DIVIDER,
|
||||
{ text = "Find", command = "find-replace:find" },
|
||||
{ text = "Replace", command = "find-replace:replace" }
|
||||
})
|
||||
end
|
||||
|
||||
return menu
|
||||
|
|
|
@ -1,258 +1,95 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local core_syntax = require "core.syntax"
|
||||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
local tokenizer = require "core.tokenizer"
|
||||
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
local comments_cache = {}
|
||||
local auto_detect_max_lines = 150
|
||||
|
||||
|
||||
local function indent_occurrences_more_than_once(stat, idx)
|
||||
if stat[idx-1] and stat[idx-1] == stat[idx] then
|
||||
return true
|
||||
elseif stat[idx+1] and stat[idx+1] == stat[idx] then
|
||||
return true
|
||||
local function add_to_stat(stat, val)
|
||||
for i = 1, #stat do
|
||||
if val == stat[i][1] then
|
||||
stat[i][2] = stat[i][2] + 1
|
||||
return
|
||||
end
|
||||
end
|
||||
return false
|
||||
stat[#stat + 1] = {val, 1}
|
||||
end
|
||||
|
||||
|
||||
local function optimal_indent_from_stat(stat)
|
||||
if #stat == 0 then return nil, 0 end
|
||||
table.sort(stat, function(a, b) return a > b end)
|
||||
local best_indent = 0
|
||||
local best_score = 0
|
||||
local count = #stat
|
||||
for x=1, count do
|
||||
local indent = stat[x]
|
||||
local bins = {}
|
||||
for k = 1, #stat do
|
||||
local indent = stat[k][1]
|
||||
local score = 0
|
||||
for y=1, count do
|
||||
if y ~= x and stat[y] % indent == 0 then
|
||||
score = score + 1
|
||||
elseif
|
||||
indent > stat[y]
|
||||
and
|
||||
indent_occurrences_more_than_once(stat, y)
|
||||
then
|
||||
score = 0
|
||||
break
|
||||
local mult_prev, lines_prev
|
||||
for i = k, #stat do
|
||||
if stat[i][1] % indent == 0 then
|
||||
local mult = stat[i][1] / indent
|
||||
if not mult_prev or (mult_prev + 1 == mult and lines_prev / stat[i][2] > 0.1) then
|
||||
-- we add the number of lines to the score only if the previous
|
||||
-- multiple of "indent" was populated with enough lines.
|
||||
score = score + stat[i][2]
|
||||
end
|
||||
mult_prev, lines_prev = mult, stat[i][2]
|
||||
end
|
||||
end
|
||||
if score > best_score then
|
||||
best_indent = indent
|
||||
best_score = score
|
||||
end
|
||||
if score > 0 then
|
||||
break
|
||||
end
|
||||
bins[#bins + 1] = {indent, score}
|
||||
end
|
||||
return best_score > 0 and best_indent or nil, best_score
|
||||
table.sort(bins, function(a, b) return a[2] > b[2] end)
|
||||
return bins[1][1], bins[1][2]
|
||||
end
|
||||
|
||||
|
||||
local function escape_comment_tokens(token)
|
||||
local special_chars = "*-%[].()+?^$"
|
||||
local escaped = ""
|
||||
for x=1, token:len() do
|
||||
local found = false
|
||||
for y=1, special_chars:len() do
|
||||
if token:sub(x, x) == special_chars:sub(y, y) then
|
||||
escaped = escaped .. "%" .. token:sub(x, x)
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
escaped = escaped .. token:sub(x, x)
|
||||
-- return nil if it is a comment or blank line or the initial part of the
|
||||
-- line otherwise.
|
||||
-- we don't need to have the whole line to detect indentation.
|
||||
local function get_first_line_part(tokens)
|
||||
local i, n = 1, #tokens
|
||||
while i + 1 <= n do
|
||||
local ttype, ttext = tokens[i], tokens[i + 1]
|
||||
if ttype ~= "comment" and ttext:gsub("%s+", "") ~= "" then
|
||||
return ttext
|
||||
end
|
||||
i = i + 2
|
||||
end
|
||||
return escaped
|
||||
end
|
||||
|
||||
|
||||
local function get_comment_patterns(syntax, _loop)
|
||||
_loop = _loop or 1
|
||||
if _loop > 5 then return end
|
||||
if comments_cache[syntax] then
|
||||
if #comments_cache[syntax] > 0 then
|
||||
return comments_cache[syntax]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
local comments = {}
|
||||
for idx=1, #syntax.patterns do
|
||||
local pattern = syntax.patterns[idx]
|
||||
local startp = ""
|
||||
if
|
||||
type(pattern.type) == "string"
|
||||
and
|
||||
(pattern.type == "comment" or pattern.type == "string")
|
||||
then
|
||||
local not_is_string = pattern.type ~= "string"
|
||||
if pattern.pattern then
|
||||
startp = type(pattern.pattern) == "table"
|
||||
and pattern.pattern[1] or pattern.pattern
|
||||
if not_is_string and startp:sub(1, 1) ~= "^" then
|
||||
startp = "^%s*" .. startp
|
||||
elseif not_is_string then
|
||||
startp = "^%s*" .. startp:sub(2, startp:len())
|
||||
end
|
||||
if type(pattern.pattern) == "table" then
|
||||
table.insert(comments, {"p", startp, pattern.pattern[2]})
|
||||
elseif not_is_string then
|
||||
table.insert(comments, {"p", startp})
|
||||
end
|
||||
elseif pattern.regex then
|
||||
startp = type(pattern.regex) == "table"
|
||||
and pattern.regex[1] or pattern.regex
|
||||
if not_is_string and startp:sub(1, 1) ~= "^" then
|
||||
startp = "^\\s*" .. startp
|
||||
elseif not_is_string then
|
||||
startp = "^\\s*" .. startp:sub(2, startp:len())
|
||||
end
|
||||
if type(pattern.regex) == "table" then
|
||||
table.insert(comments, {
|
||||
"r", regex.compile(startp), regex.compile(pattern.regex[2])
|
||||
})
|
||||
elseif not_is_string then
|
||||
table.insert(comments, {"r", regex.compile(startp)})
|
||||
end
|
||||
end
|
||||
elseif pattern.syntax then
|
||||
local subsyntax = type(pattern.syntax) == 'table' and pattern.syntax
|
||||
or core_syntax.get("file"..pattern.syntax, "")
|
||||
local sub_comments = get_comment_patterns(subsyntax, _loop + 1)
|
||||
if sub_comments then
|
||||
for s=1, #sub_comments do
|
||||
table.insert(comments, sub_comments[s])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if #comments == 0 then
|
||||
local single_line_comment = syntax.comment
|
||||
and escape_comment_tokens(syntax.comment) or nil
|
||||
local block_comment = nil
|
||||
if syntax.block_comment then
|
||||
block_comment = {
|
||||
escape_comment_tokens(syntax.block_comment[1]),
|
||||
escape_comment_tokens(syntax.block_comment[2])
|
||||
}
|
||||
end
|
||||
if single_line_comment then
|
||||
table.insert(comments, {"p", "^%s*" .. single_line_comment})
|
||||
end
|
||||
if block_comment then
|
||||
table.insert(comments, {"p", "^%s*" .. block_comment[1], block_comment[2]})
|
||||
end
|
||||
end
|
||||
comments_cache[syntax] = comments
|
||||
if #comments > 0 then
|
||||
return comments
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
local function get_non_empty_lines(syntax, lines)
|
||||
return coroutine.wrap(function()
|
||||
local comments = get_comment_patterns(syntax)
|
||||
|
||||
local tokens, state
|
||||
local i = 0
|
||||
local end_regex = nil
|
||||
local end_pattern = nil
|
||||
local inside_comment = false
|
||||
for _, line in ipairs(lines) do
|
||||
if line:gsub("^%s+", "") ~= "" then
|
||||
local is_comment = false
|
||||
if comments then
|
||||
if not inside_comment then
|
||||
for c=1, #comments do
|
||||
local comment = comments[c]
|
||||
if comment[1] == "p" then
|
||||
if comment[3] then
|
||||
local start, ending = line:find(comment[2])
|
||||
if start then
|
||||
if not line:find(comment[3], ending+1) then
|
||||
is_comment = true
|
||||
inside_comment = true
|
||||
end_pattern = comment[3]
|
||||
end
|
||||
break
|
||||
end
|
||||
elseif line:find(comment[2]) then
|
||||
is_comment = true
|
||||
break
|
||||
end
|
||||
else
|
||||
if comment[3] then
|
||||
local start, ending = regex.find_offsets(
|
||||
comment[2], line, 1, regex.ANCHORED
|
||||
)
|
||||
if start then
|
||||
if not regex.find_offsets(
|
||||
comment[3], line, ending+1, regex.ANCHORED
|
||||
)
|
||||
then
|
||||
is_comment = true
|
||||
inside_comment = true
|
||||
end_regex = comment[3]
|
||||
end
|
||||
break
|
||||
end
|
||||
elseif regex.find_offsets(comment[2], line, 1, regex.ANCHORED) then
|
||||
is_comment = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif end_pattern and line:find(end_pattern) then
|
||||
is_comment = true
|
||||
inside_comment = false
|
||||
end_pattern = nil
|
||||
elseif end_regex and regex.find_offsets(end_regex, line) then
|
||||
is_comment = true
|
||||
inside_comment = false
|
||||
end_regex = nil
|
||||
end
|
||||
end
|
||||
if
|
||||
not is_comment
|
||||
and
|
||||
not inside_comment
|
||||
then
|
||||
i = i + 1
|
||||
coroutine.yield(i, line)
|
||||
end
|
||||
tokens, state = tokenizer.tokenize(syntax, line, state)
|
||||
local line_start = get_first_line_part(tokens)
|
||||
if line_start then
|
||||
i = i + 1
|
||||
coroutine.yield(i, line_start)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local auto_detect_max_lines = 100
|
||||
|
||||
local function detect_indent_stat(doc)
|
||||
local stat = {}
|
||||
local tab_count = 0
|
||||
local runs = 1
|
||||
local max_lines = auto_detect_max_lines
|
||||
for i, text in get_non_empty_lines(doc.syntax, doc.lines) do
|
||||
local spaces = text:match("^ +")
|
||||
if spaces and #spaces > 1 then table.insert(stat, #spaces) end
|
||||
local tabs = text:match("^\t+")
|
||||
if tabs then tab_count = tab_count + 1 end
|
||||
-- if nothing found for first lines try at least 4 more times
|
||||
if i == max_lines and runs < 5 and #stat == 0 and tab_count == 0 then
|
||||
max_lines = max_lines + auto_detect_max_lines
|
||||
runs = runs + 1
|
||||
local str = text:match("^ %s+%S")
|
||||
if str then add_to_stat(stat, #str - 1) end
|
||||
local str = text:match("^\t+")
|
||||
if str then tab_count = tab_count + 1 end
|
||||
-- Stop parsing when files is very long. Not needed for euristic determination.
|
||||
elseif i > max_lines then break end
|
||||
if i > auto_detect_max_lines then break end
|
||||
end
|
||||
table.sort(stat, function(a, b) return a[1] < b[1] end)
|
||||
local indent, score = optimal_indent_from_stat(stat)
|
||||
if tab_count > score then
|
||||
return "hard", config.indent_size, tab_count
|
||||
|
@ -264,7 +101,7 @@ end
|
|||
|
||||
local function update_cache(doc)
|
||||
local type, size, score = detect_indent_stat(doc)
|
||||
local score_threshold = 2
|
||||
local score_threshold = 4
|
||||
if score < score_threshold then
|
||||
-- use default values
|
||||
type = config.tab_type
|
||||
|
@ -293,54 +130,55 @@ end
|
|||
|
||||
local function set_indent_type(doc, type)
|
||||
local _, indent_size = doc:get_indent_info()
|
||||
cache[doc] = {
|
||||
type = type,
|
||||
size = indent_size,
|
||||
confirmed = true
|
||||
}
|
||||
cache[doc] = {type = type,
|
||||
size = indent_size,
|
||||
confirmed = true}
|
||||
doc.indent_info = cache[doc]
|
||||
end
|
||||
|
||||
local function set_indent_type_command(dv)
|
||||
core.command_view:enter("Specify indent style for this file", {
|
||||
submit = function(value)
|
||||
local doc = dv.doc
|
||||
local function set_indent_type_command()
|
||||
core.command_view:enter(
|
||||
"Specify indent style for this file",
|
||||
function(value) -- submit
|
||||
local doc = core.active_view.doc
|
||||
value = value:lower()
|
||||
set_indent_type(doc, value == "tabs" and "hard" or "soft")
|
||||
end,
|
||||
suggest = function(text)
|
||||
function(text) -- suggest
|
||||
return common.fuzzy_match({"tabs", "spaces"}, text)
|
||||
end,
|
||||
validate = function(text)
|
||||
nil, -- cancel
|
||||
function(text) -- validate
|
||||
local t = text:lower()
|
||||
return t == "tabs" or t == "spaces"
|
||||
end
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
local function set_indent_size(doc, size)
|
||||
local indent_type = doc:get_indent_info()
|
||||
cache[doc] = {
|
||||
type = indent_type,
|
||||
size = size,
|
||||
confirmed = true
|
||||
}
|
||||
cache[doc] = {type = indent_type,
|
||||
size = size,
|
||||
confirmed = true}
|
||||
doc.indent_info = cache[doc]
|
||||
end
|
||||
|
||||
local function set_indent_size_command(dv)
|
||||
core.command_view:enter("Specify indent size for current file", {
|
||||
submit = function(value)
|
||||
value = math.floor(tonumber(value))
|
||||
local doc = dv.doc
|
||||
local function set_indent_size_command()
|
||||
core.command_view:enter(
|
||||
"Specify indent size for current file",
|
||||
function(value) -- submit
|
||||
local value = math.floor(tonumber(value))
|
||||
local doc = core.active_view.doc
|
||||
set_indent_size(doc, value)
|
||||
end,
|
||||
validate = function(value)
|
||||
value = tonumber(value)
|
||||
nil, -- suggest
|
||||
nil, -- cancel
|
||||
function(value) -- validate
|
||||
local value = tonumber(value)
|
||||
return value ~= nil and value >= 1
|
||||
end
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
@ -349,24 +187,20 @@ command.add("core.docview", {
|
|||
["indent:set-file-indent-size"] = set_indent_size_command
|
||||
})
|
||||
|
||||
command.add(
|
||||
function()
|
||||
|
||||
command.add(function()
|
||||
return core.active_view:is(DocView)
|
||||
and cache[core.active_view.doc]
|
||||
and cache[core.active_view.doc].type == "soft"
|
||||
and cache[core.active_view.doc]
|
||||
and cache[core.active_view.doc].type == "soft"
|
||||
end, {
|
||||
["indent:switch-file-to-tabs-indentation"] = function()
|
||||
set_indent_type(core.active_view.doc, "hard")
|
||||
end
|
||||
["indent:switch-file-to-tabs-indentation"] = function() set_indent_type(core.active_view.doc, "hard") end
|
||||
})
|
||||
|
||||
command.add(
|
||||
function()
|
||||
|
||||
command.add(function()
|
||||
return core.active_view:is(DocView)
|
||||
and cache[core.active_view.doc]
|
||||
and cache[core.active_view.doc].type == "hard"
|
||||
and cache[core.active_view.doc]
|
||||
and cache[core.active_view.doc].type == "hard"
|
||||
end, {
|
||||
["indent:switch-file-to-spaces-indentation"] = function()
|
||||
set_indent_type(core.active_view.doc, "soft")
|
||||
end
|
||||
["indent:switch-file-to-spaces-indentation"] = function() set_indent_type(core.active_view.doc, "soft") end
|
||||
})
|
||||
|
|
|
@ -1,321 +1,36 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local Highlighter = require "core.doc.highlighter"
|
||||
|
||||
config.plugins.drawwhitespace = common.merge({
|
||||
enabled = false,
|
||||
show_leading = true,
|
||||
show_trailing = true,
|
||||
show_middle = true,
|
||||
|
||||
show_middle_min = 1,
|
||||
|
||||
color = style.syntax.whitespace or style.syntax.comment,
|
||||
leading_color = nil,
|
||||
middle_color = nil,
|
||||
trailing_color = nil,
|
||||
|
||||
substitutions = {
|
||||
{
|
||||
char = " ",
|
||||
sub = "·",
|
||||
-- You can put any of the previous options here too.
|
||||
-- For example:
|
||||
-- show_middle_min = 2,
|
||||
-- show_leading = false,
|
||||
},
|
||||
{
|
||||
char = "\t",
|
||||
sub = "»",
|
||||
},
|
||||
},
|
||||
|
||||
config_spec = {
|
||||
name = "Draw Whitespace",
|
||||
{
|
||||
label = "Enabled",
|
||||
description = "Disable or enable the drawing of white spaces.",
|
||||
path = "enabled",
|
||||
type = "toggle",
|
||||
default = false
|
||||
},
|
||||
{
|
||||
label = "Show Leading",
|
||||
description = "Draw whitespaces starting at the beginning of a line.",
|
||||
path = "show_leading",
|
||||
type = "toggle",
|
||||
default = true,
|
||||
},
|
||||
{
|
||||
label = "Show Middle",
|
||||
description = "Draw whitespaces on the middle of a line.",
|
||||
path = "show_middle",
|
||||
type = "toggle",
|
||||
default = true,
|
||||
},
|
||||
{
|
||||
label = "Show Trailing",
|
||||
description = "Draw whitespaces on the end of a line.",
|
||||
path = "show_trailing",
|
||||
type = "toggle",
|
||||
default = true,
|
||||
},
|
||||
{
|
||||
label = "Show Trailing as Error",
|
||||
description = "Uses an error square to spot them easily, requires 'Show Trailing' enabled.",
|
||||
path = "show_trailing_error",
|
||||
type = "toggle",
|
||||
default = false,
|
||||
on_apply = function(enabled)
|
||||
local found = nil
|
||||
local substitutions = config.plugins.drawwhitespace.substitutions
|
||||
for i, sub in ipairs(substitutions) do
|
||||
if sub.trailing_error then
|
||||
found = i
|
||||
end
|
||||
end
|
||||
if found == nil and enabled then
|
||||
table.insert(substitutions, {
|
||||
char = " ",
|
||||
sub = "█",
|
||||
show_leading = false,
|
||||
show_middle = false,
|
||||
show_trailing = true,
|
||||
trailing_color = style.error,
|
||||
trailing_error = true
|
||||
})
|
||||
elseif found ~= nil and not enabled then
|
||||
table.remove(substitutions, found)
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
}, config.plugins.drawwhitespace)
|
||||
|
||||
|
||||
local ws_cache
|
||||
local cached_settings
|
||||
local function reset_cache()
|
||||
ws_cache = setmetatable({}, { __mode = "k" })
|
||||
local settings = config.plugins.drawwhitespace
|
||||
cached_settings = {
|
||||
show_leading = settings.show_leading,
|
||||
show_trailing = settings.show_trailing,
|
||||
show_middle = settings.show_middle,
|
||||
show_middle_min = settings.show_middle_min,
|
||||
color = settings.color,
|
||||
leading_color = settings.leading_color,
|
||||
middle_color = settings.middle_color,
|
||||
trailing_color = settings.trailing_color,
|
||||
substitutions = settings.substitutions,
|
||||
}
|
||||
end
|
||||
reset_cache()
|
||||
|
||||
local function reset_cache_if_needed()
|
||||
local settings = config.plugins.drawwhitespace
|
||||
if
|
||||
not ws_cache or
|
||||
cached_settings.show_leading ~= settings.show_leading
|
||||
or cached_settings.show_trailing ~= settings.show_trailing
|
||||
or cached_settings.show_middle ~= settings.show_middle
|
||||
or cached_settings.show_middle_min ~= settings.show_middle_min
|
||||
or cached_settings.color ~= settings.color
|
||||
or cached_settings.leading_color ~= settings.leading_color
|
||||
or cached_settings.middle_color ~= settings.middle_color
|
||||
or cached_settings.trailing_color ~= settings.trailing_color
|
||||
-- we assume that the entire table changes
|
||||
or cached_settings.substitutions ~= settings.substitutions
|
||||
then
|
||||
reset_cache()
|
||||
end
|
||||
end
|
||||
|
||||
-- Move cache to make space for new lines
|
||||
local prev_insert_notify = Highlighter.insert_notify
|
||||
function Highlighter:insert_notify(line, n, ...)
|
||||
prev_insert_notify(self, line, n, ...)
|
||||
if not ws_cache[self] then
|
||||
ws_cache[self] = {}
|
||||
end
|
||||
local to = math.min(line + n, #self.doc.lines)
|
||||
for i=#self.doc.lines+n,to,-1 do
|
||||
ws_cache[self][i] = ws_cache[self][i - n]
|
||||
end
|
||||
for i=line,to do
|
||||
ws_cache[self][i] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Close the cache gap created by removed lines
|
||||
local prev_remove_notify = Highlighter.remove_notify
|
||||
function Highlighter:remove_notify(line, n, ...)
|
||||
prev_remove_notify(self, line, n, ...)
|
||||
if not ws_cache[self] then
|
||||
ws_cache[self] = {}
|
||||
end
|
||||
local to = math.max(line + n, #self.doc.lines)
|
||||
for i=line,to do
|
||||
ws_cache[self][i] = ws_cache[self][i + n]
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove changed lines from the cache
|
||||
local prev_update_notify = Highlighter.update_notify
|
||||
function Highlighter:update_notify(line, n, ...)
|
||||
prev_update_notify(self, line, n, ...)
|
||||
if not ws_cache[self] then
|
||||
ws_cache[self] = {}
|
||||
end
|
||||
for i=line,line+n do
|
||||
ws_cache[self][i] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_option(substitution, option)
|
||||
if substitution[option] == nil then
|
||||
return config.plugins.drawwhitespace[option]
|
||||
end
|
||||
return substitution[option]
|
||||
end
|
||||
|
||||
local draw_line_text = DocView.draw_line_text
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
if
|
||||
not config.plugins.drawwhitespace.enabled
|
||||
or
|
||||
getmetatable(self) ~= DocView
|
||||
then
|
||||
return draw_line_text(self, idx, x, y)
|
||||
end
|
||||
|
||||
local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"])
|
||||
local font_size = font:get_size()
|
||||
local _, indent_size = self.doc:get_indent_info()
|
||||
|
||||
reset_cache_if_needed()
|
||||
if
|
||||
not ws_cache[self.doc.highlighter]
|
||||
or ws_cache[self.doc.highlighter].font ~= font
|
||||
or ws_cache[self.doc.highlighter].font_size ~= font_size
|
||||
or ws_cache[self.doc.highlighter].indent_size ~= indent_size
|
||||
then
|
||||
ws_cache[self.doc.highlighter] =
|
||||
setmetatable(
|
||||
{ font = font, font_size = font_size, indent_size = indent_size },
|
||||
{ __mode = "k" }
|
||||
)
|
||||
end
|
||||
|
||||
if not ws_cache[self.doc.highlighter][idx] then -- need to cache line
|
||||
local cache = {}
|
||||
|
||||
local tx
|
||||
local text = self.doc.lines[idx]
|
||||
|
||||
for _, substitution in pairs(config.plugins.drawwhitespace.substitutions) do
|
||||
local char = substitution.char
|
||||
local sub = substitution.sub
|
||||
local offset = 1
|
||||
|
||||
local show_leading = get_option(substitution, "show_leading")
|
||||
local show_middle = get_option(substitution, "show_middle")
|
||||
local show_trailing = get_option(substitution, "show_trailing")
|
||||
|
||||
local show_middle_min = get_option(substitution, "show_middle_min")
|
||||
|
||||
local base_color = get_option(substitution, "color")
|
||||
local leading_color = get_option(substitution, "leading_color") or base_color
|
||||
local middle_color = get_option(substitution, "middle_color") or base_color
|
||||
local trailing_color = get_option(substitution, "trailing_color") or base_color
|
||||
|
||||
local pattern = char.."+"
|
||||
while true do
|
||||
local s, e = text:find(pattern, offset)
|
||||
if not s then break end
|
||||
|
||||
tx = self:get_col_x_offset(idx, s)
|
||||
|
||||
local color = base_color
|
||||
local draw = false
|
||||
|
||||
if e >= #text - 1 then
|
||||
draw = show_trailing
|
||||
color = trailing_color
|
||||
elseif s == 1 then
|
||||
draw = show_leading
|
||||
color = leading_color
|
||||
else
|
||||
draw = show_middle and (e - s + 1 >= show_middle_min)
|
||||
color = middle_color
|
||||
end
|
||||
|
||||
if draw then
|
||||
local last_cache_idx = #cache
|
||||
-- We need to draw tabs one at a time because they might have a
|
||||
-- different size than the substituting character.
|
||||
-- This also applies to any other char if we use non-monospace fonts
|
||||
-- but we ignore this case for now.
|
||||
if char == "\t" then
|
||||
for i = s,e do
|
||||
tx = self:get_col_x_offset(idx, i)
|
||||
cache[last_cache_idx + 1] = sub
|
||||
cache[last_cache_idx + 2] = tx
|
||||
cache[last_cache_idx + 3] = font:get_width(sub)
|
||||
cache[last_cache_idx + 4] = color
|
||||
last_cache_idx = last_cache_idx + 4
|
||||
end
|
||||
else
|
||||
cache[last_cache_idx + 1] = string.rep(sub, e - s + 1)
|
||||
cache[last_cache_idx + 2] = tx
|
||||
cache[last_cache_idx + 3] = font:get_width(cache[last_cache_idx + 1])
|
||||
cache[last_cache_idx + 4] = color
|
||||
end
|
||||
end
|
||||
offset = e + 1
|
||||
end
|
||||
end
|
||||
ws_cache[self.doc.highlighter][idx] = cache
|
||||
end
|
||||
|
||||
-- draw from cache
|
||||
local x1, _, x2, _ = self:get_content_bounds()
|
||||
x1 = x1 + x
|
||||
x2 = x2 + x
|
||||
local color = style.syntax.whitespace or style.syntax.comment
|
||||
local ty = y + self:get_line_text_y_offset()
|
||||
local cache = ws_cache[self.doc.highlighter][idx]
|
||||
for i=1,#cache,4 do
|
||||
local tx = cache[i + 1] + x
|
||||
local tw = cache[i + 2]
|
||||
if tx <= x2 then
|
||||
local sub = cache[i]
|
||||
local color = cache[i + 3]
|
||||
if tx + tw >= x1 then
|
||||
tx = renderer.draw_text(font, sub, tx, ty, color)
|
||||
end
|
||||
end
|
||||
local tx
|
||||
local text, offset, s, e = self.doc.lines[idx], 1
|
||||
local x1, _, x2, _ = self:get_content_bounds()
|
||||
local _offset = self:get_x_offset_col(idx, x1)
|
||||
offset = _offset
|
||||
while true do
|
||||
s, e = text:find(" +", offset)
|
||||
if not s then break end
|
||||
tx = self:get_col_x_offset(idx, s) + x
|
||||
renderer.draw_text(font, string.rep("·", e - s + 1), tx, ty, color)
|
||||
if tx > x + x2 then break end
|
||||
offset = e + 1
|
||||
end
|
||||
|
||||
return draw_line_text(self, idx, x, y)
|
||||
offset = _offset
|
||||
while true do
|
||||
s, e = text:find("\t", offset)
|
||||
if not s then break end
|
||||
tx = self:get_col_x_offset(idx, s) + x
|
||||
renderer.draw_text(font, "»", tx, ty, color)
|
||||
if tx > x + x2 then break end
|
||||
offset = e + 1
|
||||
end
|
||||
draw_line_text(self, idx, x, y)
|
||||
end
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["draw-whitespace:toggle"] = function()
|
||||
config.plugins.drawwhitespace.enabled = not config.plugins.drawwhitespace.enabled
|
||||
end,
|
||||
|
||||
["draw-whitespace:disable"] = function()
|
||||
config.plugins.drawwhitespace.enabled = false
|
||||
end,
|
||||
|
||||
["draw-whitespace:enable"] = function()
|
||||
config.plugins.drawwhitespace.enabled = true
|
||||
end,
|
||||
})
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "C",
|
||||
files = { "%.c$" },
|
||||
files = { "%.c$", "%.h$", "%.inl$" },
|
||||
comment = "//",
|
||||
block_comment = { "/*", "*/" },
|
||||
patterns = {
|
||||
{ pattern = "//.*", type = "comment" },
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
|
@ -15,64 +14,12 @@ syntax.add {
|
|||
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
|
||||
{ pattern = "%.?%d+f?", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "##", type = "operator" },
|
||||
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
-- static declarations
|
||||
{ pattern = "static()%s+()inline",
|
||||
type = { "keyword", "normal", "keyword" }
|
||||
},
|
||||
{ pattern = "static()%s+()const",
|
||||
type = { "keyword", "normal", "keyword" }
|
||||
},
|
||||
{ pattern = "static()%s+()[%a_][%w_]*",
|
||||
type = { "keyword", "normal", "literal" }
|
||||
},
|
||||
-- match function type declarations
|
||||
{ pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*%f[%(]",
|
||||
type = { "literal", "operator", "normal", "function" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*%f[%(]",
|
||||
type = { "literal", "normal", "operator", "function" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*%f[%(]",
|
||||
type = { "literal", "normal", "function" }
|
||||
},
|
||||
-- match variable type declarations
|
||||
{ pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*",
|
||||
type = { "literal", "operator", "normal", "normal" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*",
|
||||
type = { "literal", "normal", "operator", "normal" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()[;,%[%)]",
|
||||
type = { "literal", "normal", "normal", "normal", "normal" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()=",
|
||||
type = { "literal", "normal", "normal", "normal", "operator" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()&()%s+()[%a_][%w_]*",
|
||||
type = { "literal", "operator", "normal", "normal" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()&()[%a_][%w_]*",
|
||||
type = { "literal", "normal", "operator", "normal" }
|
||||
},
|
||||
-- Uppercase constants of at least 2 chars in len
|
||||
{ pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%)%]}%?%^%%=/<>~|&;:,!]",
|
||||
type = "number"
|
||||
},
|
||||
-- Magic constants
|
||||
{ pattern = "__[%u%l]+__", type = "number" },
|
||||
-- all other functions
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
-- Macros
|
||||
{ pattern = "^%s*#%s*define%s+()[%a_][%a%d_]*",
|
||||
type = { "keyword", "symbol" }
|
||||
},
|
||||
{ pattern = "#%s*include%s()<.->", type = {"keyword", "string"} },
|
||||
{ pattern = "%f[#]#%s*[%a_][%w_]*", type = "keyword" },
|
||||
-- Everything else to make the tokenizer work properly
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
|
||||
{ pattern = "#[%a_][%w_]*", type = "keyword" },
|
||||
},
|
||||
symbols = {
|
||||
["if"] = "keyword",
|
||||
|
@ -97,8 +44,6 @@ syntax.add {
|
|||
["case"] = "keyword",
|
||||
["default"] = "keyword",
|
||||
["auto"] = "keyword",
|
||||
["struct"] = "keyword",
|
||||
["union"] = "keyword",
|
||||
["void"] = "keyword2",
|
||||
["int"] = "keyword2",
|
||||
["short"] = "keyword2",
|
||||
|
@ -115,7 +60,6 @@ syntax.add {
|
|||
["#if"] = "keyword",
|
||||
["#ifdef"] = "keyword",
|
||||
["#ifndef"] = "keyword",
|
||||
["#elif"] = "keyword",
|
||||
["#else"] = "keyword",
|
||||
["#elseif"] = "keyword",
|
||||
["#endif"] = "keyword",
|
||||
|
|
|
@ -1,109 +1,37 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
pcall(require, "plugins.language_c")
|
||||
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "C++",
|
||||
files = {
|
||||
"%.h$", "%.inl$", "%.cpp$", "%.cc$", "%.C$", "%.cxx$",
|
||||
"%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$",
|
||||
"%.ino$"
|
||||
"%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$"
|
||||
},
|
||||
comment = "//",
|
||||
block_comment = { "/*", "*/" },
|
||||
patterns = {
|
||||
{ pattern = "//.*", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "0x%x+[%x']*", type = "number" },
|
||||
{ pattern = "%d+[%d%.'eE]*f?", type = "number" },
|
||||
{ pattern = "%.?%d+[%d']*f?", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|:&]", type = "operator" },
|
||||
{ pattern = "##", type = "operator" },
|
||||
{ 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"} },
|
||||
-- static declarations
|
||||
{ pattern = "static()%s+()inline",
|
||||
type = { "keyword", "normal", "keyword" }
|
||||
},
|
||||
{ pattern = "static()%s+()const",
|
||||
type = { "keyword", "normal", "keyword" }
|
||||
},
|
||||
{ pattern = "static()%s+()[%a_][%w_]*",
|
||||
type = { "keyword", "normal", "literal" }
|
||||
},
|
||||
-- match method type declarations
|
||||
{ pattern = "[%a_][%w_]*()%s*()%**()%s*()[%a_][%w_]*()%s*()::",
|
||||
type = {
|
||||
"literal", "normal", "operator", "normal",
|
||||
"literal", "normal", "operator"
|
||||
}
|
||||
},
|
||||
-- match function type declarations
|
||||
{ pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*%f[%(]",
|
||||
type = { "literal", "operator", "normal", "function" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*%f[%(]",
|
||||
type = { "literal", "normal", "operator", "function" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*%f[%(]",
|
||||
type = { "literal", "normal", "function" }
|
||||
},
|
||||
-- match variable type declarations
|
||||
{ pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*",
|
||||
type = { "literal", "operator", "normal", "normal" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*",
|
||||
type = { "literal", "normal", "operator", "normal" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()[;,%[%)]",
|
||||
type = { "literal", "normal", "normal", "normal", "normal" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()=",
|
||||
type = { "literal", "normal", "normal", "normal", "operator" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()&()%s+()[%a_][%w_]*",
|
||||
type = { "literal", "operator", "normal", "normal" }
|
||||
},
|
||||
{ pattern = "[%a_][%w_]*()%s+()&()[%a_][%w_]*",
|
||||
type = { "literal", "normal", "operator", "normal" }
|
||||
},
|
||||
-- Match scope operator element access
|
||||
{ pattern = "[%a_][%w_]*()%s*()::",
|
||||
type = { "literal", "normal", "operator" }
|
||||
},
|
||||
-- Uppercase constants of at least 2 chars in len
|
||||
{ pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%)%]}%?%^%%=/<>~|&;:,!]",
|
||||
type = "number"
|
||||
},
|
||||
-- Magic constants
|
||||
{ pattern = "__[%u%l]+__", type = "number" },
|
||||
-- all other functions
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
-- Macros
|
||||
{ pattern = "^%s*#%s*define%s+()[%a_][%a%d_]*",
|
||||
type = { "keyword", "symbol" }
|
||||
},
|
||||
{ pattern = "#%s*include%s+()<.->",
|
||||
type = { "keyword", "string" }
|
||||
},
|
||||
{ pattern = "%f[#]#%s*[%a_][%w_]*", type = "keyword" },
|
||||
-- Everything else to make the tokenizer work properly
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ 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",
|
||||
["and"] = "keyword",
|
||||
["and_eq"] = "keyword",
|
||||
["not"] = "keyword",
|
||||
["not_eq"] = "keyword",
|
||||
["or"] = "keyword",
|
||||
["or_eq"] = "keyword",
|
||||
["xor"] = "keyword",
|
||||
["xor_eq"] = "keyword",
|
||||
["private"] = "keyword",
|
||||
["protected"] = "keyword",
|
||||
["public"] = "keyword",
|
||||
|
@ -111,12 +39,9 @@ syntax.add {
|
|||
["nullptr"] = "keyword",
|
||||
["operator"] = "keyword",
|
||||
["asm"] = "keyword",
|
||||
["bitand"] = "keyword",
|
||||
["bitor"] = "keyword",
|
||||
["catch"] = "keyword",
|
||||
["throw"] = "keyword",
|
||||
["try"] = "keyword",
|
||||
["class"] = "keyword",
|
||||
["compl"] = "keyword",
|
||||
["explicit"] = "keyword",
|
||||
["export"] = "keyword",
|
||||
|
@ -126,8 +51,8 @@ syntax.add {
|
|||
["constinit"] = "keyword",
|
||||
["const_cast"] = "keyword",
|
||||
["dynamic_cast"] = "keyword",
|
||||
["reinterpret_cast"] = "keyword",
|
||||
["static_cast"] = "keyword",
|
||||
["reinterpret_cast"] = "keyword",
|
||||
["static_cast"] = "keyword",
|
||||
["static_assert"] = "keyword",
|
||||
["template"] = "keyword",
|
||||
["this"] = "keyword",
|
||||
|
@ -138,6 +63,7 @@ syntax.add {
|
|||
["co_yield"] = "keyword",
|
||||
["decltype"] = "keyword",
|
||||
["delete"] = "keyword",
|
||||
["export"] = "keyword",
|
||||
["friend"] = "keyword",
|
||||
["typeid"] = "keyword",
|
||||
["typename"] = "keyword",
|
||||
|
@ -145,7 +71,6 @@ syntax.add {
|
|||
["override"] = "keyword",
|
||||
["virtual"] = "keyword",
|
||||
["using"] = "keyword",
|
||||
["namespace"] = "keyword",
|
||||
["new"] = "keyword",
|
||||
["noexcept"] = "keyword",
|
||||
["if"] = "keyword",
|
||||
|
@ -159,8 +84,6 @@ syntax.add {
|
|||
["continue"] = "keyword",
|
||||
["return"] = "keyword",
|
||||
["goto"] = "keyword",
|
||||
["struct"] = "keyword",
|
||||
["union"] = "keyword",
|
||||
["typedef"] = "keyword",
|
||||
["enum"] = "keyword",
|
||||
["extern"] = "keyword",
|
||||
|
@ -172,6 +95,7 @@ syntax.add {
|
|||
["case"] = "keyword",
|
||||
["default"] = "keyword",
|
||||
["auto"] = "keyword",
|
||||
["const"] = "keyword",
|
||||
["void"] = "keyword2",
|
||||
["int"] = "keyword2",
|
||||
["short"] = "keyword2",
|
||||
|
@ -181,18 +105,12 @@ syntax.add {
|
|||
["char"] = "keyword2",
|
||||
["unsigned"] = "keyword2",
|
||||
["bool"] = "keyword2",
|
||||
["true"] = "literal",
|
||||
["false"] = "literal",
|
||||
["NULL"] = "literal",
|
||||
["wchar_t"] = "keyword2",
|
||||
["char8_t"] = "keyword2",
|
||||
["char16_t"] = "keyword2",
|
||||
["char32_t"] = "keyword2",
|
||||
["true"] = "keyword2",
|
||||
["false"] = "keyword2",
|
||||
["#include"] = "keyword",
|
||||
["#if"] = "keyword",
|
||||
["#ifdef"] = "keyword",
|
||||
["#ifndef"] = "keyword",
|
||||
["#elif"] = "keyword",
|
||||
["#else"] = "keyword",
|
||||
["#elseif"] = "keyword",
|
||||
["#endif"] = "keyword",
|
||||
|
@ -200,5 +118,6 @@ syntax.add {
|
|||
["#warning"] = "keyword",
|
||||
["#error"] = "keyword",
|
||||
["#pragma"] = "keyword",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "CSS",
|
||||
files = { "%.css$" },
|
||||
block_comment = { "/*", "*/" },
|
||||
patterns = {
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
{ pattern = "//.*", type = "comment" },
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
|
|
|
@ -1,23 +1,31 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "HTML",
|
||||
files = { "%.html?$" },
|
||||
block_comment = { "<!--", "-->" },
|
||||
patterns = {
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][cC][rR][iI][pP][tT]%f[%s>].->",
|
||||
"<%s*/%s*[sS][cC][rR][iI][pP][tT]%s*>"
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*" ..
|
||||
"['\"]%a+/[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]['\"]%s*>",
|
||||
"<%s*/[sS][cC][rR][iI][pP][tT]>"
|
||||
},
|
||||
syntax = ".js",
|
||||
type = "function"
|
||||
},
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][cC][rR][iI][pP][tT]%s*>",
|
||||
"<%s*/%s*[sS][cC][rR][iI][pP][tT]>"
|
||||
},
|
||||
syntax = ".js",
|
||||
type = "function"
|
||||
},
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][tT][yY][lL][eE]%f[%s>].->",
|
||||
"<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][tT][yY][lL][eE][^>]*>",
|
||||
"<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
|
||||
},
|
||||
syntax = ".css",
|
||||
type = "function"
|
||||
|
|
|
@ -1,75 +1,23 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
-- Regex pattern explanation:
|
||||
-- This will match / and will look ahead for something that looks like a regex.
|
||||
--
|
||||
-- (?!/) Don't match empty regexes.
|
||||
--
|
||||
-- (?>...) this is using an atomic group to minimize backtracking, as that'd
|
||||
-- cause "Catastrophic Backtracking" in some cases.
|
||||
--
|
||||
-- [^\\[\/]++ will match anything that's isn't an escape, a start of character
|
||||
-- class or an end of pattern, without backtracking (the second +).
|
||||
--
|
||||
-- \\. will match anything that's escaped.
|
||||
--
|
||||
-- \[(?:[^\\\]++]|\\.)*+\] will match character classes.
|
||||
--
|
||||
-- /[gmiyuvsd]*\s*[\n,;\)\]\}\.]) will match the end of pattern delimiter, optionally
|
||||
-- followed by pattern options, and anything that can
|
||||
-- be after a pattern.
|
||||
--
|
||||
-- Demo with some unit tests (click on the Unit Tests entry): https://regex101.com/r/Vx5L5V/1
|
||||
-- Note that it has a couple of changes to make it work on that platform.
|
||||
local regex_pattern = {
|
||||
[=[\/(?=(?!\/)(?:(?>[^\\[\/]++|\\.|\[(?:[^\\\]]++|\\.)*+\])*+)++\/[gmiyuvsd]*\s*(?:[\n,;\)\]\}\.]|\/[\/*]))()]=],
|
||||
"/()[gmiyuvsd]*", "\\"
|
||||
}
|
||||
|
||||
-- For the moment let's not actually differentiate the insides of the regex,
|
||||
-- as this will need new token types...
|
||||
local inner_regex_syntax = {
|
||||
patterns = {
|
||||
{ pattern = "%(()%?[:!=><]", type = { "string", "string" } },
|
||||
{ pattern = "[.?+*%(%)|]", type = "string" },
|
||||
{ pattern = "{%d*,?%d*}", type = "string" },
|
||||
{ regex = { [=[\[()\^?]=], [=[(?:\]|(?=\n))()]=], "\\" },
|
||||
type = { "string", "string" },
|
||||
syntax = { -- Inside character class
|
||||
patterns = {
|
||||
{ pattern = "\\\\", type = "string" },
|
||||
{ pattern = "\\%]", type = "string" },
|
||||
{ pattern = "[^%]\n]", type = "string" }
|
||||
},
|
||||
symbols = {}
|
||||
}
|
||||
},
|
||||
{ regex = "\\/", type = "string" },
|
||||
{ regex = "[^/\n]", type = "string" },
|
||||
},
|
||||
symbols = {}
|
||||
}
|
||||
|
||||
syntax.add {
|
||||
name = "JavaScript",
|
||||
files = { "%.js$", "%.json$", "%.cson$", "%.mjs$", "%.cjs$" },
|
||||
files = { "%.js$", "%.json$", "%.cson$" },
|
||||
comment = "//",
|
||||
block_comment = { "/*", "*/" },
|
||||
patterns = {
|
||||
{ pattern = "//.*", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ regex = regex_pattern, syntax = inner_regex_syntax, type = {"string", "string"} },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = { "`", "`", '\\' }, type = "string" },
|
||||
-- Use (?:\/(?!\/|\*))? to avoid that a regex can start after a number, while also allowing // and /* comments
|
||||
{ regex = [[-?0[xXbBoO][\da-fA-F_]+n?()\s*()(?:\/(?!\/|\*))?]], type = {"number", "normal", "operator"} },
|
||||
{ regex = [[-?\d+[0-9.eE_n]*()\s*()(?:\/(?!\/|\*))?]], type = {"number", "normal", "operator"} },
|
||||
{ regex = [[-?\.?\d+()\s*()(?:\/(?!\/|\*))?]], type = {"number", "normal", "operator"} },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '/[^= ]', '/', '\\' },type = "string" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = { "`", "`", '\\' }, type = "string" },
|
||||
{ pattern = "0x[%da-fA-F_]+n?", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE_n]*", type = "number" },
|
||||
{ pattern = "-?%.?%d+", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
},
|
||||
symbols = {
|
||||
["async"] = "keyword",
|
||||
|
@ -93,7 +41,6 @@ syntax.add {
|
|||
["get"] = "keyword",
|
||||
["if"] = "keyword",
|
||||
["import"] = "keyword",
|
||||
["from"] = "keyword",
|
||||
["in"] = "keyword",
|
||||
["of"] = "keyword",
|
||||
["instanceof"] = "keyword",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
|
@ -6,13 +6,12 @@ syntax.add {
|
|||
files = "%.lua$",
|
||||
headers = "^#!.*[ /]lua",
|
||||
comment = "--",
|
||||
block_comment = { "--[[", "]]" },
|
||||
patterns = {
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = { "%[%[", "%]%]" }, type = "string" },
|
||||
{ pattern = { "%-%-%[%[", "%]%]"}, type = "comment" },
|
||||
{ pattern = "%-%-.*", type = "comment" },
|
||||
{ pattern = "%-%-.-\n", type = "comment" },
|
||||
{ pattern = "0x%x+%.%x*[pP][-+]?%d+", type = "number" },
|
||||
{ pattern = "0x%x+%.%x*", type = "number" },
|
||||
{ pattern = "0x%.%x+[pP][-+]?%d+", type = "number" },
|
||||
|
|
|
@ -1,268 +1,56 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
local style = require "core.style"
|
||||
local core = require "core"
|
||||
|
||||
local in_squares_match = "^%[%]"
|
||||
local in_parenthesis_match = "^%(%)"
|
||||
|
||||
|
||||
syntax.add {
|
||||
name = "Markdown",
|
||||
files = { "%.md$", "%.markdown$" },
|
||||
block_comment = { "<!--", "-->" },
|
||||
space_handling = false, -- turn off this feature to handle it our selfs
|
||||
patterns = {
|
||||
---- Place patterns that require spaces at start to optimize matching speed
|
||||
---- and apply the %s+ optimization immediately afterwards
|
||||
-- bullets
|
||||
{ pattern = "^%s*%*%s", type = "number" },
|
||||
{ pattern = "^%s*%-%s", type = "number" },
|
||||
{ pattern = "^%s*%+%s", type = "number" },
|
||||
-- numbered bullet
|
||||
{ pattern = "^%s*[0-9]+[%.%)]%s", type = "number" },
|
||||
-- blockquote
|
||||
{ pattern = "^%s*>+%s", type = "string" },
|
||||
-- alternative bold italic formats
|
||||
{ pattern = { "%s___", "___" }, type = "markdown_bold_italic" },
|
||||
{ pattern = { "%s__", "__" }, type = "markdown_bold" },
|
||||
{ pattern = { "%s_[%S]", "_" }, type = "markdown_italic" },
|
||||
-- reference links
|
||||
{
|
||||
pattern = "^%s*%[%^()["..in_squares_match.."]+()%]: ",
|
||||
type = { "function", "number", "function" }
|
||||
},
|
||||
{
|
||||
pattern = "^%s*%[%^?()["..in_squares_match.."]+()%]:%s+.*",
|
||||
type = { "function", "number", "function" }
|
||||
},
|
||||
-- optimization
|
||||
{ pattern = "%s+", type = "normal" },
|
||||
|
||||
---- HTML rules imported and adapted from language_html
|
||||
---- to not conflict with markdown rules
|
||||
-- Inline JS and CSS
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*" ..
|
||||
"['\"]%a+/[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]['\"]%s*>",
|
||||
"<%s*/[sS][cC][rR][iI][pP][tT]>"
|
||||
},
|
||||
syntax = ".js",
|
||||
type = "function"
|
||||
},
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][cC][rR][iI][pP][tT]%s*>",
|
||||
"<%s*/%s*[sS][cC][rR][iI][pP][tT]>"
|
||||
},
|
||||
syntax = ".js",
|
||||
type = "function"
|
||||
},
|
||||
{
|
||||
pattern = {
|
||||
"<%s*[sS][tT][yY][lL][eE][^>]*>",
|
||||
"<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
|
||||
},
|
||||
syntax = ".css",
|
||||
type = "function"
|
||||
},
|
||||
-- Comments
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
-- Tags
|
||||
{ pattern = "%f[^<]![%a_][%w_]*", type = "keyword2" },
|
||||
{ pattern = "%f[^<][%a_][%w_]*", type = "function" },
|
||||
{ pattern = "%f[^<]/[%a_][%w_]*", type = "function" },
|
||||
-- Attributes
|
||||
{
|
||||
pattern = "[a-z%-]+%s*()=%s*()\".-\"",
|
||||
type = { "keyword", "operator", "string" }
|
||||
},
|
||||
{
|
||||
pattern = "[a-z%-]+%s*()=%s*()'.-'",
|
||||
type = { "keyword", "operator", "string" }
|
||||
},
|
||||
{
|
||||
pattern = "[a-z%-]+%s*()=%s*()%-?%d[%d%.]*",
|
||||
type = { "keyword", "operator", "number" }
|
||||
},
|
||||
-- Entities
|
||||
{ pattern = "&#?[a-zA-Z0-9]+;", type = "keyword2" },
|
||||
|
||||
---- Markdown rules
|
||||
-- math
|
||||
{ pattern = { "%$%$", "%$%$", "\\" }, type = "string", syntax = ".tex"},
|
||||
{ regex = { "\\$", [[\$|(?=\\*\n)]], "\\" }, type = "string", syntax = ".tex"},
|
||||
-- code blocks
|
||||
{ pattern = { "```caddyfile", "```" }, type = "string", syntax = "Caddyfile" },
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
|
||||
{ pattern = { "```cpp", "```" }, type = "string", syntax = ".cpp" },
|
||||
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" },
|
||||
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
|
||||
{ pattern = { "```perl", "```" }, type = "string", syntax = ".pl" },
|
||||
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
|
||||
{ pattern = { "```javascript", "```" }, type = "string", syntax = ".js" },
|
||||
{ pattern = { "```json", "```" }, type = "string", syntax = ".js" },
|
||||
{ pattern = { "```html", "```" }, type = "string", syntax = ".html" },
|
||||
{ pattern = { "```ini", "```" }, type = "string", syntax = ".ini" },
|
||||
{ pattern = { "```xml", "```" }, type = "string", syntax = ".xml" },
|
||||
{ pattern = { "```css", "```" }, type = "string", syntax = ".css" },
|
||||
{ pattern = { "```lua", "```" }, type = "string", syntax = ".lua" },
|
||||
{ pattern = { "```bash", "```" }, type = "string", syntax = ".sh" },
|
||||
{ pattern = { "```sh", "```" }, type = "string", syntax = ".sh" },
|
||||
{ pattern = { "```java", "```" }, type = "string", syntax = ".java" },
|
||||
{ pattern = { "```c#", "```" }, type = "string", syntax = ".cs" },
|
||||
{ pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" },
|
||||
{ pattern = { "```d", "```" }, type = "string", syntax = ".d" },
|
||||
{ pattern = { "```glsl", "```" }, type = "string", syntax = ".glsl" },
|
||||
{ pattern = { "```c", "```" }, type = "string", syntax = ".c" },
|
||||
{ pattern = { "```julia", "```" }, type = "string", syntax = ".jl" },
|
||||
{ pattern = { "```rust", "```" }, type = "string", syntax = ".rs" },
|
||||
{ pattern = { "```dart", "```" }, type = "string", syntax = ".dart" },
|
||||
{ pattern = { "```julia", "```" }, type = "string", syntax = ".jl" },
|
||||
{ pattern = { "```rust", "```" }, type = "string", syntax = ".rs" },
|
||||
{ pattern = { "```dart", "```" }, type = "string", syntax = ".dart" },
|
||||
{ pattern = { "```v", "```" }, type = "string", syntax = ".v" },
|
||||
{ pattern = { "```toml", "```" }, type = "string", syntax = ".toml" },
|
||||
{ pattern = { "```yaml", "```" }, type = "string", syntax = ".yaml" },
|
||||
{ pattern = { "```nim", "```" }, type = "string", syntax = ".nim" },
|
||||
{ pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" },
|
||||
{ pattern = { "```rescript", "```" }, type = "string", syntax = ".res" },
|
||||
{ pattern = { "```moon", "```" }, type = "string", syntax = ".moon" },
|
||||
{ pattern = { "```go", "```" }, type = "string", syntax = ".go" },
|
||||
{ pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" },
|
||||
{ pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" },
|
||||
{ pattern = { "```", "```" }, type = "string" },
|
||||
{ pattern = { "``", "``" }, type = "string" },
|
||||
{ pattern = { "%f[\\`]%`[%S]", "`" }, type = "string" },
|
||||
-- lines
|
||||
{ pattern = "^%-%-%-+\n" , type = "comment" },
|
||||
{ pattern = "^%*%*%*+\n", type = "comment" },
|
||||
{ pattern = "^___+\n", type = "comment" },
|
||||
{ pattern = "^===+\n", type = "comment" },
|
||||
-- strike
|
||||
{ pattern = { "~~", "~~" }, type = "keyword2" },
|
||||
-- highlight
|
||||
{ pattern = { "==", "==" }, type = "literal" },
|
||||
-- bold and italic
|
||||
{ pattern = { "%*%*%*%S", "%*%*%*" }, type = "markdown_bold_italic" },
|
||||
{ pattern = { "%*%*%S", "%*%*" }, type = "markdown_bold" },
|
||||
-- handle edge case where asterisk can be at end of line and not close
|
||||
{
|
||||
pattern = { "%f[\\%*]%*[%S]", "%*%f[^%*]" },
|
||||
type = "markdown_italic"
|
||||
},
|
||||
-- alternative bold italic formats
|
||||
{ pattern = "^___[%s%p%w]+___" , type = "markdown_bold_italic" },
|
||||
{ pattern = "^__[%s%p%w]+__" , type = "markdown_bold" },
|
||||
{ pattern = "^_[%s%p%w]+_" , type = "markdown_italic" },
|
||||
-- heading with custom id
|
||||
{
|
||||
pattern = "^#+%s[%w%s%p]+(){()#[%w%-]+()}",
|
||||
type = { "keyword", "function", "string", "function" }
|
||||
},
|
||||
-- headings
|
||||
{ pattern = "^#+%s.+\n", type = "keyword" },
|
||||
-- superscript and subscript
|
||||
{
|
||||
pattern = "%^()%d+()%^",
|
||||
type = { "function", "number", "function" }
|
||||
},
|
||||
{
|
||||
pattern = "%~()%d+()%~",
|
||||
type = { "function", "number", "function" }
|
||||
},
|
||||
-- definitions
|
||||
{ pattern = "^:%s.+", type = "function" },
|
||||
-- emoji
|
||||
{ pattern = ":[a-zA-Z0-9_%-]+:", type = "literal" },
|
||||
-- images and link
|
||||
{
|
||||
pattern = "!?%[!?%[()["..in_squares_match.."]+()%]%(()["..in_parenthesis_match.."]+()%)%]%(()["..in_parenthesis_match.."]+()%)",
|
||||
type = { "function", "string", "function", "number", "function", "number", "function" }
|
||||
},
|
||||
{
|
||||
pattern = "!?%[!?%[?()["..in_squares_match.."]+()%]?%]%(()["..in_parenthesis_match.."]+()%)",
|
||||
type = { "function", "string", "function", "number", "function" }
|
||||
},
|
||||
-- reference links
|
||||
{
|
||||
pattern = "%[()["..in_squares_match.."]+()%] *()%[()["..in_squares_match.."]+()%]",
|
||||
type = { "function", "string", "function", "function", "number", "function" }
|
||||
},
|
||||
{
|
||||
pattern = "!?%[%^?()["..in_squares_match.."]+()%]",
|
||||
type = { "function", "number", "function" }
|
||||
},
|
||||
-- url's and email
|
||||
{
|
||||
pattern = "<[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+%.[a-zA-Z0-9-.]+>",
|
||||
type = "function"
|
||||
},
|
||||
{ pattern = "<https?://%S+>", type = "function" },
|
||||
{ pattern = { "```toml", "```" }, type = "string", syntax = ".toml" },
|
||||
{ pattern = { "```yaml", "```" }, type = "string", syntax = ".yaml" },
|
||||
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
|
||||
{ pattern = { "```nim", "```" }, type = "string", syntax = ".nim" },
|
||||
{ pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" },
|
||||
{ pattern = { "```rescript", "```" }, type = "string", syntax = ".res" },
|
||||
{ pattern = { "```moon", "```" }, type = "string", syntax = ".moon" },
|
||||
{ pattern = { "```go", "```" }, type = "string", syntax = ".go" },
|
||||
{ pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" },
|
||||
{ pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" },
|
||||
{ pattern = { "```", "```" }, type = "string" },
|
||||
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||
{ pattern = "%-%-%-+", type = "comment" },
|
||||
{ pattern = "%*%s+", type = "operator" },
|
||||
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||
{ pattern = "#.-\n", type = "keyword" },
|
||||
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||
{ pattern = "https?://%S+", type = "function" },
|
||||
-- optimize consecutive dashes used in tables
|
||||
{ pattern = "%-+", type = "normal" },
|
||||
},
|
||||
symbols = { },
|
||||
}
|
||||
|
||||
-- Adjust the color on theme changes
|
||||
core.add_thread(function()
|
||||
local custom_fonts = { bold = {font = nil, color = nil}, italic = {}, bold_italic = {} }
|
||||
local initial_color
|
||||
local last_code_font
|
||||
|
||||
local function set_font(attr)
|
||||
local attributes = {}
|
||||
if attr ~= "bold_italic" then
|
||||
attributes[attr] = true
|
||||
else
|
||||
attributes["bold"] = true
|
||||
attributes["italic"] = true
|
||||
end
|
||||
local font = style.code_font:copy(
|
||||
style.code_font:get_size(),
|
||||
attributes
|
||||
)
|
||||
custom_fonts[attr].font = font
|
||||
style.syntax_fonts["markdown_"..attr] = font
|
||||
end
|
||||
|
||||
local function set_color(attr)
|
||||
custom_fonts[attr].color = style.syntax["keyword2"]
|
||||
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
|
||||
end
|
||||
|
||||
-- Add 3 type of font styles for use on markdown files
|
||||
for attr, _ in pairs(custom_fonts) do
|
||||
-- Only set it if the font wasn't manually customized
|
||||
if not style.syntax_fonts["markdown_"..attr] then
|
||||
set_font(attr)
|
||||
end
|
||||
|
||||
-- Only set it if the color wasn't manually customized
|
||||
if not style.syntax["markdown_"..attr] then
|
||||
set_color(attr)
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
if last_code_font ~= style.code_font then
|
||||
last_code_font = style.code_font
|
||||
for attr, _ in pairs(custom_fonts) do
|
||||
-- Only set it if the font wasn't manually customized
|
||||
if style.syntax_fonts["markdown_"..attr] == custom_fonts[attr].font then
|
||||
set_font(attr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if initial_color ~= style.syntax["keyword2"] then
|
||||
initial_color = style.syntax["keyword2"]
|
||||
for attr, _ in pairs(custom_fonts) do
|
||||
-- Only set it if the color wasn't manually customized
|
||||
if style.syntax["markdown_"..attr] == custom_fonts[attr].color then
|
||||
set_color(attr)
|
||||
end
|
||||
end
|
||||
end
|
||||
coroutine.yield(1)
|
||||
end
|
||||
end)
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "Python",
|
||||
files = { "%.py$", "%.pyw$", "%.rpy$", "%.pyi$" },
|
||||
files = { "%.py$", "%.pyw$", "%.rpy$" },
|
||||
headers = "^#!.*[ /]python",
|
||||
comment = "#",
|
||||
block_comment = { '"""', '"""' },
|
||||
patterns = {
|
||||
{ pattern = "#.*", type = "comment" },
|
||||
{ pattern = { '^%s*"""', '"""' }, type = "comment" },
|
||||
{ pattern = '[uUrR]%f["]', type = "keyword" },
|
||||
{ pattern = "class%s+()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||
{ pattern = { "#", "\n" }, type = "comment" },
|
||||
{ pattern = { '[ruU]?"""', '"""'; '\\' }, type = "string" },
|
||||
{ pattern = { "[ruU]?'''", "'''", '\\' }, type = "string" },
|
||||
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "-?0[xboXBO][%da-fA-F_]+",type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE_]*", type = "number" },
|
||||
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
||||
{ pattern = "-?%.?%d+", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
|
@ -33,8 +28,6 @@ syntax.add {
|
|||
["lambda"] = "keyword",
|
||||
["try"] = "keyword",
|
||||
["def"] = "keyword",
|
||||
["async"] = "keyword",
|
||||
["await"] = "keyword",
|
||||
["from"] = "keyword",
|
||||
["nonlocal"] = "keyword",
|
||||
["while"] = "keyword",
|
||||
|
@ -47,8 +40,6 @@ syntax.add {
|
|||
["if"] = "keyword",
|
||||
["or"] = "keyword",
|
||||
["else"] = "keyword",
|
||||
["match"] = "keyword",
|
||||
["case"] = "keyword",
|
||||
["import"] = "keyword",
|
||||
["pass"] = "keyword",
|
||||
["break"] = "keyword",
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
name = "XML",
|
||||
files = { "%.xml$" },
|
||||
headers = "<%?xml",
|
||||
block_comment = { "<!--", "-->" },
|
||||
patterns = {
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },
|
||||
|
|
|
@ -1,115 +1,21 @@
|
|||
-- mod-version:3
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
config.plugins.lineguide = common.merge({
|
||||
enabled = false,
|
||||
width = 2,
|
||||
rulers = {
|
||||
-- 80,
|
||||
-- 100,
|
||||
-- 120,
|
||||
config.line_limit
|
||||
},
|
||||
-- The config specification used by gui generators
|
||||
config_spec = {
|
||||
name = "Line Guide",
|
||||
{
|
||||
label = "Enabled",
|
||||
description = "Disable or enable drawing of the line guide.",
|
||||
path = "enabled",
|
||||
type = "toggle",
|
||||
default = true
|
||||
},
|
||||
{
|
||||
label = "Width",
|
||||
description = "Width in pixels of the line guide.",
|
||||
path = "width",
|
||||
type = "number",
|
||||
default = 2,
|
||||
min = 1
|
||||
},
|
||||
{
|
||||
label = "Ruler Positions",
|
||||
description = "The different column numbers for the line guides to draw.",
|
||||
path = "rulers",
|
||||
type = "list_strings",
|
||||
default = { tostring(config.line_limit) or "80" },
|
||||
get_value = function(rulers)
|
||||
if type(rulers) == "table" then
|
||||
local new_rulers = {}
|
||||
for _, ruler in ipairs(rulers) do
|
||||
table.insert(new_rulers, tostring(ruler))
|
||||
end
|
||||
return new_rulers
|
||||
else
|
||||
return { tostring(config.line_limit) }
|
||||
end
|
||||
end,
|
||||
set_value = function(rulers)
|
||||
local new_rulers = {}
|
||||
for _, ruler in ipairs(rulers) do
|
||||
local number = tonumber(ruler)
|
||||
if number then
|
||||
table.insert(new_rulers, number)
|
||||
end
|
||||
end
|
||||
if #new_rulers == 0 then
|
||||
table.insert(new_rulers, config.line_limit)
|
||||
end
|
||||
return new_rulers
|
||||
end
|
||||
}
|
||||
}
|
||||
}, config.plugins.lineguide)
|
||||
|
||||
local function get_ruler(v)
|
||||
local result = nil
|
||||
if type(v) == 'number' then
|
||||
result = { columns = v }
|
||||
elseif type(v) == 'table' then
|
||||
result = v
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local draw_overlay = DocView.draw_overlay
|
||||
|
||||
function DocView:draw_overlay(...)
|
||||
if
|
||||
type(config.plugins.lineguide) == "table"
|
||||
and
|
||||
config.plugins.lineguide.enabled
|
||||
and
|
||||
self:is(DocView)
|
||||
then
|
||||
local line_x = self:get_line_screen_position(1)
|
||||
local character_width = self:get_font():get_width("n")
|
||||
local ruler_width = config.plugins.lineguide.width
|
||||
local ruler_color = style.guide or style.selection
|
||||
|
||||
for k,v in ipairs(config.plugins.lineguide.rulers) do
|
||||
local ruler = get_ruler(v)
|
||||
|
||||
if ruler then
|
||||
local x = line_x + (character_width * ruler.columns)
|
||||
local y = self.position.y
|
||||
local w = ruler_width
|
||||
local h = self.size.y
|
||||
|
||||
renderer.draw_rect(x, y, w, h, ruler.color or ruler_color)
|
||||
end
|
||||
end
|
||||
if not self:is(CommandView) then
|
||||
local offset = self:get_font():get_width("n") * config.line_limit
|
||||
local x = self:get_line_screen_position(1) + offset
|
||||
local y = self.position.y
|
||||
local w = math.ceil(SCALE * 1)
|
||||
local h = self.size.y
|
||||
|
||||
local color = style.guide or style.selection
|
||||
renderer.draw_rect(x, y, w, h, color)
|
||||
end
|
||||
-- everything else like the cursor above the line guides
|
||||
draw_overlay(self, ...)
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
["lineguide:toggle"] = function()
|
||||
config.plugins.lineguide.enabled = not config.plugins.lineguide.enabled
|
||||
end
|
||||
})
|
||||
|
|
|
@ -1,600 +0,0 @@
|
|||
-- mod-version:3 --priority:10
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
local style = require "core.style"
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local translate = require "core.doc.translate"
|
||||
|
||||
|
||||
config.plugins.linewrapping = common.merge({
|
||||
-- The type of wrapping to perform. Can be "letter" or "word".
|
||||
mode = "letter",
|
||||
-- If nil, uses the DocView's size, otherwise, uses this exact width. Can be a function.
|
||||
width_override = nil,
|
||||
-- Whether or not to draw a guide
|
||||
guide = true,
|
||||
-- Whether or not we should indent ourselves like the first line of a wrapped block.
|
||||
indent = true,
|
||||
-- Whether or not to enable wrapping by default when opening files.
|
||||
enable_by_default = false,
|
||||
-- Requires tokenization
|
||||
require_tokenization = false,
|
||||
-- The config specification used by gui generators
|
||||
config_spec = {
|
||||
name = "Line Wrapping",
|
||||
{
|
||||
label = "Mode",
|
||||
description = "The type of wrapping to perform.",
|
||||
path = "mode",
|
||||
type = "selection",
|
||||
default = "letter",
|
||||
values = {
|
||||
{"Letters", "letter"},
|
||||
{"Words", "word"}
|
||||
}
|
||||
},
|
||||
{
|
||||
label = "Guide",
|
||||
description = "Whether or not to draw a guide.",
|
||||
path = "guide",
|
||||
type = "toggle",
|
||||
default = true
|
||||
},
|
||||
{
|
||||
label = "Indent",
|
||||
description = "Whether or not to follow the indentation of wrapped line.",
|
||||
path = "indent",
|
||||
type = "toggle",
|
||||
default = true
|
||||
},
|
||||
{
|
||||
label = "Enable by Default",
|
||||
description = "Whether or not to enable wrapping by default when opening files.",
|
||||
path = "enable_by_default",
|
||||
type = "toggle",
|
||||
default = false
|
||||
},
|
||||
{
|
||||
label = "Require Tokenization",
|
||||
description = "Use tokenization when applying wrapping.",
|
||||
path = "require_tokenization",
|
||||
type = "toggle",
|
||||
default = false
|
||||
}
|
||||
}
|
||||
}, config.plugins.linewrapping)
|
||||
|
||||
local LineWrapping = {}
|
||||
|
||||
-- Optimzation function. The tokenizer is relatively slow (at present), and
|
||||
-- so if we don't need to run it, should be run sparingly.
|
||||
local function spew_tokens(doc, line) if line < math.huge then return math.huge, "normal", doc.lines[line] end end
|
||||
local function get_tokens(doc, line)
|
||||
if config.plugins.linewrapping.require_tokenization then
|
||||
return doc.highlighter:each_token(line)
|
||||
end
|
||||
return spew_tokens, doc, line
|
||||
end
|
||||
|
||||
-- Computes the breaks for a given line, width and mode. Returns a list of columns
|
||||
-- at which the line should be broken.
|
||||
function LineWrapping.compute_line_breaks(doc, default_font, line, width, mode)
|
||||
local xoffset, last_i, i, last_space, last_width, begin_width = 0, 1, 1, nil, 0, 0
|
||||
local splits = { 1 }
|
||||
for idx, type, text in get_tokens(doc, line) do
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
if idx == 1 or idx == math.huge and config.plugins.linewrapping.indent then
|
||||
local _, indent_end = text:find("^%s+")
|
||||
if indent_end then begin_width = font:get_width(text:sub(1, indent_end)) end
|
||||
end
|
||||
local w = font:get_width(text)
|
||||
if xoffset + w > width then
|
||||
for char in common.utf8_chars(text) do
|
||||
w = font:get_width(char)
|
||||
xoffset = xoffset + w
|
||||
if xoffset > width then
|
||||
if mode == "word" and last_space then
|
||||
table.insert(splits, last_space + 1)
|
||||
xoffset = w + begin_width + (xoffset - last_width)
|
||||
else
|
||||
table.insert(splits, i)
|
||||
xoffset = w + begin_width
|
||||
end
|
||||
last_space = nil
|
||||
elseif char == ' ' then
|
||||
last_space = i
|
||||
last_width = xoffset
|
||||
end
|
||||
i = i + #char
|
||||
end
|
||||
else
|
||||
xoffset = xoffset + w
|
||||
i = i + #text
|
||||
end
|
||||
end
|
||||
return splits, begin_width
|
||||
end
|
||||
|
||||
-- breaks are held in a single table that contains n*2 elements, where n is the amount of line breaks.
|
||||
-- each element represents line and column of the break. line_offset will check from the specified line
|
||||
-- if the first line has not changed breaks, it will stop there.
|
||||
function LineWrapping.reconstruct_breaks(docview, default_font, width, line_offset)
|
||||
if width ~= math.huge then
|
||||
local doc = docview.doc
|
||||
-- two elements per wrapped line; first maps to original line number, second to column number.
|
||||
docview.wrapped_lines = { }
|
||||
-- one element per actual line; maps to the first index of in wrapped_lines for this line
|
||||
docview.wrapped_line_to_idx = { }
|
||||
-- one element per actual line; gives the indent width for the acutal line
|
||||
docview.wrapped_line_offsets = { }
|
||||
docview.wrapped_settings = { ["width"] = width, ["font"] = default_font }
|
||||
for i = line_offset or 1, #doc.lines do
|
||||
local breaks, offset = LineWrapping.compute_line_breaks(doc, default_font, i, width, config.plugins.linewrapping.mode)
|
||||
table.insert(docview.wrapped_line_offsets, offset)
|
||||
for k, col in ipairs(breaks) do
|
||||
table.insert(docview.wrapped_lines, i)
|
||||
table.insert(docview.wrapped_lines, col)
|
||||
end
|
||||
end
|
||||
-- list of indices for wrapped_lines, that are based on original line number
|
||||
-- holds the index to the first in the wrapped_lines list.
|
||||
local last_wrap = nil
|
||||
for i = 1, #docview.wrapped_lines, 2 do
|
||||
if not last_wrap or last_wrap ~= docview.wrapped_lines[i] then
|
||||
table.insert(docview.wrapped_line_to_idx, (i + 1) / 2)
|
||||
last_wrap = docview.wrapped_lines[i]
|
||||
end
|
||||
end
|
||||
else
|
||||
docview.wrapped_lines = nil
|
||||
docview.wrapped_line_to_idx = nil
|
||||
docview.wrapped_line_offsets = nil
|
||||
docview.wrapped_settings = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- When we have an insertion or deletion, we have four sections of text.
|
||||
-- 1. The unaffected section, located prior to the cursor. This is completely ignored.
|
||||
-- 2. The beginning of the affected line prior to the insertion or deletion. Begins on column 1 of the selection.
|
||||
-- 3. The removed/pasted lines.
|
||||
-- 4. Every line after the modification, begins one line after the selection in the initial document.
|
||||
function LineWrapping.update_breaks(docview, old_line1, old_line2, net_lines)
|
||||
-- Step 1: Determine the index for the line for #2.
|
||||
local old_idx1 = docview.wrapped_line_to_idx[old_line1] or 1
|
||||
-- Step 2: Determine the index of the line for #4.
|
||||
local old_idx2 = (docview.wrapped_line_to_idx[old_line2 + 1] or ((#docview.wrapped_lines / 2) + 1)) - 1
|
||||
-- Step 3: Remove all old breaks for the old lines from the table, and all old widths from wrapped_line_offsets.
|
||||
local offset = (old_idx1 - 1) * 2 + 1
|
||||
for i = old_idx1, old_idx2 do
|
||||
table.remove(docview.wrapped_lines, offset)
|
||||
table.remove(docview.wrapped_lines, offset)
|
||||
end
|
||||
for i = old_line1, old_line2 do
|
||||
table.remove(docview.wrapped_line_offsets, old_line1)
|
||||
end
|
||||
-- Step 4: Shift the line number of wrapped_lines past #4 by the amount of inserted/deleted lines.
|
||||
if net_lines ~= 0 then
|
||||
for i = offset, #docview.wrapped_lines, 2 do
|
||||
docview.wrapped_lines[i] = docview.wrapped_lines[i] + net_lines
|
||||
end
|
||||
end
|
||||
-- Step 5: Compute the breaks and offsets for the lines for #2 and #3. Insert them into the table.
|
||||
local new_line1 = old_line1
|
||||
local new_line2 = old_line2 + net_lines
|
||||
for line = new_line1, new_line2 do
|
||||
local breaks, begin_width = LineWrapping.compute_line_breaks(docview.doc, docview.wrapped_settings.font, line, docview.wrapped_settings.width, config.plugins.linewrapping.mode)
|
||||
table.insert(docview.wrapped_line_offsets, line, begin_width)
|
||||
for i,b in ipairs(breaks) do
|
||||
table.insert(docview.wrapped_lines, offset, b)
|
||||
table.insert(docview.wrapped_lines, offset, line)
|
||||
offset = offset + 2
|
||||
end
|
||||
end
|
||||
-- Step 6: Recompute the wrapped_line_to_idx cache from #2.
|
||||
local line = old_line1
|
||||
offset = (old_idx1 - 1) * 2 + 1
|
||||
while offset < #docview.wrapped_lines do
|
||||
if docview.wrapped_lines[offset + 1] == 1 then
|
||||
docview.wrapped_line_to_idx[line] = ((offset - 1) / 2) + 1
|
||||
line = line + 1
|
||||
end
|
||||
offset = offset + 2
|
||||
end
|
||||
while line <= #docview.wrapped_line_to_idx do
|
||||
table.remove(docview.wrapped_line_to_idx)
|
||||
end
|
||||
end
|
||||
|
||||
-- Draws a guide if applicable to show where wrapping is occurring.
|
||||
function LineWrapping.draw_guide(docview)
|
||||
if config.plugins.linewrapping.guide and docview.wrapped_settings.width ~= math.huge then
|
||||
local x, y = docview:get_content_offset()
|
||||
local gw = docview:get_gutter_width()
|
||||
renderer.draw_rect(x + gw + docview.wrapped_settings.width, y, 1, core.root_view.size.y, style.selection)
|
||||
end
|
||||
end
|
||||
|
||||
function LineWrapping.update_docview_breaks(docview)
|
||||
local w = docview.v_scrollbar.expanded_size or style.expanded_scrollbar_size
|
||||
local width = (type(config.plugins.linewrapping.width_override) == "function" and config.plugins.linewrapping.width_override(docview))
|
||||
or config.plugins.linewrapping.width_override or (docview.size.x - docview:get_gutter_width() - w)
|
||||
if (not docview.wrapped_settings or docview.wrapped_settings.width == nil or width ~= docview.wrapped_settings.width) then
|
||||
docview.scroll.to.x = 0
|
||||
LineWrapping.reconstruct_breaks(docview, docview:get_font(), width)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_idx_line_col(docview, idx)
|
||||
local doc = docview.doc
|
||||
if not docview.wrapped_settings then
|
||||
if idx > #doc.lines then return #doc.lines, #doc.lines[#doc.lines] + 1 end
|
||||
return idx, 1
|
||||
end
|
||||
if idx < 1 then return 1, 1 end
|
||||
local offset = (idx - 1) * 2 + 1
|
||||
if offset > #docview.wrapped_lines then return #doc.lines, #doc.lines[#doc.lines] + 1 end
|
||||
return docview.wrapped_lines[offset], docview.wrapped_lines[offset + 1]
|
||||
end
|
||||
|
||||
local function get_idx_line_length(docview, idx)
|
||||
local doc = docview.doc
|
||||
if not docview.wrapped_settings then
|
||||
if idx > #doc.lines then return #doc.lines[#doc.lines] + 1 end
|
||||
return #doc.lines[idx]
|
||||
end
|
||||
local offset = (idx - 1) * 2 + 1
|
||||
local start = docview.wrapped_lines[offset + 1]
|
||||
if docview.wrapped_lines[offset + 2] and docview.wrapped_lines[offset + 2] == docview.wrapped_lines[offset] then
|
||||
return docview.wrapped_lines[offset + 3] - docview.wrapped_lines[offset + 1]
|
||||
else
|
||||
return #doc.lines[docview.wrapped_lines[offset]] - docview.wrapped_lines[offset + 1] + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function get_total_wrapped_lines(docview)
|
||||
if not docview.wrapped_settings then return docview.doc and #docview.doc.lines end
|
||||
return #docview.wrapped_lines / 2
|
||||
end
|
||||
|
||||
-- If line end, gives the end of an index line, rather than the first character of the next line.
|
||||
local function get_line_idx_col_count(docview, line, col, line_end, ndoc)
|
||||
local doc = docview.doc
|
||||
if not docview.wrapped_settings then return common.clamp(line, 1, #doc.lines), col, 1, 1 end
|
||||
if line > #doc.lines then return get_line_idx_col_count(docview, #doc.lines, #doc.lines[#doc.lines] + 1) end
|
||||
line = math.max(line, 1)
|
||||
local idx = docview.wrapped_line_to_idx[line] or 1
|
||||
local ncol, scol = 1, 1
|
||||
if col then
|
||||
local i = idx + 1
|
||||
while line == docview.wrapped_lines[(i - 1) * 2 + 1] and col >= docview.wrapped_lines[(i - 1) * 2 + 2] do
|
||||
local nscol = docview.wrapped_lines[(i - 1) * 2 + 2]
|
||||
if line_end and col == nscol then
|
||||
break
|
||||
end
|
||||
scol = nscol
|
||||
i = i + 1
|
||||
idx = idx + 1
|
||||
end
|
||||
ncol = (col - scol) + 1
|
||||
end
|
||||
local count = (docview.wrapped_line_to_idx[line + 1] or (get_total_wrapped_lines(docview) + 1)) - (docview.wrapped_line_to_idx[line] or get_total_wrapped_lines(docview))
|
||||
return idx, ncol, count, scol
|
||||
end
|
||||
|
||||
local function get_line_col_from_index_and_x(docview, idx, x)
|
||||
local doc = docview.doc
|
||||
local line, col = get_idx_line_col(docview, idx)
|
||||
if idx < 1 then return 1, 1 end
|
||||
local xoffset, last_i, i = (col ~= 1 and docview.wrapped_line_offsets[line] or 0), col, 1
|
||||
if x < xoffset then return line, col end
|
||||
local default_font = docview:get_font()
|
||||
for _, type, text in doc.highlighter:each_token(line) do
|
||||
local font, w = style.syntax_fonts[type] or default_font, 0
|
||||
for char in common.utf8_chars(text) do
|
||||
if i >= col then
|
||||
if xoffset >= x then
|
||||
return line, (xoffset - x > (w / 2) and last_i or i)
|
||||
end
|
||||
w = font:get_width(char)
|
||||
xoffset = xoffset + w
|
||||
end
|
||||
last_i = i
|
||||
i = i + #char
|
||||
end
|
||||
end
|
||||
return line, #doc.lines[line]
|
||||
end
|
||||
|
||||
|
||||
local open_files = setmetatable({ }, { __mode = "k" })
|
||||
|
||||
local old_doc_insert = Doc.raw_insert
|
||||
function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||
local old_lines = #self.lines
|
||||
old_doc_insert(self, line, col, text, undo_stack, time)
|
||||
if open_files[self] then
|
||||
for i,docview in ipairs(open_files[self]) do
|
||||
if docview.wrapped_settings then
|
||||
local lines = #self.lines - old_lines
|
||||
LineWrapping.update_breaks(docview, line, line, lines)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local old_doc_remove = Doc.raw_remove
|
||||
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||
local old_lines = #self.lines
|
||||
old_doc_remove(self, line1, col1, line2, col2, undo_stack, time)
|
||||
if open_files[self] then
|
||||
for i,docview in ipairs(open_files[self]) do
|
||||
if docview.wrapped_settings then
|
||||
local lines = #self.lines - old_lines
|
||||
LineWrapping.update_breaks(docview, line1, line2, lines)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local old_doc_update = DocView.update
|
||||
function DocView:update()
|
||||
old_doc_update(self)
|
||||
if self.wrapped_settings and self.size.x > 0 then
|
||||
LineWrapping.update_docview_breaks(self)
|
||||
end
|
||||
end
|
||||
|
||||
function DocView:get_scrollable_size()
|
||||
if not config.scroll_past_end then
|
||||
return self:get_line_height() * get_total_wrapped_lines(self) + style.padding.y * 2
|
||||
end
|
||||
return self:get_line_height() * (get_total_wrapped_lines(self) - 1) + self.size.y
|
||||
end
|
||||
|
||||
local old_get_h_scrollable_size = DocView.get_h_scrollable_size
|
||||
function DocView:get_h_scrollable_size(...)
|
||||
if self.wrapping_enabled then return 0 end
|
||||
return old_get_h_scrollable_size(self, ...)
|
||||
end
|
||||
|
||||
local old_new = DocView.new
|
||||
function DocView:new(doc)
|
||||
old_new(self, doc)
|
||||
if not open_files[doc] then open_files[doc] = {} end
|
||||
table.insert(open_files[doc], self)
|
||||
if config.plugins.linewrapping.enable_by_default then
|
||||
self.wrapping_enabled = true
|
||||
LineWrapping.update_docview_breaks(self)
|
||||
else
|
||||
self.wrapping_enabled = false
|
||||
end
|
||||
end
|
||||
|
||||
local old_scroll_to_line = DocView.scroll_to_line
|
||||
function DocView:scroll_to_line(...)
|
||||
if self.wrapping_enabled then LineWrapping.update_docview_breaks(self) end
|
||||
old_scroll_to_line(self, ...)
|
||||
end
|
||||
|
||||
local old_scroll_to_make_visible = DocView.scroll_to_make_visible
|
||||
function DocView:scroll_to_make_visible(line, col)
|
||||
if self.wrapping_enabled then LineWrapping.update_docview_breaks(self) end
|
||||
old_scroll_to_make_visible(self, line, col)
|
||||
if self.wrapped_settings then self.scroll.to.x = 0 end
|
||||
end
|
||||
|
||||
local old_get_visible_line_range = DocView.get_visible_line_range
|
||||
function DocView:get_visible_line_range()
|
||||
if not self.wrapped_settings then return old_get_visible_line_range(self) end
|
||||
local x, y, x2, y2 = self:get_content_bounds()
|
||||
local lh = self:get_line_height()
|
||||
local minline = get_idx_line_col(self, math.max(1, math.floor(y / lh)))
|
||||
local maxline = get_idx_line_col(self, math.min(get_total_wrapped_lines(self), math.floor(y2 / lh) + 1))
|
||||
return minline, maxline
|
||||
end
|
||||
|
||||
local old_get_x_offset_col = DocView.get_x_offset_col
|
||||
function DocView:get_x_offset_col(line, x)
|
||||
if not self.wrapped_settings then return old_get_x_offset_col(self, line, x) end
|
||||
local idx = get_line_idx_col_count(self, line)
|
||||
return get_line_col_from_index_and_x(self, idx, x)
|
||||
end
|
||||
|
||||
-- If line end is true, returns the end of the previous line, in a multi-line break.
|
||||
local old_get_col_x_offset = DocView.get_col_x_offset
|
||||
function DocView:get_col_x_offset(line, col, line_end)
|
||||
if not self.wrapped_settings then return old_get_col_x_offset(self, line, col) end
|
||||
local idx, ncol, count, scol = get_line_idx_col_count(self, line, col, line_end)
|
||||
local xoffset, i = (scol ~= 1 and self.wrapped_line_offsets[line] or 0), 1
|
||||
local default_font = self:get_font()
|
||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||
if i + #text >= scol then
|
||||
if i < scol then
|
||||
text = text:sub(scol - i + 1)
|
||||
i = scol
|
||||
end
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
for char in common.utf8_chars(text) do
|
||||
if i >= col then
|
||||
return xoffset
|
||||
end
|
||||
xoffset = xoffset + font:get_width(char)
|
||||
i = i + #char
|
||||
end
|
||||
else
|
||||
i = i + #text
|
||||
end
|
||||
end
|
||||
return xoffset
|
||||
end
|
||||
|
||||
local old_get_line_screen_position = DocView.get_line_screen_position
|
||||
function DocView:get_line_screen_position(line, col)
|
||||
if not self.wrapped_settings then return old_get_line_screen_position(self, line, col) end
|
||||
local idx, ncol, count = get_line_idx_col_count(self, line, col)
|
||||
local x, y = self:get_content_offset()
|
||||
local lh = self:get_line_height()
|
||||
local gw = self:get_gutter_width()
|
||||
return x + gw + (col and self:get_col_x_offset(line, col) or 0), y + (idx-1) * lh + style.padding.y
|
||||
end
|
||||
|
||||
local old_resolve_screen_position = DocView.resolve_screen_position
|
||||
function DocView:resolve_screen_position(x, y)
|
||||
if not self.wrapped_settings then return old_resolve_screen_position(self, x, y) end
|
||||
local ox, oy = self:get_line_screen_position(1)
|
||||
local idx = common.clamp(math.floor((y - oy) / self:get_line_height()) + 1, 1, get_total_wrapped_lines(self))
|
||||
return get_line_col_from_index_and_x(self, idx, x - ox)
|
||||
end
|
||||
|
||||
local old_draw_line_text = DocView.draw_line_text
|
||||
function DocView:draw_line_text(line, x, y)
|
||||
if not self.wrapped_settings then return old_draw_line_text(self, line, x, y) end
|
||||
local default_font = self:get_font()
|
||||
local tx, ty, begin_width = x, y + self:get_line_text_y_offset(), self.wrapped_line_offsets[line]
|
||||
local lh = self:get_line_height()
|
||||
local idx, _, count = get_line_idx_col_count(self, line)
|
||||
local total_offset = 1
|
||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||
local color = style.syntax[type]
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
local token_offset = 1
|
||||
-- Split tokens if we're at the end of the document.
|
||||
while text ~= nil and token_offset <= #text do
|
||||
local next_line, next_line_start_col = get_idx_line_col(self, idx + 1)
|
||||
if next_line ~= line then
|
||||
next_line_start_col = #self.doc.lines[line]
|
||||
end
|
||||
local max_length = next_line_start_col - total_offset
|
||||
local rendered_text = text:sub(token_offset, token_offset + max_length - 1)
|
||||
tx = renderer.draw_text(font, rendered_text, tx, ty, color)
|
||||
total_offset = total_offset + #rendered_text
|
||||
if total_offset ~= next_line_start_col or max_length == 0 then break end
|
||||
token_offset = token_offset + #rendered_text
|
||||
idx = idx + 1
|
||||
tx, ty = x + begin_width, ty + lh
|
||||
end
|
||||
end
|
||||
return lh * count
|
||||
end
|
||||
|
||||
local old_draw_line_body = DocView.draw_line_body
|
||||
function DocView:draw_line_body(line, x, y)
|
||||
if not self.wrapped_settings then return old_draw_line_body(self, line, x, y) end
|
||||
local lh = self:get_line_height()
|
||||
local idx0, _, count = get_line_idx_col_count(self, line)
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
||||
if line >= line1 and line <= line2 then
|
||||
if line1 ~= line then col1 = 1 end
|
||||
if line2 ~= line then col2 = #self.doc.lines[line] + 1 end
|
||||
if col1 ~= col2 then
|
||||
local idx1, ncol1 = get_line_idx_col_count(self, line, col1)
|
||||
local idx2, ncol2 = get_line_idx_col_count(self, line, col2)
|
||||
local start = 0
|
||||
for i = idx1, idx2 do
|
||||
local x1, x2 = x + (idx1 == i and self:get_col_x_offset(line1, col1) or 0)
|
||||
if idx2 == i then
|
||||
x2 = x + self:get_col_x_offset(line, col2)
|
||||
else
|
||||
start = start + get_idx_line_length(self, i, line)
|
||||
x2 = x + self:get_col_x_offset(line, start + 1, true)
|
||||
end
|
||||
renderer.draw_rect(x1, y + (i - idx0) * lh, x2 - x1, lh, style.selection)
|
||||
end
|
||||
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 draw_highlight ~= false and config.highlight_current_line
|
||||
and line1 == line and core.active_view == self then
|
||||
draw_highlight = (line1 == line2 and col1 == col2)
|
||||
end
|
||||
end
|
||||
if draw_highlight then
|
||||
for i=1,count do
|
||||
self:draw_line_highlight(x + self.scroll.x, y + lh * (i - 1))
|
||||
end
|
||||
end
|
||||
-- draw line's text
|
||||
return self:draw_line_text(line, x, y)
|
||||
end
|
||||
|
||||
local old_draw = DocView.draw
|
||||
function DocView:draw()
|
||||
old_draw(self)
|
||||
if self.wrapped_settings then
|
||||
LineWrapping.draw_guide(self)
|
||||
end
|
||||
end
|
||||
|
||||
local old_draw_line_gutter = DocView.draw_line_gutter
|
||||
function DocView:draw_line_gutter(line, x, y, width)
|
||||
local lh = self:get_line_height()
|
||||
local _, _, count = get_line_idx_col_count(self, line)
|
||||
return (old_draw_line_gutter(self, line, x, y, width) or lh) * count
|
||||
end
|
||||
|
||||
local old_translate_end_of_line = translate.end_of_line
|
||||
function translate.end_of_line(doc, line, col)
|
||||
if not core.active_view or core.active_view.doc ~= doc or not core.active_view.wrapped_settings then old_translate_end_of_line(doc, line, col) end
|
||||
local idx, ncol = get_line_idx_col_count(core.active_view, line, col)
|
||||
local nline, ncol2 = get_idx_line_col(core.active_view, idx + 1)
|
||||
if nline ~= line then return line, math.huge end
|
||||
return line, ncol2 - 1
|
||||
end
|
||||
|
||||
local old_translate_start_of_line = translate.start_of_line
|
||||
function translate.start_of_line(doc, line, col)
|
||||
if not core.active_view or core.active_view.doc ~= doc or not core.active_view.wrapped_settings then old_translate_start_of_line(doc, line, col) end
|
||||
local idx, ncol = get_line_idx_col_count(core.active_view, line, col)
|
||||
local nline, ncol2 = get_idx_line_col(core.active_view, idx - 1)
|
||||
if nline ~= line then return line, 1 end
|
||||
return line, ncol2 + 1
|
||||
end
|
||||
|
||||
local old_previous_line = DocView.translate.previous_line
|
||||
function DocView.translate.previous_line(doc, line, col, dv)
|
||||
if not dv.wrapped_settings then return old_previous_line(doc, line, col, dv) end
|
||||
local idx, ncol = get_line_idx_col_count(dv, line, col)
|
||||
return get_line_col_from_index_and_x(dv, idx - 1, dv:get_col_x_offset(line, col))
|
||||
end
|
||||
|
||||
local old_next_line = DocView.translate.next_line
|
||||
function DocView.translate.next_line(doc, line, col, dv)
|
||||
if not dv.wrapped_settings then return old_next_line(doc, line, col, dv) end
|
||||
local idx, ncol = get_line_idx_col_count(dv, line, col)
|
||||
return get_line_col_from_index_and_x(dv, idx + 1, dv:get_col_x_offset(line, col))
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
["line-wrapping:enable"] = function()
|
||||
if core.active_view and core.active_view.doc then
|
||||
core.active_view.wrapping_enabled = true
|
||||
LineWrapping.update_docview_breaks(core.active_view)
|
||||
end
|
||||
end,
|
||||
["line-wrapping:disable"] = function()
|
||||
if core.active_view and core.active_view.doc then
|
||||
core.active_view.wrapping_enabled = false
|
||||
LineWrapping.reconstruct_breaks(core.active_view, core.active_view:get_font(), math.huge)
|
||||
end
|
||||
end,
|
||||
["line-wrapping:toggle"] = function()
|
||||
if core.active_view and core.active_view.doc and core.active_view.wrapped_settings then
|
||||
command.perform("line-wrapping:disable")
|
||||
else
|
||||
command.perform("line-wrapping:enable")
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["f10"] = "line-wrapping:toggle",
|
||||
}
|
||||
|
||||
return LineWrapping
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:3
|
||||
-- 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:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local keymap = require "core.keymap"
|
||||
|
@ -6,16 +6,16 @@ local command = require "core.command"
|
|||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
---@class plugins.projectsearch.resultsview : core.view
|
||||
|
||||
local ResultsView = View:extend()
|
||||
|
||||
ResultsView.context = "session"
|
||||
|
||||
function ResultsView:new(path, text, fn)
|
||||
function ResultsView:new(text, fn)
|
||||
ResultsView.super.new(self)
|
||||
self.scrollable = true
|
||||
self.brightness = 0
|
||||
self:begin_search(path, text, fn)
|
||||
self:begin_search(text, fn)
|
||||
end
|
||||
|
||||
|
||||
|
@ -45,8 +45,8 @@ local function find_all_matches_in_file(t, filename, fn)
|
|||
end
|
||||
|
||||
|
||||
function ResultsView:begin_search(path, text, fn)
|
||||
self.search_args = { path, text, fn }
|
||||
function ResultsView:begin_search(text, fn)
|
||||
self.search_args = { text, fn }
|
||||
self.results = {}
|
||||
self.last_file_idx = 1
|
||||
self.query = text
|
||||
|
@ -56,9 +56,9 @@ function ResultsView:begin_search(path, text, fn)
|
|||
core.add_thread(function()
|
||||
local i = 1
|
||||
for dir_name, file in core.get_project_files() do
|
||||
if file.type == "file" and (not path or (dir_name .. "/" .. file.filename):find(path, 1, true) == 1) then
|
||||
local truncated_path = (dir_name == core.project_dir and "" or (dir_name .. PATHSEP))
|
||||
find_all_matches_in_file(self.results, truncated_path .. file.filename, fn)
|
||||
if file.type == "file" then
|
||||
local path = (dir_name == core.project_dir and "" or (dir_name .. PATHSEP))
|
||||
find_all_matches_in_file(self.results, path .. file.filename, fn)
|
||||
end
|
||||
self.last_file_idx = i
|
||||
i = i + 1
|
||||
|
@ -176,7 +176,7 @@ function ResultsView:draw()
|
|||
local text
|
||||
if self.searching then
|
||||
if files_number then
|
||||
text = string.format("Searching %.f%% (%d of %d files, %d matches) for %q...",
|
||||
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
|
||||
per * 100, self.last_file_idx, files_number,
|
||||
#self.results, self.query)
|
||||
else
|
||||
|
@ -219,122 +219,41 @@ function ResultsView:draw()
|
|||
end
|
||||
|
||||
|
||||
---@param path string
|
||||
---@param text string
|
||||
---@param fn fun(line_text:string):...
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
local function begin_search(path, text, fn)
|
||||
local function begin_search(text, fn)
|
||||
if text == "" then
|
||||
core.error("Expected non-empty string")
|
||||
return
|
||||
end
|
||||
local rv = ResultsView(path, text, fn)
|
||||
local rv = ResultsView(text, fn)
|
||||
core.root_view:get_active_node_default():add_view(rv)
|
||||
return rv
|
||||
end
|
||||
|
||||
|
||||
local function get_selected_text()
|
||||
local view = core.active_view
|
||||
local doc = (view and view.doc) and view.doc or nil
|
||||
if doc then
|
||||
return doc:get_text(table.unpack({ doc:get_selection() }))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function normalize_path(path)
|
||||
if not path then return nil end
|
||||
path = common.normalize_path(path)
|
||||
for i, project_dir in ipairs(core.project_directories) do
|
||||
if common.path_belongs_to(path, project_dir.name) then
|
||||
return project_dir.item.filename .. PATHSEP .. common.relative_path(project_dir.name, path)
|
||||
end
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
---@class plugins.projectsearch
|
||||
local projectsearch = {}
|
||||
|
||||
---@type plugins.projectsearch.resultsview
|
||||
projectsearch.ResultsView = ResultsView
|
||||
|
||||
---@param text string
|
||||
---@param path string
|
||||
---@param insensitive? boolean
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
function projectsearch.search_plain(text, path, insensitive)
|
||||
if insensitive then text = text:lower() end
|
||||
return begin_search(path, text, function(line_text)
|
||||
if insensitive then
|
||||
return line_text:lower():find(text, nil, true)
|
||||
else
|
||||
return line_text:find(text, nil, true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param path string
|
||||
---@param insensitive? boolean
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
function projectsearch.search_regex(text, path, insensitive)
|
||||
local re, errmsg
|
||||
if insensitive then
|
||||
re, errmsg = regex.compile(text, "i")
|
||||
else
|
||||
re, errmsg = regex.compile(text)
|
||||
end
|
||||
if not re then core.log("%s", errmsg) return end
|
||||
return begin_search(path, text, function(line_text)
|
||||
return regex.cmatch(re, line_text)
|
||||
end)
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param path string
|
||||
---@param insensitive? boolean
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
function projectsearch.search_fuzzy(text, path, insensitive)
|
||||
if insensitive then text = text:lower() end
|
||||
return begin_search(path, text, function(line_text)
|
||||
if insensitive then
|
||||
return common.fuzzy_match(line_text:lower(), text) and 1
|
||||
else
|
||||
return common.fuzzy_match(line_text, text) and 1
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["project-search:find"] = function(path)
|
||||
core.command_view:enter("Find Text In " .. (normalize_path(path) or "Project"), {
|
||||
text = get_selected_text(),
|
||||
select_text = true,
|
||||
submit = function(text)
|
||||
projectsearch.search_plain(text, path, true)
|
||||
end
|
||||
})
|
||||
["project-search:find"] = function()
|
||||
core.command_view:enter("Find Text In Project", function(text)
|
||||
text = text:lower()
|
||||
begin_search(text, function(line_text)
|
||||
return line_text:lower():find(text, nil, true)
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
|
||||
["project-search:find-regex"] = function(path)
|
||||
core.command_view:enter("Find Regex In " .. (normalize_path(path) or "Project"), {
|
||||
submit = function(text)
|
||||
projectsearch.search_regex(text, path, true)
|
||||
end
|
||||
})
|
||||
["project-search:find-regex"] = function()
|
||||
core.command_view:enter("Find Regex In Project", function(text)
|
||||
local re = regex.compile(text, "i")
|
||||
begin_search(text, function(line_text)
|
||||
return regex.cmatch(re, line_text)
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
|
||||
["project-search:fuzzy-find"] = function(path)
|
||||
core.command_view:enter("Fuzzy Find Text In " .. (normalize_path(path) or "Project"), {
|
||||
text = get_selected_text(),
|
||||
select_text = true,
|
||||
submit = function(text)
|
||||
projectsearch.search_fuzzy(text, path, true)
|
||||
end
|
||||
})
|
||||
["project-search:fuzzy-find"] = function()
|
||||
core.command_view:enter("Fuzzy Find Text In Project", function(text)
|
||||
begin_search(text, function(line_text)
|
||||
return common.fuzzy_match(line_text, text) and 1
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
|
@ -359,22 +278,22 @@ command.add(ResultsView, {
|
|||
["project-search:refresh"] = function()
|
||||
core.active_view:refresh()
|
||||
end,
|
||||
|
||||
|
||||
["project-search:move-to-previous-page"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view.scroll.to.y - view.size.y
|
||||
end,
|
||||
|
||||
|
||||
["project-search:move-to-next-page"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view.scroll.to.y + view.size.y
|
||||
end,
|
||||
|
||||
|
||||
["project-search:move-to-start-of-doc"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = 0
|
||||
end,
|
||||
|
||||
|
||||
["project-search:move-to-end-of-doc"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view:get_scrollable_size()
|
||||
|
@ -394,6 +313,3 @@ keymap.add {
|
|||
["home"] = "project-search:move-to-start-of-doc",
|
||||
["end"] = "project-search:move-to-end-of-doc"
|
||||
}
|
||||
|
||||
|
||||
return projectsearch
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
@ -19,8 +19,8 @@ end
|
|||
|
||||
|
||||
command.add("core.docview", {
|
||||
["quote:quote"] = function(dv)
|
||||
dv.doc:replace(function(text)
|
||||
["quote:quote"] = function()
|
||||
core.active_view.doc:replace(function(text)
|
||||
return '"' .. text:gsub("[\0-\31\\\"]", replace) .. '"'
|
||||
end)
|
||||
end,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
|
@ -25,8 +25,8 @@ end
|
|||
|
||||
|
||||
command.add("core.docview", {
|
||||
["reflow:reflow"] = function(dv)
|
||||
local doc = dv.doc
|
||||
["reflow:reflow"] = function()
|
||||
local doc = core.active_view.doc
|
||||
doc:replace(function(text)
|
||||
local prefix_set = "[^%w\n%[%](){}`'\"]*"
|
||||
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
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 RootView = require "core.rootview"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
config.plugins.scale = common.merge({
|
||||
-- The method used to apply the scaling: "code", "ui"
|
||||
config.plugins.scale = {
|
||||
mode = "code",
|
||||
-- Default scale applied at startup.
|
||||
default_scale = "autodetect",
|
||||
-- Allow using CTRL + MouseWheel for changing the scale.
|
||||
use_mousewheel = true
|
||||
}, config.plugins.scale)
|
||||
}
|
||||
|
||||
local scale_steps = 0.05
|
||||
|
||||
|
@ -25,16 +22,11 @@ local function set_scale(scale)
|
|||
scale = common.clamp(scale, 0.2, 6)
|
||||
|
||||
-- save scroll positions
|
||||
local v_scrolls = {}
|
||||
local h_scrolls = {}
|
||||
local scrolls = {}
|
||||
for _, view in ipairs(core.root_view.root_node:get_children()) do
|
||||
local n = view:get_scrollable_size()
|
||||
if n ~= math.huge and n > view.size.y then
|
||||
v_scrolls[view] = view.scroll.y / (n - view.size.y)
|
||||
end
|
||||
local hn = view:get_h_scrollable_size()
|
||||
if hn ~= math.huge and hn > view.size.x then
|
||||
h_scrolls[view] = view.scroll.x / (hn - view.size.x)
|
||||
if n ~= math.huge and not view:is(CommandView) and n > view.size.y then
|
||||
scrolls[view] = view.scroll.y / (n - view.size.y)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -44,34 +36,33 @@ local function set_scale(scale)
|
|||
if config.plugins.scale.mode == "ui" then
|
||||
SCALE = scale
|
||||
|
||||
style.padding.x = style.padding.x * s
|
||||
style.padding.y = style.padding.y * s
|
||||
style.divider_size = style.divider_size * s
|
||||
style.scrollbar_size = style.scrollbar_size * s
|
||||
style.expanded_scrollbar_size = style.expanded_scrollbar_size * s
|
||||
style.caret_width = style.caret_width * s
|
||||
style.tab_width = style.tab_width * s
|
||||
style.padding.x = style.padding.x * s
|
||||
style.padding.y = style.padding.y * s
|
||||
style.divider_size = style.divider_size * s
|
||||
style.scrollbar_size = style.scrollbar_size * s
|
||||
style.caret_width = style.caret_width * s
|
||||
style.tab_width = style.tab_width * s
|
||||
|
||||
for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do
|
||||
style[name]:set_size(s * style[name]:get_size())
|
||||
style[name] = renderer.font.copy(style[name], s * style[name]:get_size())
|
||||
end
|
||||
else
|
||||
style.code_font:set_size(s * style.code_font:get_size())
|
||||
style.code_font = renderer.font.copy(style.code_font, s * style.code_font:get_size())
|
||||
end
|
||||
|
||||
for name, font in pairs(style.syntax_fonts) do
|
||||
style.syntax_fonts[name]:set_size(s * font:get_size())
|
||||
for _, font in pairs(style.syntax_fonts) do
|
||||
renderer.font.set_size(font, s * font:get_size())
|
||||
end
|
||||
|
||||
for _, font in pairs(style.syntax_fonts) do
|
||||
renderer.font.set_size(font, s * font:get_size())
|
||||
end
|
||||
|
||||
-- restore scroll positions
|
||||
for view, n in pairs(v_scrolls) do
|
||||
for view, n in pairs(scrolls) do
|
||||
view.scroll.y = n * (view:get_scrollable_size() - view.size.y)
|
||||
view.scroll.to.y = view.scroll.y
|
||||
end
|
||||
for view, hn in pairs(h_scrolls) do
|
||||
view.scroll.x = hn * (view:get_h_scrollable_size() - view.size.x)
|
||||
view.scroll.to.x = view.scroll.x
|
||||
end
|
||||
|
||||
core.redraw = true
|
||||
end
|
||||
|
@ -92,75 +83,6 @@ local function dec_scale()
|
|||
set_scale(current_scale - scale_steps)
|
||||
end
|
||||
|
||||
if default_scale ~= config.plugins.scale.default_scale then
|
||||
if type(config.plugins.scale.default_scale) == "number" then
|
||||
set_scale(config.plugins.scale.default_scale)
|
||||
end
|
||||
end
|
||||
|
||||
-- The config specification used by gui generators
|
||||
config.plugins.scale.config_spec = {
|
||||
name = "Scale",
|
||||
{
|
||||
label = "Mode",
|
||||
description = "The method used to apply the scaling.",
|
||||
path = "mode",
|
||||
type = "selection",
|
||||
default = "code",
|
||||
values = {
|
||||
{"Everything", "ui"},
|
||||
{"Code Only", "code"}
|
||||
}
|
||||
},
|
||||
{
|
||||
label = "Default Scale",
|
||||
description = "The scaling factor applied to lite-xl.",
|
||||
path = "default_scale",
|
||||
type = "selection",
|
||||
default = "autodetect",
|
||||
values = {
|
||||
{"Autodetect", "autodetect"},
|
||||
{"80%", 0.80},
|
||||
{"90%", 0.90},
|
||||
{"100%", 1.00},
|
||||
{"110%", 1.10},
|
||||
{"120%", 1.20},
|
||||
{"125%", 1.25},
|
||||
{"130%", 1.30},
|
||||
{"140%", 1.40},
|
||||
{"150%", 1.50},
|
||||
{"175%", 1.75},
|
||||
{"200%", 2.00},
|
||||
{"250%", 2.50},
|
||||
{"300%", 3.00}
|
||||
},
|
||||
on_apply = function(value)
|
||||
if type(value) == "string" then value = default_scale end
|
||||
if value ~= current_scale then
|
||||
set_scale(value)
|
||||
end
|
||||
end
|
||||
},
|
||||
{
|
||||
label = "Use MouseWheel",
|
||||
description = "Allow using CTRL + MouseWheel for changing the scale.",
|
||||
path = "use_mousewheel",
|
||||
type = "toggle",
|
||||
default = true,
|
||||
on_apply = function(enabled)
|
||||
if enabled then
|
||||
keymap.add {
|
||||
["ctrl+wheelup"] = "scale:increase",
|
||||
["ctrl+wheeldown"] = "scale:decrease"
|
||||
}
|
||||
else
|
||||
keymap.unbind("ctrl+wheelup", "scale:increase")
|
||||
keymap.unbind("ctrl+wheeldown", "scale:decrease")
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["scale:reset" ] = function() res_scale() end,
|
||||
|
@ -171,16 +93,11 @@ command.add(nil, {
|
|||
keymap.add {
|
||||
["ctrl+0"] = "scale:reset",
|
||||
["ctrl+-"] = "scale:decrease",
|
||||
["ctrl+="] = "scale:increase"
|
||||
["ctrl+="] = "scale:increase",
|
||||
["ctrl+wheelup"] = "scale:increase",
|
||||
["ctrl+wheeldown"] = "scale:decrease"
|
||||
}
|
||||
|
||||
if config.plugins.scale.use_mousewheel then
|
||||
keymap.add {
|
||||
["ctrl+wheelup"] = "scale:increase",
|
||||
["ctrl+wheeldown"] = "scale:decrease"
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
["set"] = set_scale,
|
||||
["get"] = get_scale,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local translate = require "core.doc.translate"
|
||||
|
@ -41,23 +41,21 @@ end
|
|||
|
||||
|
||||
command.add("core.docview", {
|
||||
["tabularize:tabularize"] = function(dv)
|
||||
core.command_view:enter("Tabularize On Delimiter", {
|
||||
submit = function(delim)
|
||||
if delim == "" then delim = " " end
|
||||
["tabularize:tabularize"] = function()
|
||||
core.command_view:enter("Tabularize On Delimiter", function(delim)
|
||||
if delim == "" then delim = " " end
|
||||
|
||||
local doc = dv.doc
|
||||
local line1, col1, line2, col2, swap = doc:get_selection(true)
|
||||
line1, col1 = doc:position_offset(line1, col1, translate.start_of_line)
|
||||
line2, col2 = doc:position_offset(line2, col2, translate.end_of_line)
|
||||
doc:set_selection(line1, col1, line2, col2, swap)
|
||||
local doc = core.active_view.doc
|
||||
local line1, col1, line2, col2, swap = doc:get_selection(true)
|
||||
line1, col1 = doc:position_offset(line1, col1, translate.start_of_line)
|
||||
line2, col2 = doc:position_offset(line2, col2, translate.end_of_line)
|
||||
doc:set_selection(line1, col1, line2, col2, swap)
|
||||
|
||||
doc:replace(function(text)
|
||||
local lines = gmatch_to_array(text, "[^\n]*\n?")
|
||||
tabularize_lines(lines, delim)
|
||||
return table.concat(lines)
|
||||
end)
|
||||
end
|
||||
})
|
||||
doc:replace(function(text)
|
||||
local lines = gmatch_to_array(text, "[^\n]*\n?")
|
||||
tabularize_lines(lines, delim)
|
||||
return table.concat(lines)
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
|
@ -7,26 +7,31 @@ local View = require "core.view"
|
|||
|
||||
local ToolbarView = View:extend()
|
||||
|
||||
local toolbar_commands = {
|
||||
{symbol = "f", command = "core:new-doc"},
|
||||
{symbol = "D", command = "core:open-file"},
|
||||
{symbol = "S", command = "doc:save"},
|
||||
{symbol = "L", command = "core:find-file"},
|
||||
{symbol = "B", command = "core:find-command"},
|
||||
{symbol = "P", command = "core:open-user-module"},
|
||||
}
|
||||
|
||||
|
||||
local function toolbar_height()
|
||||
return style.icon_big_font:get_height() + style.padding.y * 2
|
||||
end
|
||||
|
||||
|
||||
function ToolbarView:new()
|
||||
ToolbarView.super.new(self)
|
||||
self.visible = true
|
||||
self.init_size = true
|
||||
self.tooltip = false
|
||||
self.toolbar_font = style.icon_big_font
|
||||
self.toolbar_commands = {
|
||||
{symbol = "f", command = "core:new-doc"},
|
||||
{symbol = "D", command = "core:open-file"},
|
||||
{symbol = "S", command = "doc:save"},
|
||||
{symbol = "L", command = "core:find-file"},
|
||||
{symbol = "B", command = "core:find-command"},
|
||||
{symbol = "P", command = "core:open-user-module"},
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
function ToolbarView:update()
|
||||
local dest_size = self.visible and (self.toolbar_font:get_height() + style.padding.y * 2) or 0
|
||||
local dest_size = self.visible and toolbar_height() or 0
|
||||
if self.init_size then
|
||||
self.size.y = dest_size
|
||||
self.init_size = nil
|
||||
|
@ -39,31 +44,21 @@ end
|
|||
|
||||
function ToolbarView:toggle_visible()
|
||||
self.visible = not self.visible
|
||||
if self.tooltip then
|
||||
core.status_view:remove_tooltip()
|
||||
self.tooltip = false
|
||||
end
|
||||
self.hovered_item = nil
|
||||
end
|
||||
|
||||
function ToolbarView:get_icon_width()
|
||||
local max_width = 0
|
||||
for i,v in ipairs(self.toolbar_commands) do max_width = math.max(max_width, self.toolbar_font:get_width(v.symbol)) end
|
||||
return max_width
|
||||
end
|
||||
|
||||
function ToolbarView:each_item()
|
||||
local icon_h, icon_w = self.toolbar_font:get_height(), self:get_icon_width()
|
||||
local icon_h, icon_w = style.icon_big_font:get_height(), style.icon_big_font:get_width("D")
|
||||
local toolbar_spacing = icon_w / 2
|
||||
local ox, oy = self:get_content_offset()
|
||||
local index = 0
|
||||
local iter = function()
|
||||
index = index + 1
|
||||
if index <= #self.toolbar_commands then
|
||||
if index <= #toolbar_commands then
|
||||
local dx = style.padding.x + (icon_w + toolbar_spacing) * (index - 1)
|
||||
local dy = style.padding.y
|
||||
if dx + icon_w > self.size.x then return end
|
||||
return self.toolbar_commands[index], ox + dx, oy + dy, icon_w, icon_h
|
||||
return toolbar_commands[index], ox + dx, oy + dy, icon_w, icon_h
|
||||
end
|
||||
end
|
||||
return iter
|
||||
|
@ -71,37 +66,33 @@ end
|
|||
|
||||
|
||||
function ToolbarView:get_min_width()
|
||||
local icon_w = self:get_icon_width()
|
||||
local icon_w = style.icon_big_font:get_width("D")
|
||||
local space = icon_w / 2
|
||||
return 2 * style.padding.x + (icon_w + space) * #self.toolbar_commands - space
|
||||
return 2 * style.padding.x + (icon_w + space) * #toolbar_commands - space
|
||||
end
|
||||
|
||||
|
||||
function ToolbarView:draw()
|
||||
if not self.visible then return end
|
||||
self:draw_background(style.background2)
|
||||
|
||||
for item, x, y, w, h in self:each_item() do
|
||||
local color = item == self.hovered_item and command.is_valid(item.command) and style.text or style.dim
|
||||
common.draw_text(self.toolbar_font, color, item.symbol, nil, x, y, 0, h)
|
||||
local color = item == self.hovered_item and style.text or style.dim
|
||||
common.draw_text(style.icon_big_font, color, item.symbol, nil, x, y, 0, h)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function ToolbarView:on_mouse_pressed(button, x, y, clicks)
|
||||
if not self.visible then return end
|
||||
local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||
if caught then return caught end
|
||||
if caught then return end
|
||||
core.set_active_view(core.last_active_view)
|
||||
if self.hovered_item and command.is_valid(self.hovered_item.command) then
|
||||
if self.hovered_item then
|
||||
command.perform(self.hovered_item.command)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function ToolbarView:on_mouse_moved(px, py, ...)
|
||||
if not self.visible then return end
|
||||
ToolbarView.super.on_mouse_moved(self, px, py, ...)
|
||||
self.hovered_item = nil
|
||||
local x_min, x_max, y_min, y_max = self.size.x, 0, self.size.y, 0
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
|
@ -8,13 +8,9 @@ local style = require "core.style"
|
|||
local View = require "core.view"
|
||||
local ContextMenu = require "core.contextmenu"
|
||||
local RootView = require "core.rootview"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
config.plugins.treeview = common.merge({
|
||||
-- Default treeview width
|
||||
size = 200 * SCALE
|
||||
}, config.plugins.treeview)
|
||||
|
||||
local default_treeview_size = 200 * SCALE
|
||||
local tooltip_offset = style.font:get_height()
|
||||
local tooltip_border = 1
|
||||
local tooltip_delay = 0.5
|
||||
|
@ -24,7 +20,7 @@ local tooltip_alpha_rate = 1
|
|||
|
||||
local function get_depth(filename)
|
||||
local n = 1
|
||||
for _ in filename:gmatch(PATHSEP) do
|
||||
for sep in filename:gmatch("[\\/]") do
|
||||
n = n + 1
|
||||
end
|
||||
return n
|
||||
|
@ -43,13 +39,20 @@ function TreeView:new()
|
|||
self.scrollable = true
|
||||
self.visible = true
|
||||
self.init_size = true
|
||||
self.target_size = config.plugins.treeview.size
|
||||
self.target_size = default_treeview_size
|
||||
self.cache = {}
|
||||
self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 }
|
||||
self.cursor_pos = { x = 0, y = 0 }
|
||||
|
||||
self.item_icon_width = 0
|
||||
self.item_text_spacing = 0
|
||||
|
||||
local on_dirmonitor_modify = core.on_dirmonitor_modify
|
||||
function core.on_dirmonitor_modify(dir, filepath)
|
||||
if self.cache[dir.name] then
|
||||
self.cache[dir.name][filepath] = nil
|
||||
end
|
||||
on_dirmonitor_modify(dir, filepath)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -72,7 +75,7 @@ function TreeView:get_cached(dir, item, dirname)
|
|||
-- used only to identify the entry into the cache.
|
||||
local cache_name = item.filename .. (item.topdir and ":" or "")
|
||||
local t = dir_cache[cache_name]
|
||||
if not t or t.type ~= item.type then
|
||||
if not t then
|
||||
t = {}
|
||||
local basename = common.basename(item.filename)
|
||||
if item.topdir then
|
||||
|
@ -83,11 +86,11 @@ function TreeView:get_cached(dir, item, dirname)
|
|||
else
|
||||
t.filename = item.filename
|
||||
t.depth = get_depth(item.filename)
|
||||
t.abs_filename = common.basepath(dirname) .. item.filename
|
||||
t.abs_filename = dirname .. PATHSEP .. item.filename
|
||||
end
|
||||
t.name = basename
|
||||
t.type = item.type
|
||||
t.dir_name = dir.name -- points to top level "dir" item
|
||||
t.dir = dir -- points to top level "dir" item
|
||||
dir_cache[cache_name] = t
|
||||
end
|
||||
return t
|
||||
|
@ -95,7 +98,7 @@ end
|
|||
|
||||
|
||||
function TreeView:get_name()
|
||||
return nil
|
||||
return "Project"
|
||||
end
|
||||
|
||||
|
||||
|
@ -139,51 +142,34 @@ function TreeView:each_item()
|
|||
count_lines = count_lines + 1
|
||||
y = y + h
|
||||
local i = 1
|
||||
if dir.files then -- if consumed max sys file descriptors this can be nil
|
||||
while i <= #dir.files and dir_cached.expanded do
|
||||
local item = dir.files[i]
|
||||
local cached = self:get_cached(dir, item, dir.name)
|
||||
while i <= #dir.files and dir_cached.expanded do
|
||||
local item = dir.files[i]
|
||||
local cached = self:get_cached(dir, item, dir.name)
|
||||
|
||||
coroutine.yield(cached, ox, y, w, h)
|
||||
count_lines = count_lines + 1
|
||||
y = y + h
|
||||
i = i + 1
|
||||
coroutine.yield(cached, ox, y, w, h)
|
||||
count_lines = count_lines + 1
|
||||
y = y + h
|
||||
i = i + 1
|
||||
|
||||
if not cached.expanded then
|
||||
if cached.skip then
|
||||
i = cached.skip
|
||||
else
|
||||
local depth = cached.depth
|
||||
while i <= #dir.files do
|
||||
if get_depth(dir.files[i].filename) <= depth then break end
|
||||
i = i + 1
|
||||
end
|
||||
cached.skip = i
|
||||
if not cached.expanded then
|
||||
if cached.skip then
|
||||
i = cached.skip
|
||||
else
|
||||
local depth = cached.depth
|
||||
while i <= #dir.files do
|
||||
if get_depth(dir.files[i].filename) <= depth then break end
|
||||
i = i + 1
|
||||
end
|
||||
cached.skip = i
|
||||
end
|
||||
end -- while files
|
||||
end
|
||||
end
|
||||
end -- while files
|
||||
end -- for directories
|
||||
self.count_lines = count_lines
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function TreeView:set_selection(selection, selection_y)
|
||||
self.selected_item = selection
|
||||
if selection and selection_y
|
||||
and (selection_y <= 0 or selection_y >= self.size.y) then
|
||||
|
||||
local lh = self:get_item_height()
|
||||
if selection_y >= self.size.y - lh then
|
||||
selection_y = selection_y - self.size.y + lh
|
||||
end
|
||||
local _, y = self:get_content_offset()
|
||||
self.scroll.to.y = selection and (selection_y - y)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TreeView:get_text_bounding_box(item, x, y, w, h)
|
||||
local icon_width = style.icon_font:get_width("D")
|
||||
local xoffset = item.depth * style.padding.x + style.padding.x + icon_width
|
||||
|
@ -194,14 +180,8 @@ end
|
|||
|
||||
|
||||
function TreeView:on_mouse_moved(px, py, ...)
|
||||
if not self.visible then return end
|
||||
self.cursor_pos.x = px
|
||||
self.cursor_pos.y = py
|
||||
if TreeView.super.on_mouse_moved(self, px, py, ...) then
|
||||
-- mouse movement handled by the View (scrollbar)
|
||||
self.hovered_item = nil
|
||||
return
|
||||
end
|
||||
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
|
||||
|
@ -223,6 +203,50 @@ function TreeView:on_mouse_moved(px, py, ...)
|
|||
end
|
||||
|
||||
|
||||
local function create_directory_in(item)
|
||||
local path = item.abs_filename
|
||||
core.command_view:enter("Create directory in " .. path, function(text)
|
||||
local dirname = path .. PATHSEP .. text
|
||||
local success, err = system.mkdir(dirname)
|
||||
if not success then
|
||||
core.error("cannot create directory %q: %s", dirname, err)
|
||||
end
|
||||
item.expanded = true
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function TreeView:on_mouse_pressed(button, x, y, clicks)
|
||||
local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||
if caught or button ~= "left" then
|
||||
return true
|
||||
end
|
||||
local hovered_item = self.hovered_item
|
||||
if not hovered_item then
|
||||
return false
|
||||
elseif hovered_item.type == "dir" then
|
||||
if keymap.modkeys["ctrl"] and button == "left" then
|
||||
create_directory_in(hovered_item)
|
||||
else
|
||||
hovered_item.expanded = not hovered_item.expanded
|
||||
if hovered_item.dir.files_limit then
|
||||
core.update_project_subdir(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
|
||||
core.project_subdir_set_show(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
|
||||
end
|
||||
end
|
||||
else
|
||||
core.try(function()
|
||||
if core.last_active_view and core.active_view == self then
|
||||
core.set_active_view(core.last_active_view)
|
||||
end
|
||||
local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename)
|
||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||
end)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function TreeView:update()
|
||||
-- update width
|
||||
local dest = self.visible and self.target_size or 0
|
||||
|
@ -230,14 +254,12 @@ function TreeView:update()
|
|||
self.size.x = dest
|
||||
self.init_size = false
|
||||
else
|
||||
self:move_towards(self.size, "x", dest, nil, "treeview")
|
||||
self:move_towards(self.size, "x", dest)
|
||||
end
|
||||
|
||||
if not self.visible then return 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, "treeview")
|
||||
self:move_towards(self.tooltip, "alpha", tooltip_alpha, tooltip_alpha_rate)
|
||||
else
|
||||
self.tooltip.alpha = 0
|
||||
end
|
||||
|
@ -245,13 +267,6 @@ function TreeView:update()
|
|||
self.item_icon_width = style.icon_font:get_width("D")
|
||||
self.item_text_spacing = style.icon_font:get_width("f") / 2
|
||||
|
||||
-- this will make sure hovered_item is updated
|
||||
-- we don't want events when the thing is scrolling fast
|
||||
local dy = math.abs(self.scroll.to.y - self.scroll.y)
|
||||
if self.scroll.to.y ~= 0 and dy < self:get_item_height() then
|
||||
self:on_mouse_moved(self.cursor_pos.x, self.cursor_pos.y, 0, 0)
|
||||
end
|
||||
|
||||
TreeView.super.update(self)
|
||||
end
|
||||
|
||||
|
@ -335,10 +350,6 @@ end
|
|||
|
||||
function TreeView:draw_item_background(item, active, hovered, x, y, w, h)
|
||||
if hovered then
|
||||
local hover_color = { table.unpack(style.line_highlight) }
|
||||
hover_color[4] = 160
|
||||
renderer.draw_rect(x, y, w, h, hover_color)
|
||||
elseif active then
|
||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||
end
|
||||
end
|
||||
|
@ -355,7 +366,6 @@ end
|
|||
|
||||
|
||||
function TreeView:draw()
|
||||
if not self.visible then return end
|
||||
self:draw_background(style.background2)
|
||||
local _y, _h = self.position.y, self.size.y
|
||||
|
||||
|
@ -365,86 +375,23 @@ function TreeView:draw()
|
|||
for item, x,y,w,h in self:each_item() do
|
||||
if y + h >= _y and y < _y + _h then
|
||||
self:draw_item(item,
|
||||
item == self.selected_item,
|
||||
item.abs_filename == active_filename,
|
||||
item == self.hovered_item,
|
||||
x, y, w, h)
|
||||
end
|
||||
end
|
||||
|
||||
self:draw_scrollbar()
|
||||
if self.hovered_item and self.tooltip.x and self.tooltip.alpha > 0 then
|
||||
if self.hovered_item and self.tooltip.alpha > 0 then
|
||||
core.root_view:defer_draw(self.draw_tooltip, self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TreeView:get_parent(item)
|
||||
local parent_path = common.dirname(item.abs_filename)
|
||||
if not parent_path then return end
|
||||
for it, _, y in self:each_item() do
|
||||
if it.abs_filename == parent_path then
|
||||
return it, y
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TreeView:get_item(item, where)
|
||||
local last_item, last_x, last_y, last_w, last_h
|
||||
local stop = false
|
||||
|
||||
for it, x, y, w, h in self:each_item() do
|
||||
if not item and where >= 0 then
|
||||
return it, x, y, w, h
|
||||
end
|
||||
if item == it then
|
||||
if where < 0 and last_item then
|
||||
break
|
||||
elseif where == 0 or (where < 0 and not last_item) then
|
||||
return it, x, y, w, h
|
||||
end
|
||||
stop = true
|
||||
elseif stop then
|
||||
item = it
|
||||
return it, x, y, w, h
|
||||
end
|
||||
last_item, last_x, last_y, last_w, last_h = it, x, y, w, h
|
||||
end
|
||||
return last_item, last_x, last_y, last_w, last_h
|
||||
end
|
||||
|
||||
function TreeView:get_next(item)
|
||||
return self:get_item(item, 1)
|
||||
end
|
||||
|
||||
function TreeView:get_previous(item)
|
||||
return self:get_item(item, -1)
|
||||
end
|
||||
|
||||
|
||||
function TreeView:toggle_expand(toggle)
|
||||
local item = self.selected_item
|
||||
|
||||
if not item then return end
|
||||
|
||||
if item.type == "dir" then
|
||||
if type(toggle) == "boolean" then
|
||||
item.expanded = toggle
|
||||
else
|
||||
item.expanded = not item.expanded
|
||||
end
|
||||
local hovered_dir = core.project_dir_by_name(item.dir_name)
|
||||
if hovered_dir and hovered_dir.files_limit then
|
||||
core.update_project_subdir(hovered_dir, item.depth == 0 and "" or item.filename, item.expanded)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- init
|
||||
local view = TreeView()
|
||||
local node = core.root_view:get_active_node()
|
||||
view.node = node:split("left", view, {x = true}, true)
|
||||
local treeview_node = node:split("left", view, {x = true}, true)
|
||||
|
||||
-- The toolbarview plugin is special because it is plugged inside
|
||||
-- a treeview pane which is itelf provided in a plugin.
|
||||
|
@ -453,12 +400,12 @@ view.node = node:split("left", view, {x = true}, true)
|
|||
-- 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 = pcall(require, "plugins.toolbarview")
|
||||
local toolbar_plugin, ToolbarView = core.try(require, "plugins.toolbarview")
|
||||
if config.plugins.toolbarview ~= false and toolbar_plugin then
|
||||
toolbar_view = ToolbarView()
|
||||
view.node:split("down", toolbar_view, {y = true})
|
||||
treeview_node:split("down", toolbar_view, {y = true})
|
||||
local min_toolbar_width = toolbar_view:get_min_width()
|
||||
view:set_target_size("x", math.max(config.plugins.treeview.size, min_toolbar_width))
|
||||
view:set_target_size("x", math.max(default_treeview_size, min_toolbar_width))
|
||||
command.add(nil, {
|
||||
["toolbar:toggle"] = function()
|
||||
toolbar_view:toggle_visible()
|
||||
|
@ -499,38 +446,19 @@ function RootView:draw(...)
|
|||
menu:draw()
|
||||
end
|
||||
|
||||
local on_quit_project = core.on_quit_project
|
||||
function core.on_quit_project()
|
||||
view.cache = {}
|
||||
on_quit_project()
|
||||
end
|
||||
|
||||
local function is_project_folder(path)
|
||||
for _,dir in pairs(core.project_directories) do
|
||||
if dir.name == path then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function is_primary_project_folder(path)
|
||||
return core.project_dir == path
|
||||
end
|
||||
|
||||
|
||||
local function treeitem() return view.hovered_item or view.selected_item end
|
||||
|
||||
|
||||
menu:register(function() return core.active_view:is(TreeView) and treeitem() end, {
|
||||
menu:register(function() return view.hovered_item end, {
|
||||
{ text = "Open in System", command = "treeview:open-in-system" },
|
||||
ContextMenu.DIVIDER
|
||||
})
|
||||
|
||||
menu:register(
|
||||
function()
|
||||
local item = treeitem()
|
||||
return core.active_view:is(TreeView) and item and not is_project_folder(item.abs_filename)
|
||||
return view.hovered_item
|
||||
and not is_project_folder(view.hovered_item.abs_filename)
|
||||
end,
|
||||
{
|
||||
{ text = "Rename", command = "treeview:rename" },
|
||||
|
@ -540,8 +468,7 @@ menu:register(
|
|||
|
||||
menu:register(
|
||||
function()
|
||||
local item = treeitem()
|
||||
return core.active_view:is(TreeView) and item and item.type == "dir"
|
||||
return view.hovered_item and view.hovered_item.type == "dir"
|
||||
end,
|
||||
{
|
||||
{ text = "New File", command = "treeview:new-file" },
|
||||
|
@ -549,144 +476,71 @@ menu:register(
|
|||
}
|
||||
)
|
||||
|
||||
menu:register(
|
||||
function()
|
||||
local item = treeitem()
|
||||
return core.active_view:is(TreeView) and item
|
||||
and not is_primary_project_folder(item.abs_filename)
|
||||
and is_project_folder(item.abs_filename)
|
||||
end,
|
||||
{
|
||||
{ text = "Remove directory", command = "treeview:remove-project-directory" },
|
||||
}
|
||||
)
|
||||
|
||||
local previous_view = nil
|
||||
|
||||
-- Register the TreeView commands and keymap
|
||||
command.add(nil, {
|
||||
["treeview:toggle"] = function()
|
||||
view.visible = not view.visible
|
||||
end,
|
||||
end})
|
||||
|
||||
["treeview:toggle-focus"] = function()
|
||||
if not core.active_view:is(TreeView) then
|
||||
if core.active_view:is(CommandView) then
|
||||
previous_view = core.last_active_view
|
||||
|
||||
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
|
||||
doc:reset_syntax()
|
||||
break -- only first needed
|
||||
end
|
||||
end
|
||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||
else
|
||||
previous_view = core.active_view
|
||||
end
|
||||
if not previous_view then
|
||||
previous_view = core.root_view:get_primary_node().active_view
|
||||
end
|
||||
core.set_active_view(view)
|
||||
if not view.selected_item then
|
||||
for it, _, y in view:each_item() do
|
||||
view:set_selection(it, y)
|
||||
break
|
||||
end
|
||||
core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
|
||||
end
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
else
|
||||
core.set_active_view(
|
||||
previous_view or core.root_view:get_primary_node().active_view
|
||||
)
|
||||
["treeview:new-file"] = function()
|
||||
if not is_project_folder(view.hovered_item.abs_filename) then
|
||||
core.command_view:set_text(view.hovered_item.filename .. "/")
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
command.add(
|
||||
function()
|
||||
return not menu.show_context_menu and core.active_view:extends(TreeView), TreeView
|
||||
end, {
|
||||
["treeview:next"] = function()
|
||||
local item, _, item_y = view:get_next(view.selected_item)
|
||||
view:set_selection(item, item_y)
|
||||
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:previous"] = function()
|
||||
local item, _, item_y = view:get_previous(view.selected_item)
|
||||
view:set_selection(item, item_y)
|
||||
end,
|
||||
|
||||
["treeview:open"] = function()
|
||||
local item = view.selected_item
|
||||
if not item then return end
|
||||
if item.type == "dir" then
|
||||
view:toggle_expand()
|
||||
else
|
||||
core.try(function()
|
||||
if core.last_active_view and core.active_view == view then
|
||||
core.set_active_view(core.last_active_view)
|
||||
end
|
||||
local doc_filename = core.normalize_to_project_dir(item.abs_filename)
|
||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||
end)
|
||||
["treeview:new-folder"] = function()
|
||||
if not is_project_folder(view.hovered_item.abs_filename) then
|
||||
core.command_view:set_text(view.hovered_item.filename .. "/")
|
||||
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:deselect"] = function()
|
||||
view.selected_item = nil
|
||||
end,
|
||||
|
||||
["treeview:select"] = function()
|
||||
view:set_selection(view.hovered_item)
|
||||
end,
|
||||
|
||||
["treeview:select-and-open"] = function()
|
||||
if view.hovered_item then
|
||||
view:set_selection(view.hovered_item)
|
||||
command.perform "treeview:open"
|
||||
end
|
||||
end,
|
||||
|
||||
["treeview:collapse"] = function()
|
||||
if view.selected_item then
|
||||
if view.selected_item.type == "dir" and view.selected_item.expanded then
|
||||
view:toggle_expand(false)
|
||||
else
|
||||
local parent_item, y = view:get_parent(view.selected_item)
|
||||
if parent_item then
|
||||
view:set_selection(parent_item, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["treeview:expand"] = function()
|
||||
local item = view.selected_item
|
||||
if not item or item.type ~= "dir" then return end
|
||||
|
||||
if item.expanded then
|
||||
local next_item, _, next_y = view:get_next(item)
|
||||
if next_item.depth > item.depth then
|
||||
view:set_selection(next_item, next_y)
|
||||
end
|
||||
else
|
||||
view:toggle_expand(true)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
command.add(
|
||||
function()
|
||||
local item = treeitem()
|
||||
return item ~= nil and (core.active_view == view or menu.show_context_menu), item
|
||||
end, {
|
||||
["treeview:delete"] = function(item)
|
||||
local filename = item.abs_filename
|
||||
local relfilename = item.filename
|
||||
if item.dir_name ~= core.project_dir then
|
||||
-- add secondary project dirs names to the file path to show
|
||||
relfilename = common.basepath(common.basename(item.dir_name)) .. PATHSEP .. relfilename
|
||||
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 = {
|
||||
{ text = "Yes", default_yes = true },
|
||||
{ text = "No", default_no = true }
|
||||
{ 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),
|
||||
|
@ -716,168 +570,20 @@ command.add(
|
|||
)
|
||||
end,
|
||||
|
||||
["treeview:rename"] = function(item)
|
||||
local old_filename = item.filename
|
||||
local old_abs_filename = item.abs_filename
|
||||
core.command_view:enter("Rename", {
|
||||
text = old_filename,
|
||||
submit = function(filename)
|
||||
local abs_filename = filename
|
||||
if not common.is_absolute_path(filename) then
|
||||
abs_filename = common.basepath(item.dir_name) .. filename
|
||||
end
|
||||
local res, err = os.rename(old_abs_filename, abs_filename)
|
||||
if res then -- successfully renamed
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc.abs_filename and old_abs_filename == doc.abs_filename then
|
||||
doc:set_filename(filename, abs_filename) -- make doc point to the new filename
|
||||
doc:reset_syntax()
|
||||
break -- only first needed
|
||||
end
|
||||
end
|
||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||
else
|
||||
core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
|
||||
end
|
||||
end,
|
||||
suggest = function(text)
|
||||
return common.path_suggest(text, item.dir_name)
|
||||
end
|
||||
})
|
||||
end,
|
||||
["treeview:open-in-system"] = function()
|
||||
local hovered_item = view.hovered_item
|
||||
|
||||
["treeview:new-file"] = function(item)
|
||||
local text
|
||||
if not is_project_folder(item.abs_filename) then
|
||||
text = item.filename .. PATHSEP
|
||||
end
|
||||
core.command_view:enter("Filename", {
|
||||
text = text,
|
||||
submit = function(filename)
|
||||
local doc_filename = common.basepath(item.dir_name) .. filename
|
||||
core.log(doc_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,
|
||||
suggest = function(text)
|
||||
return common.path_suggest(text, item.dir_name)
|
||||
end
|
||||
})
|
||||
end,
|
||||
|
||||
["treeview:new-folder"] = function(item)
|
||||
local text
|
||||
if not is_project_folder(item.abs_filename) then
|
||||
text = item.filename .. PATHSEP
|
||||
end
|
||||
core.command_view:enter("Folder Name", {
|
||||
text = text,
|
||||
submit = function(filename)
|
||||
local dir_path = common.basepath(item.dir_name) .. filename
|
||||
common.mkdirp(dir_path)
|
||||
core.log("Created %s", dir_path)
|
||||
end,
|
||||
suggest = function(text)
|
||||
return common.path_suggest(text, item.dir_name)
|
||||
end
|
||||
})
|
||||
end,
|
||||
|
||||
["treeview:open-in-system"] = function(item)
|
||||
if PLATFORM == "Windows" then
|
||||
system.exec(string.format("start \"\" %q", item.abs_filename))
|
||||
elseif string.find(PLATFORM, "Mac") or PLATFORM == "MorphOS" then
|
||||
system.exec(string.format("open %q", item.abs_filename))
|
||||
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" or string.find(PLATFORM, "BSD") then
|
||||
system.exec(string.format("xdg-open %q", item.abs_filename))
|
||||
elseif PLATFORM == "AmigaOS 4" then
|
||||
system.exec(string.format("WBRUN %q SHOW=all VIEWBY=name", item.abs_filename))
|
||||
system.exec(string.format("xdg-open %q", hovered_item.abs_filename))
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
local projectsearch = pcall(require, "plugins.projectsearch")
|
||||
if projectsearch then
|
||||
menu:register(function()
|
||||
local item = treeitem()
|
||||
return item and item.type == "dir"
|
||||
end, {
|
||||
{ text = "Find in directory", command = "treeview:search-in-directory" }
|
||||
})
|
||||
command.add(function()
|
||||
return view.hovered_item and view.hovered_item.type == "dir"
|
||||
end, {
|
||||
["treeview:search-in-directory"] = function(item)
|
||||
command.perform("project-search:find", view.hovered_item.abs_filename)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
command.add(function()
|
||||
local item = treeitem()
|
||||
return item
|
||||
and not is_primary_project_folder(item.abs_filename)
|
||||
and is_project_folder(item.abs_filename), item
|
||||
end, {
|
||||
["treeview:remove-project-directory"] = function(item)
|
||||
core.remove_project_directory(item.dir_name)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
keymap.add {
|
||||
["ctrl+\\"] = "treeview:toggle",
|
||||
["up"] = "treeview:previous",
|
||||
["down"] = "treeview:next",
|
||||
["left"] = "treeview:collapse",
|
||||
["right"] = "treeview:expand",
|
||||
["return"] = "treeview:open",
|
||||
["escape"] = "treeview:deselect",
|
||||
["delete"] = "treeview:delete",
|
||||
["ctrl+return"] = "treeview:new-folder",
|
||||
["lclick"] = "treeview:select-and-open",
|
||||
["mclick"] = "treeview:select",
|
||||
["ctrl+lclick"] = "treeview:new-folder"
|
||||
}
|
||||
|
||||
-- The config specification used by gui generators
|
||||
config.plugins.treeview.config_spec = {
|
||||
name = "Treeview",
|
||||
{
|
||||
label = "Size",
|
||||
description = "Default treeview width.",
|
||||
path = "size",
|
||||
type = "number",
|
||||
default = toolbar_view and math.ceil(toolbar_view:get_min_width() / SCALE)
|
||||
or 200 * SCALE,
|
||||
min = toolbar_view and toolbar_view:get_min_width() / SCALE
|
||||
or 200 * SCALE,
|
||||
get_value = function(value)
|
||||
return value / SCALE
|
||||
end,
|
||||
set_value = function(value)
|
||||
return value * SCALE
|
||||
end,
|
||||
on_apply = function(value)
|
||||
view:set_target_size("x", math.max(
|
||||
value, toolbar_view and toolbar_view:get_min_width() or 200 * SCALE
|
||||
))
|
||||
end
|
||||
},
|
||||
{
|
||||
label = "Hide on Startup",
|
||||
description = "Show or hide the treeview on startup.",
|
||||
path = "visible",
|
||||
type = "toggle",
|
||||
default = false,
|
||||
on_apply = function(value)
|
||||
view.visible = not value
|
||||
end
|
||||
}
|
||||
}
|
||||
keymap.add { ["ctrl+\\"] = "treeview:toggle" }
|
||||
|
||||
-- Return the treeview with toolbar and contextmenu to allow
|
||||
-- user or plugin modifications
|
||||
|
|
|
@ -1,53 +1,10 @@
|
|||
-- mod-version:3
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
---@class config.plugins.trimwhitespace
|
||||
---@field enabled boolean
|
||||
---@field trim_empty_end_lines boolean
|
||||
config.plugins.trimwhitespace = common.merge({
|
||||
enabled = false,
|
||||
trim_empty_end_lines = false,
|
||||
config_spec = {
|
||||
name = "Trim Whitespace",
|
||||
{
|
||||
label = "Enabled",
|
||||
description = "Disable or enable the trimming of white spaces by default.",
|
||||
path = "enabled",
|
||||
type = "toggle",
|
||||
default = false
|
||||
},
|
||||
{
|
||||
label = "Trim Empty End Lines",
|
||||
description = "Remove any empty new lines at the end of documents.",
|
||||
path = "trim_empty_end_lines",
|
||||
type = "toggle",
|
||||
default = false
|
||||
}
|
||||
}
|
||||
}, config.plugins.trimwhitespace)
|
||||
|
||||
---@class plugins.trimwhitespace
|
||||
local trimwhitespace = {}
|
||||
|
||||
---Disable whitespace trimming for a specific document.
|
||||
---@param doc core.doc
|
||||
function trimwhitespace.disable(doc)
|
||||
doc.disable_trim_whitespace = true
|
||||
end
|
||||
|
||||
---Re-enable whitespace trimming if previously disabled.
|
||||
---@param doc core.doc
|
||||
function trimwhitespace.enable(doc)
|
||||
doc.disable_trim_whitespace = nil
|
||||
end
|
||||
|
||||
---Perform whitespace trimming in all lines of a document except the
|
||||
---line where the caret is currently positioned.
|
||||
---@param doc core.doc
|
||||
function trimwhitespace.trim(doc)
|
||||
local function trim_trailing_whitespace(doc)
|
||||
local cline, ccol = doc:get_selection()
|
||||
for i = 1, #doc.lines do
|
||||
local old_text = doc:get_text(i, 1, i, math.huge)
|
||||
|
@ -65,54 +22,16 @@ function trimwhitespace.trim(doc)
|
|||
end
|
||||
end
|
||||
|
||||
---Removes all empty new lines at the end of the document.
|
||||
---@param doc core.doc
|
||||
---@param raw_remove? boolean Perform the removal not registering to undo stack
|
||||
function trimwhitespace.trim_empty_end_lines(doc, raw_remove)
|
||||
for _=#doc.lines, 1, -1 do
|
||||
local l = #doc.lines
|
||||
if l > 1 and doc.lines[l] == "\n" then
|
||||
local current_line = doc:get_selection()
|
||||
if current_line == l then
|
||||
doc:set_selection(l-1, math.huge, l-1, math.huge)
|
||||
end
|
||||
if not raw_remove then
|
||||
doc:remove(l-1, math.huge, l, math.huge)
|
||||
else
|
||||
table.remove(doc.lines, l)
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["trim-whitespace:trim-trailing-whitespace"] = function(dv)
|
||||
trimwhitespace.trim(dv.doc)
|
||||
end,
|
||||
|
||||
["trim-whitespace:trim-empty-end-lines"] = function(dv)
|
||||
trimwhitespace.trim_empty_end_lines(dv.doc)
|
||||
["trim-whitespace:trim-trailing-whitespace"] = function()
|
||||
trim_trailing_whitespace(core.active_view.doc)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
local doc_save = Doc.save
|
||||
local save = Doc.save
|
||||
Doc.save = function(self, ...)
|
||||
if
|
||||
config.plugins.trimwhitespace.enabled
|
||||
and
|
||||
not self.disable_trim_whitespace
|
||||
then
|
||||
trimwhitespace.trim(self)
|
||||
if config.plugins.trimwhitespace.trim_empty_end_lines then
|
||||
trimwhitespace.trim_empty_end_lines(self)
|
||||
end
|
||||
end
|
||||
doc_save(self, ...)
|
||||
trim_trailing_whitespace(self)
|
||||
save(self, ...)
|
||||
end
|
||||
|
||||
|
||||
return trimwhitespace
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- mod-version:3
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local DocView = require "core.docview"
|
||||
|
@ -7,7 +7,7 @@ local LogView = require "core.logview"
|
|||
|
||||
local function workspace_files_for(project_dir)
|
||||
local basename = common.basename(project_dir)
|
||||
local workspace_dir = common.basepath(USERDIR) .. "ws"
|
||||
local workspace_dir = USERDIR .. PATHSEP .. "ws"
|
||||
local info_wsdir = system.get_file_info(workspace_dir)
|
||||
if not info_wsdir then
|
||||
local ok, err = system.mkdir(workspace_dir)
|
||||
|
@ -22,7 +22,7 @@ local function workspace_files_for(project_dir)
|
|||
if file:sub(1, n) == basename then
|
||||
local id = tonumber(file:sub(n + 1):match("^-(%d+)$"))
|
||||
if id then
|
||||
coroutine.yield(common.basepath(workspace_dir) .. file, id)
|
||||
coroutine.yield(workspace_dir .. PATHSEP .. file, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -52,7 +52,7 @@ local function get_workspace_filename(project_dir)
|
|||
id = id + 1
|
||||
end
|
||||
local basename = common.basename(project_dir)
|
||||
return common.basepath(USERDIR) .. "ws" .. PATHSEP .. basename .. "-" .. tostring(id)
|
||||
return USERDIR .. PATHSEP .. "ws" .. PATHSEP .. basename .. "-" .. tostring(id)
|
||||
end
|
||||
|
||||
|
||||
|
@ -83,8 +83,7 @@ local function save_view(view)
|
|||
filename = view.doc.filename,
|
||||
selection = { view.doc:get_selection() },
|
||||
scroll = { x = view.scroll.to.x, y = view.scroll.to.y },
|
||||
crlf = view.doc.crlf,
|
||||
text = view.doc.new_file and view.doc:get_text(1, 1, math.huge, math.huge)
|
||||
text = not view.doc.filename and view.doc:get_text(1, 1, math.huge, math.huge)
|
||||
}
|
||||
end
|
||||
if mt == LogView then return end
|
||||
|
@ -93,8 +92,7 @@ local function save_view(view)
|
|||
return {
|
||||
type = "view",
|
||||
active = (core.active_view == view),
|
||||
module = name,
|
||||
scroll = { x = view.scroll.to.x, y = view.scroll.to.y, to = { x = view.scroll.to.x, y = view.scroll.to.y } },
|
||||
module = name
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -107,6 +105,7 @@ local function load_view(t)
|
|||
if not t.filename then
|
||||
-- document not associated to a file
|
||||
dv = DocView(core.open_doc())
|
||||
if t.text then dv.doc:insert(1, 1, t.text) end
|
||||
else
|
||||
-- we have a filename, try to read the file
|
||||
local ok, doc = pcall(core.open_doc, t.filename)
|
||||
|
@ -114,13 +113,11 @@ local function load_view(t)
|
|||
dv = DocView(doc)
|
||||
end
|
||||
end
|
||||
-- doc view "dv" can be nil here if the filename associated to the document
|
||||
-- cannot be read.
|
||||
if dv and dv.doc then
|
||||
if dv.doc.new_file and t.text then
|
||||
dv.doc:insert(1, 1, t.text)
|
||||
dv.doc.crlf = t.crlf
|
||||
end
|
||||
dv.doc:set_selection(table.unpack(t.selection))
|
||||
dv.last_line1, dv.last_col1, dv.last_line2, dv.last_col2 = dv.doc:get_selection()
|
||||
dv.last_line, dv.last_col = dv.doc:get_selection()
|
||||
dv.scroll.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
|
||||
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
|
||||
end
|
||||
|
@ -165,9 +162,6 @@ local function load_node(node, t)
|
|||
if t.active_view == i then
|
||||
active_view = view
|
||||
end
|
||||
if not view:is(DocView) then
|
||||
view.scroll = v.scroll
|
||||
end
|
||||
end
|
||||
end
|
||||
if active_view then
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
---@meta
|
||||
|
||||
---
|
||||
---Functionality that allows to monitor a directory or file for changes
|
||||
---using the native facilities provided by the current operating system
|
||||
---for better efficiency and performance.
|
||||
---@class dirmonitor
|
||||
dirmonitor = {}
|
||||
|
||||
---@alias dirmonitor.callback fun(fd_or_path:integer|string)
|
||||
|
||||
---
|
||||
---Creates a new dirmonitor object.
|
||||
---
|
||||
---@return dirmonitor
|
||||
function dirmonitor.new() end
|
||||
|
||||
---
|
||||
---Monitors a directory or file for changes.
|
||||
---
|
||||
---In "multiple" mode you will need to call this method more than once to
|
||||
---recursively monitor directories and files.
|
||||
---
|
||||
---In "single" mode you will only need to call this method for the parent
|
||||
---directory and every sub directory and files will get automatically monitored.
|
||||
---
|
||||
---@param path string
|
||||
---
|
||||
---@return integer fd The file descriptor id assigned to the monitored path when
|
||||
---the mode is "multiple", in "single" mode: 1 for success or -1 on failure.
|
||||
function dirmonitor:watch(path) end
|
||||
|
||||
---
|
||||
---Stops monitoring a file descriptor in "multiple" mode
|
||||
---or in "single" mode a directory path.
|
||||
---
|
||||
---@param fd_or_path integer | string A file descriptor or path.
|
||||
function dirmonitor:unwatch(fd_or_path) end
|
||||
|
||||
---
|
||||
---Verify if the resources registered for monitoring have changed, should
|
||||
---be called periodically to check for changes.
|
||||
---
|
||||
---The callback will be called for each file or directory that was:
|
||||
---edited, removed or added. A file descriptor will be passed to the
|
||||
---callback in "multiple" mode or a path in "single" mode.
|
||||
---
|
||||
---If an error occurred during the callback execution, the error callback will be called with the error object.
|
||||
---This callback should not manipulate coroutines to avoid deadlocks.
|
||||
---
|
||||
---@param callback dirmonitor.callback
|
||||
---@param error_callback fun(error: any): nil
|
||||
---
|
||||
---@return boolean? changes True when changes were detected.
|
||||
function dirmonitor:check(callback, error_callback) end
|
||||
|
||||
---
|
||||
---Get the working mode for the current file system monitoring backend.
|
||||
---
|
||||
---"multiple": various file descriptors are needed to recursively monitor a
|
||||
---directory contents, backends: inotify and kqueue.
|
||||
---
|
||||
---"single": a single process takes care of monitoring a path recursively
|
||||
---so no individual file descriptors are used, backends: win32 and fsevents.
|
||||
---
|
||||
---@return "single" | "multiple"
|
||||
function dirmonitor:mode() end
|
||||
|
||||
|
||||
return dirmonitor
|
|
@ -4,13 +4,8 @@
|
|||
---@type table<integer, string>
|
||||
ARGS = {}
|
||||
|
||||
---The current platform tuple used for native modules loading,
|
||||
---for example: "x86_64-linux", "x86_64-darwin", "x86_64-windows", etc...
|
||||
---@type string
|
||||
ARCH = "Architecture-OperatingSystem"
|
||||
|
||||
---The current operating system.
|
||||
---@type string | "Windows" | "Mac OS X" | "Linux" | "iOS" | "Android"
|
||||
---@type string | "'Windows'" | "'Mac OS X'" | "'Linux'" | "'iOS'" | "'Android'"
|
||||
PLATFORM = "Operating System"
|
||||
|
||||
---The current text or ui scale.
|
||||
|
|
|
@ -81,27 +81,27 @@ process.REDIRECT_DISCARD = 3
|
|||
process.REDIRECT_STDOUT = 4
|
||||
|
||||
---@alias process.errortype
|
||||
---| `process.ERROR_PIPE`
|
||||
---| `process.ERROR_WOULDBLOCK`
|
||||
---| `process.ERROR_TIMEDOUT`
|
||||
---| `process.ERROR_INVAL`
|
||||
---| `process.ERROR_NOMEM`
|
||||
---|>'process.ERROR_PIPE'
|
||||
---| 'process.ERROR_WOULDBLOCK'
|
||||
---| 'process.ERROR_TIMEDOUT'
|
||||
---| 'process.ERROR_INVAL'
|
||||
---| 'process.ERROR_NOMEM'
|
||||
|
||||
---@alias process.streamtype
|
||||
---| `process.STREAM_STDIN`
|
||||
---| `process.STREAM_STDOUT`
|
||||
---| `process.STREAM_STDERR`
|
||||
---|>'process.STREAM_STDIN'
|
||||
---| 'process.STREAM_STDOUT'
|
||||
---| 'process.STREAM_STDERR'
|
||||
|
||||
---@alias process.waittype
|
||||
---| `process.WAIT_INFINITE`
|
||||
---| `process.WAIT_DEADLINE`
|
||||
---|>'process.WAIT_INFINITE'
|
||||
---| 'process.WAIT_DEADLINE'
|
||||
|
||||
---@alias process.redirecttype
|
||||
---| `process.REDIRECT_DEFAULT`
|
||||
---| `process.REDIRECT_PIPE`
|
||||
---| `process.REDIRECT_PARENT`
|
||||
---| `process.REDIRECT_DISCARD`
|
||||
---| `process.REDIRECT_STDOUT`
|
||||
---|>'process.REDIRECT_DEFAULT'
|
||||
---| 'process.REDIRECT_PIPE'
|
||||
---| 'process.REDIRECT_PARENT'
|
||||
---| 'process.REDIRECT_DISCARD'
|
||||
---| 'process.REDIRECT_STDOUT'
|
||||
|
||||
---
|
||||
--- Options that can be passed to process.start()
|
||||
|
@ -112,6 +112,7 @@ process.REDIRECT_STDOUT = 4
|
|||
---@field public stdout process.redirecttype
|
||||
---@field public stderr process.redirecttype
|
||||
---@field public env table<string, string>
|
||||
process.options = {}
|
||||
|
||||
---
|
||||
---Create and start a new process
|
||||
|
@ -123,7 +124,7 @@ process.REDIRECT_STDOUT = 4
|
|||
---@return process | nil
|
||||
---@return string errmsg
|
||||
---@return process.errortype | integer errcode
|
||||
function process.start(command_and_params, options) end
|
||||
function process:start(command_and_params, options) end
|
||||
|
||||
---
|
||||
---Translates an error code into a useful text message
|
||||
|
@ -232,6 +233,3 @@ function process:returncode() end
|
|||
---
|
||||
---@return boolean
|
||||
function process:running() end
|
||||
|
||||
|
||||
return process
|
||||
|
|
|
@ -31,9 +31,9 @@ regex.NOTEMPTY = 0x00000004
|
|||
regex.NOTEMPTY_ATSTART = 0x00000008
|
||||
|
||||
---@alias regex.modifiers
|
||||
---| "i" # Case insesitive matching
|
||||
---| "m" # Multiline matching
|
||||
---| "s" # Match all characters with dot (.) metacharacter even new lines
|
||||
---|>'"i"' # Case insesitive matching
|
||||
---| '"m"' # Multiline matching
|
||||
---| '"s"' # Match all characters with dot (.) metacharacter even new lines
|
||||
|
||||
---
|
||||
---Compiles a regular expression pattern that can be used to search in strings.
|
||||
|
@ -41,8 +41,8 @@ regex.NOTEMPTY_ATSTART = 0x00000008
|
|||
---@param pattern string
|
||||
---@param options? regex.modifiers A string of one or more pattern modifiers.
|
||||
---
|
||||
---@return regex? regex Ready to use regular expression object or nil on error.
|
||||
---@return string? error The error message if compiling the pattern failed.
|
||||
---@return regex|string regex Ready to use regular expression object or error
|
||||
---message if compiling the pattern failed.
|
||||
function regex.compile(pattern, options) end
|
||||
|
||||
---
|
||||
|
@ -53,42 +53,5 @@ function regex.compile(pattern, options) end
|
|||
---@param options? integer A bit field of matching options, eg:
|
||||
---regex.NOTBOL | regex.NOTEMPTY
|
||||
---
|
||||
---@return integer? ... List of offsets where a match was found.
|
||||
---@return table<integer, integer> list List of offsets where a match was found.
|
||||
function regex:cmatch(subject, offset, options) end
|
||||
|
||||
---
|
||||
---Returns an iterator function that, each time it is called, returns the
|
||||
---next captures from `pattern` over the string subject.
|
||||
---
|
||||
---Example:
|
||||
---```lua
|
||||
--- s = "hello world hello world"
|
||||
--- for hello, world in regex.gmatch("(hello)\\s+(world)", s) do
|
||||
--- print(hello .. " " .. world)
|
||||
--- end
|
||||
---```
|
||||
---
|
||||
---@param pattern string
|
||||
---@param subject string
|
||||
---@param offset? integer
|
||||
---
|
||||
---@return fun():string, ...
|
||||
function regex.gmatch(pattern, subject, offset) end
|
||||
|
||||
---
|
||||
---Replaces the matched pattern globally on the subject with the given
|
||||
---replacement, supports named captures ((?'name'<pattern>), ${name}) and
|
||||
---$[1-9][0-9]* substitutions. Raises an error when failing to compile the
|
||||
---pattern or by a substitution mistake.
|
||||
---
|
||||
---@param pattern regex|string
|
||||
---@param subject string
|
||||
---@param replacement string
|
||||
---@param limit? integer Limits the number of substitutions that will be done.
|
||||
---
|
||||
---@return string? replaced_subject
|
||||
---@return integer? total_replacements
|
||||
function regex.gsub(pattern, subject, replacement, limit) end
|
||||
|
||||
|
||||
return regex
|
||||
|
|
|
@ -6,25 +6,23 @@
|
|||
renderer = {}
|
||||
|
||||
---
|
||||
---Array of bytes that represents a color used by the rendering functions.
|
||||
---Note: indexes for rgba are numerical 1 = r, 2 = g, 3 = b, 4 = a but for
|
||||
---documentation purposes the letters r, g, b, a were used.
|
||||
---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 "none" | "grayscale" | "subpixel"
|
||||
---@field public hinting "slight" | "none" | "full"
|
||||
---@field public bold boolean
|
||||
---@field public italic boolean
|
||||
---@field public underline boolean
|
||||
---@field public smoothing boolean
|
||||
---@field public strikethrough boolean
|
||||
---@field public antialiasing "'grayscale'" | "'subpixel'"
|
||||
---@field public hinting "'slight'" | "'none'" | '"full"'
|
||||
-- @field public bold boolean
|
||||
-- @field public italic boolean
|
||||
-- @field public underline boolean
|
||||
renderer.fontoptions = {}
|
||||
|
||||
---
|
||||
---@class renderer.font
|
||||
|
@ -35,29 +33,18 @@ renderer.font = {}
|
|||
---
|
||||
---@param path string
|
||||
---@param size number
|
||||
---@param options? renderer.fontoptions
|
||||
---@param options renderer.fontoptions
|
||||
---
|
||||
---@return renderer.font
|
||||
function renderer.font.load(path, size, options) end
|
||||
|
||||
---
|
||||
---Combines an array of fonts into a single one for broader charset support,
|
||||
---the order of the list determines the fonts precedence when retrieving
|
||||
---a symbol from it.
|
||||
---
|
||||
---@param fonts renderer.font[]
|
||||
---
|
||||
---@return renderer.font
|
||||
function renderer.font.group(fonts) end
|
||||
|
||||
---
|
||||
---Clones a font object into a new one.
|
||||
---
|
||||
---@param size? number Optional new size for cloned font.
|
||||
---@param options? renderer.fontoptions
|
||||
---
|
||||
---@return renderer.font
|
||||
function renderer.font:copy(size, options) end
|
||||
function renderer.font:copy(size) end
|
||||
|
||||
---
|
||||
---Set the amount of characters that represent a tab.
|
||||
|
@ -94,11 +81,23 @@ function renderer.font:get_size() end
|
|||
function renderer.font:set_size(size) end
|
||||
|
||||
---
|
||||
---Get the current path of the font as a string if a single font or as an
|
||||
---array of strings if a group font.
|
||||
---Assistive functionality to replace characters in a
|
||||
---rendered text with other characters.
|
||||
---@class renderer.replacements
|
||||
renderer.replacements = {}
|
||||
|
||||
---
|
||||
---@return string | table<integer, string>
|
||||
function renderer.font:get_path() end
|
||||
---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
|
||||
|
@ -142,16 +141,29 @@ function renderer.set_clip_rect(x, y, width, height) end
|
|||
function renderer.draw_rect(x, y, width, height, color) end
|
||||
|
||||
---
|
||||
---Draw text and return the x coordinate where the text finished drawing.
|
||||
---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
|
||||
function renderer.draw_text(font, text, x, y, color) end
|
||||
---@return number x_subpixel
|
||||
function renderer.draw_text(font, text, x, y, color, replace, color_replace) end
|
||||
|
||||
|
||||
return renderer
|
||||
---
|
||||
---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
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
---@meta
|
||||
|
||||
---UTF-8 equivalent of string.byte
|
||||
---@param s string
|
||||
---@param i? integer
|
||||
---@param j? integer
|
||||
---@return integer
|
||||
---@return ...
|
||||
function string.ubyte(s, i, j) end
|
||||
|
||||
---UTF-8 equivalent of string.char
|
||||
---@param byte integer
|
||||
---@param ... integer
|
||||
---@return string
|
||||
---@return ...
|
||||
function string.uchar(byte, ...) end
|
||||
|
||||
---UTF-8 equivalent of string.find
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@param init? integer
|
||||
---@param plain? boolean
|
||||
---@return integer start
|
||||
---@return integer end
|
||||
---@return ... captured
|
||||
function string.ufind(s, pattern, init, plain) end
|
||||
|
||||
---UTF-8 equivalent of string.gmatch
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@param init? integer
|
||||
---@return fun():string, ...
|
||||
function string.ugmatch(s, pattern, init) end
|
||||
|
||||
---UTF-8 equivalent of string.gsub
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@param repl string|table|function
|
||||
---@param n integer
|
||||
---@return string
|
||||
---@return integer count
|
||||
function string.ugsub(s, pattern, repl, n) end
|
||||
|
||||
---UTF-8 equivalent of string.len
|
||||
---@param s string
|
||||
---@return integer
|
||||
function string.ulen(s) end
|
||||
|
||||
---UTF-8 equivalent of string.lower
|
||||
---@param s string
|
||||
---@return string
|
||||
function string.ulower(s) end
|
||||
|
||||
---UTF-8 equivalent of string.match
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@param init? integer
|
||||
---@return string | number captured
|
||||
function string.umatch(s, pattern, init) end
|
||||
|
||||
---UTF-8 equivalent of string.reverse
|
||||
---@param s string
|
||||
---@return string
|
||||
function string.ureverse(s) end
|
||||
|
||||
---UTF-8 equivalent of string.sub
|
||||
---@param s string
|
||||
---@param i integer
|
||||
---@param j? integer
|
||||
---@return string
|
||||
function string.usub(s, i, j) end
|
||||
|
||||
---UTF-8 equivalent of string.upper
|
||||
---@param s string
|
||||
---@return string
|
||||
function string.uupper(s) end
|
||||
|
||||
---Equivalent to utf8.escape()
|
||||
---@param s string
|
||||
---@return string utf8_string
|
||||
function string.uescape(s) end
|
||||
|
||||
|
||||
---Equivalent to utf8.charpos()
|
||||
---@param s string
|
||||
---@param charpos? integer
|
||||
---@param index? integer
|
||||
---@return integer charpos
|
||||
---@return integer codepoint
|
||||
function string.ucharpos(s, charpos, index) end
|
||||
|
||||
---Equivalent to utf8.next()
|
||||
---@param s string
|
||||
---@param charpos? integer
|
||||
---@param index? integer
|
||||
---@return integer charpos
|
||||
---@return integer codepoint
|
||||
function string.unext(s, charpos, index) end
|
||||
|
||||
---Equivalent to utf8.insert()
|
||||
---@param s string
|
||||
---@param idx? integer
|
||||
---@param substring string
|
||||
---@return string new_string
|
||||
function string.uinsert(s, idx, substring) end
|
||||
|
||||
---Equivalent to utf8.remove()
|
||||
---@param s string
|
||||
---@param start? integer
|
||||
---@param stop? integer
|
||||
---@return string new_string
|
||||
function string.uremove(s, start, stop) end
|
||||
|
||||
---Equivalent to utf8.width()
|
||||
---@param s string
|
||||
---@param ambi_is_double? boolean
|
||||
---@param default_width? integer
|
||||
---@return integer width
|
||||
function string.uwidth(s, ambi_is_double, default_width) end
|
||||
|
||||
---Equivalent to utf8.widthindex()
|
||||
---@param s string
|
||||
---@param location integer
|
||||
---@param ambi_is_double? boolean
|
||||
---@param default_width? integer
|
||||
---@return integer idx
|
||||
---@return integer offset
|
||||
---@return integer width
|
||||
function string.uwidthindex(s, location, ambi_is_double, default_width) end
|
||||
|
||||
---Equivalent to utf8.title()
|
||||
---@param s string
|
||||
---@return string new_string
|
||||
function string.utitle(s) end
|
||||
|
||||
---Equivalent to utf8.fold()
|
||||
---@param s string
|
||||
---@return string new_string
|
||||
function string.ufold(s) end
|
||||
|
||||
---Equivalent to utf8.ncasecmp()
|
||||
---@param a string
|
||||
---@param b string
|
||||
---@return integer result
|
||||
function string.uncasecmp(a, b) end
|
||||
|
||||
---Equivalent to utf8.offset()
|
||||
---@param s string
|
||||
---@param n integer
|
||||
---@param i? integer
|
||||
---@return integer position_in_bytes
|
||||
function string.uoffset(s, n, i) end
|
||||
|
||||
---Equivalent to utf8.codepoint()
|
||||
---@param s string
|
||||
---@param i? integer
|
||||
---@param j? integer
|
||||
---@return integer code
|
||||
---@return ...
|
||||
function string.ucodepoint(s, i, j) end
|
||||
|
||||
---Equivalent to utf8.codes()
|
||||
---@param s string
|
||||
---@return fun():integer, integer
|
||||
function string.ucodes(s) end
|
|
@ -6,15 +6,15 @@
|
|||
system = {}
|
||||
|
||||
---@alias system.fileinfotype
|
||||
---| "file" # It is a file.
|
||||
---| "dir" # It is a directory.
|
||||
---|>'"file"' # It is a file.
|
||||
---| '"dir"' # It is a directory.
|
||||
|
||||
---
|
||||
---@class system.fileinfo
|
||||
---@field public modified number A timestamp in seconds.
|
||||
---@field public size number Size in bytes.
|
||||
---@field public type system.fileinfotype Type of file
|
||||
---@field public symlink boolean The directory is a symlink. This field is only set on Linux and on directories.
|
||||
system.fileinfo = {}
|
||||
|
||||
---
|
||||
---Core function used to retrieve the current event been triggered by SDL.
|
||||
|
@ -24,7 +24,7 @@ system = {}
|
|||
---
|
||||
---Window events:
|
||||
--- * "quit"
|
||||
--- * "resized" -> width, height (in points)
|
||||
--- * "resized" -> width, height
|
||||
--- * "exposed"
|
||||
--- * "minimized"
|
||||
--- * "maximized"
|
||||
|
@ -38,18 +38,12 @@ system = {}
|
|||
--- * "keypressed" -> key_name
|
||||
--- * "keyreleased" -> key_name
|
||||
--- * "textinput" -> text
|
||||
--- * "textediting" -> text, start, length
|
||||
---
|
||||
---Mouse events:
|
||||
--- * "mousepressed" -> button_name, x, y, amount_of_clicks
|
||||
--- * "mousereleased" -> button_name, x, y
|
||||
--- * "mousemoved" -> x, y, relative_x, relative_y
|
||||
--- * "mousewheel" -> y, x
|
||||
---
|
||||
---Touch events:
|
||||
--- * "touchpressed" -> x, y, finger_id
|
||||
--- * "touchreleased" -> x, y, finger_id
|
||||
--- * "touchmoved" -> x, y, distance_x, distance_y, finger_id
|
||||
--- * "mousewheel" -> y
|
||||
---
|
||||
---@return string type
|
||||
---@return any? arg1
|
||||
|
@ -61,16 +55,16 @@ 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. If not provided, waits forever.
|
||||
---@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 or if no event was received.
|
||||
---@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"
|
||||
---@param type string | "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'"
|
||||
function system.set_cursor(type) end
|
||||
|
||||
---
|
||||
|
@ -80,10 +74,10 @@ function system.set_cursor(type) end
|
|||
function system.set_window_title(title) end
|
||||
|
||||
---@alias system.windowmode
|
||||
---| "normal"
|
||||
---| "minimized"
|
||||
---| "maximized"
|
||||
---| "fullscreen"
|
||||
---|>'"normal"'
|
||||
---| '"minimized"'
|
||||
---| '"maximized"'
|
||||
---| '"fullscreen"'
|
||||
|
||||
---
|
||||
---Change the window mode.
|
||||
|
@ -107,12 +101,10 @@ 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.
|
||||
---To disable custom window management, call this function without any
|
||||
---arguments
|
||||
---
|
||||
---@param title_height? number Height of the window decoration
|
||||
---@param controls_width? number Width of window controls (maximize,minimize and close buttons, etc).
|
||||
---@param resize_border? number The amount of pixels reserved for resizing
|
||||
---@param 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
|
||||
|
||||
---
|
||||
|
@ -139,30 +131,6 @@ function system.set_window_size(width, height, x, y) end
|
|||
---@return boolean
|
||||
function system.window_has_focus() end
|
||||
|
||||
---
|
||||
---Gets the mode of the window.
|
||||
---
|
||||
---@return system.windowmode
|
||||
function system.get_window_mode() end
|
||||
|
||||
---
|
||||
---Sets the position of the IME composition window.
|
||||
---
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param width number
|
||||
---@param height number
|
||||
function system.set_text_input_rect(x, y, width, height) end
|
||||
|
||||
---
|
||||
---Clears any ongoing composition on the IME
|
||||
function system.clear_ime() end
|
||||
|
||||
---
|
||||
---Raise the main window and give it input focus.
|
||||
---Note: may not always be obeyed by the users window manager.
|
||||
function system.raise_window() end
|
||||
|
||||
---
|
||||
---Opens a message box to display an error message.
|
||||
---
|
||||
|
@ -170,14 +138,6 @@ function system.raise_window() end
|
|||
---@param message string
|
||||
function system.show_fatal_error(title, message) end
|
||||
|
||||
---
|
||||
---Deletes an empty directory.
|
||||
---
|
||||
---@param path string
|
||||
---@return boolean success True if the operation suceeded, false otherwise
|
||||
---@return string? message An error message if the operation failed
|
||||
function system.rmdir(path) end
|
||||
|
||||
---
|
||||
---Change the current directory path which affects relative file operations.
|
||||
---This function raises an error if the path doesn't exists.
|
||||
|
@ -192,7 +152,6 @@ function system.chdir(path) end
|
|||
---@param directory_path string
|
||||
---
|
||||
---@return boolean created True on success or false on failure.
|
||||
---@return string? message The error message if the operation failed.
|
||||
function system.mkdir(directory_path) end
|
||||
|
||||
---
|
||||
|
@ -209,7 +168,7 @@ function system.list_dir(path) end
|
|||
---
|
||||
---@param path string
|
||||
---
|
||||
---@return string? abspath
|
||||
---@return string
|
||||
function system.absolute_path(path) end
|
||||
|
||||
---
|
||||
|
@ -221,28 +180,6 @@ function system.absolute_path(path) end
|
|||
---@return string? message Error message in case of error.
|
||||
function system.get_file_info(path) end
|
||||
|
||||
|
||||
---@alias system.fstype
|
||||
---| "ext2/ext3"
|
||||
---| "nfs"
|
||||
---| "fuse"
|
||||
---| "smb"
|
||||
---| "smb2"
|
||||
---| "reiserfs"
|
||||
---| "tmpfs"
|
||||
---| "ramfs"
|
||||
---| "ntfs"
|
||||
|
||||
---
|
||||
---Gets the filesystem type of a path.
|
||||
---Note: This only works on Linux.
|
||||
---
|
||||
---@param path string Can be path to a directory or a file
|
||||
---
|
||||
---@return system.fstype
|
||||
function system.get_fs_type(path) end
|
||||
|
||||
|
||||
---
|
||||
---Retrieve the text currently stored on the clipboard.
|
||||
---
|
||||
|
@ -255,12 +192,6 @@ function system.get_clipboard() end
|
|||
---@param text string
|
||||
function system.set_clipboard(text) end
|
||||
|
||||
---
|
||||
---Get the process id of lite-xl itself.
|
||||
---
|
||||
---@return integer
|
||||
function system.get_process_id() end
|
||||
|
||||
---
|
||||
---Get amount of iterations since the application was launched
|
||||
---also known as SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency()
|
||||
|
@ -278,9 +209,7 @@ function system.sleep(seconds) end
|
|||
---Similar to os.execute() but does not return the exit status of the
|
||||
---executed command and executes the process in a non blocking way by
|
||||
---forking it to the background.
|
||||
---Note: Do not use this function, use the Process API instead.
|
||||
---
|
||||
---@deprecated
|
||||
---@param command string The command to execute.
|
||||
function system.exec(command) end
|
||||
|
||||
|
@ -302,27 +231,4 @@ function system.fuzzy_match(haystack, needle, file) end
|
|||
---
|
||||
---@param opacity number A value from 0.0 to 1.0, the lower the value
|
||||
---the less visible the window will be.
|
||||
---@return boolean success True if the operation suceeded.
|
||||
function system.set_window_opacity(opacity) end
|
||||
|
||||
---
|
||||
---Loads a lua native module using the default Lua API or lite-xl native plugin API.
|
||||
---Note: Never use this function directly.
|
||||
---
|
||||
---@param name string the name of the module
|
||||
---@param path string the path to the shared library file
|
||||
---@return number nargs the return value of the entrypoint
|
||||
function system.load_native_plugin(name, path) end
|
||||
|
||||
---
|
||||
---Compares two paths in the order used by TreeView.
|
||||
---
|
||||
---@param path1 string
|
||||
---@param type1 system.fileinfotype
|
||||
---@param path2 string
|
||||
---@param type2 system.fileinfotype
|
||||
---@return boolean compare_result True if path1 < path2
|
||||
function system.path_compare(path1, type1, path2, type2) end
|
||||
|
||||
|
||||
return system
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
---@meta
|
||||
|
||||
---Additional utf8 support not provided by lua.
|
||||
---@class utf8extra
|
||||
utf8extra = {}
|
||||
|
||||
---UTF-8 equivalent of string.byte
|
||||
---@param s string
|
||||
---@param i? integer
|
||||
---@param j? integer
|
||||
---@return integer
|
||||
---@return ...
|
||||
function utf8extra.byte(s, i, j) end
|
||||
|
||||
---UTF-8 equivalent of string.char
|
||||
---@param byte integer
|
||||
---@param ... integer
|
||||
---@return string
|
||||
---@return ...
|
||||
function utf8extra.char(byte, ...) end
|
||||
|
||||
---UTF-8 equivalent of string.find
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@param init? integer
|
||||
---@param plain? boolean
|
||||
---@return integer start
|
||||
---@return integer end
|
||||
---@return ... captured
|
||||
function utf8extra.find(s, pattern, init, plain) end
|
||||
|
||||
---UTF-8 equivalent of string.gmatch
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@param init? integer
|
||||
---@return fun():string, ...
|
||||
function utf8extra.gmatch(s, pattern, init) end
|
||||
|
||||
---UTF-8 equivalent of string.gsub
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@param repl string|table|function
|
||||
---@param n integer
|
||||
---@return string
|
||||
---@return integer count
|
||||
function utf8extra.gsub(s, pattern, repl, n) end
|
||||
|
||||
---UTF-8 equivalent of string.len
|
||||
---@param s string
|
||||
---@return integer
|
||||
function utf8extra.len(s) end
|
||||
|
||||
---UTF-8 equivalent of string.lower
|
||||
---@param s string
|
||||
---@return string
|
||||
function utf8extra.lower(s) end
|
||||
|
||||
---UTF-8 equivalent of string.match
|
||||
---@param s string
|
||||
---@param pattern string
|
||||
---@param init? integer
|
||||
---@return string | number captured
|
||||
function utf8extra.match(s, pattern, init) end
|
||||
|
||||
---UTF-8 equivalent of string.reverse
|
||||
---@param s string
|
||||
---@return string
|
||||
function utf8extra.reverse(s) end
|
||||
|
||||
---UTF-8 equivalent of string.sub
|
||||
---@param s string
|
||||
---@param i integer
|
||||
---@param j? integer
|
||||
---@return string
|
||||
function utf8extra.sub(s, i, j) end
|
||||
|
||||
---UTF-8 equivalent of string.upper
|
||||
---@param s string
|
||||
---@return string
|
||||
function utf8extra.upper(s) end
|
||||
|
||||
---Escape a str to UTF-8 format string. It support several escape format:
|
||||
---* %ddd - which ddd is a decimal number at any length: change Unicode code point to UTF-8 format.
|
||||
---* %{ddd} - same as %nnn but has bracket around.
|
||||
---* %uddd - same as %ddd, u stands Unicode
|
||||
---* %u{ddd} - same as %{ddd}
|
||||
---* %xhhh - hexadigit version of %ddd
|
||||
---* %x{hhh} same as %xhhh.
|
||||
---* %? - '?' stands for any other character: escape this character.
|
||||
---Example:
|
||||
---```lua
|
||||
---local u = utf8.escape
|
||||
---print(u"%123%u123%{123}%u{123}%xABC%x{ABC}")
|
||||
---print(u"%%123%?%d%%u")
|
||||
---```
|
||||
---@param s string
|
||||
---@return string utf8_string
|
||||
function utf8extra.escape(s) end
|
||||
|
||||
---Convert UTF-8 position to byte offset. if only index is given, return byte
|
||||
---offset of this UTF-8 char index. if both charpos and index is given, a new
|
||||
---charpos will be calculated, by add/subtract UTF-8 char index to current
|
||||
---charpos. in all cases, it returns a new char position, and code point
|
||||
---(a number) at this position.
|
||||
---@param s string
|
||||
---@param charpos? integer
|
||||
---@param index? integer
|
||||
---@return integer charpos
|
||||
---@return integer codepoint
|
||||
function utf8extra.charpos(s, charpos, index) end
|
||||
|
||||
---Iterate though the UTF-8 string s. If only s is given, it can used as a iterator:
|
||||
---```lua
|
||||
--- for pos, code in utf8.next, "utf8-string" do
|
||||
--- -- ...
|
||||
--- end
|
||||
---````
|
||||
---If only charpos is given, return the next byte offset of in string. if
|
||||
---charpos and index is given, a new charpos will be calculated, by add/subtract
|
||||
---UTF-8 char offset to current charpos. in all case, it return a new char
|
||||
---position (in bytes), and code point (a number) at this position.
|
||||
---@param s string
|
||||
---@param charpos? integer
|
||||
---@param index? integer
|
||||
---@return integer charpos
|
||||
---@return integer codepoint
|
||||
function utf8extra.next(s, charpos, index) end
|
||||
|
||||
---Insert a substring to s. If idx is given, insert substring before char at
|
||||
---this index, otherwise substring will concat to s. idx can be negative.
|
||||
---@param s string
|
||||
---@param idx? integer
|
||||
---@param substring string
|
||||
---@return string new_string
|
||||
function utf8extra.insert(s, idx, substring) end
|
||||
|
||||
---Delete a substring in s. If neither start nor stop is given, delete the last
|
||||
---UTF-8 char in s, otherwise delete char from start to end of s. if stop is
|
||||
---given, delete char from start to stop (include start and stop). start and
|
||||
---stop can be negative.
|
||||
---@param s string
|
||||
---@param start? integer
|
||||
---@param stop? integer
|
||||
---@return string new_string
|
||||
function utf8extra.remove(s, start, stop) end
|
||||
|
||||
---Calculate the width of UTF-8 string s. if ambi_is_double is given, the
|
||||
---ambiguous width character's width is 2, otherwise it's 1. fullwidth/doublewidth
|
||||
---character's width is 2, and other character's width is 1. if default_width is
|
||||
---given, it will be the width of unprintable character, used display a
|
||||
---non-character mark for these characters. if s is a code point, return the
|
||||
---width of this code point.
|
||||
---@param s string
|
||||
---@param ambi_is_double? boolean
|
||||
---@param default_width? integer
|
||||
---@return integer width
|
||||
function utf8extra.width(s, ambi_is_double, default_width) end
|
||||
|
||||
---Return the character index at given location in string s. this is a reverse
|
||||
---operation of utf8.width(). this function returns a index of location, and a
|
||||
---offset in UTF-8 encoding. e.g. if cursor is at the second column (middle)
|
||||
---of the wide char, offset will be 2. the width of character at idx is
|
||||
---returned, also.
|
||||
---@param s string
|
||||
---@param location integer
|
||||
---@param ambi_is_double? boolean
|
||||
---@param default_width? integer
|
||||
---@return integer idx
|
||||
---@return integer offset
|
||||
---@return integer width
|
||||
function utf8extra.widthindex(s, location, ambi_is_double, default_width) end
|
||||
|
||||
---Convert UTF-8 string s to title-case, used to compare by ignore case. if s
|
||||
---is a number, it's treat as a code point and return a convert code point
|
||||
---(number). utf8.lower/utf8.pper has the same extension.
|
||||
---@param s string
|
||||
---@return string new_string
|
||||
function utf8extra.title(s) end
|
||||
|
||||
---Convert UTF-8 string s to folded case, used to compare by ignore case. if s
|
||||
---is a number, it's treat as a code point and return a convert code point
|
||||
---(number). utf8.lower/utf8.pper has the same extension.
|
||||
---@param s string
|
||||
---@return string new_string
|
||||
function utf8extra.fold(s) end
|
||||
|
||||
---Compare a and b without case, -1 means a < b, 0 means a == b and 1 means a > b.
|
||||
---@param a string
|
||||
---@param b string
|
||||
---@return integer result
|
||||
function utf8extra.ncasecmp(a, b) end
|
||||
|
||||
|
||||
return utf8extra
|
166
meson.build
166
meson.build
|
@ -1,41 +1,18 @@
|
|||
project('lite-xl',
|
||||
['c'],
|
||||
version : '2.1.4',
|
||||
version : '2.0.3',
|
||||
license : 'MIT',
|
||||
meson_version : '>= 0.56',
|
||||
default_options : [
|
||||
'c_std=gnu11'
|
||||
]
|
||||
meson_version : '>= 0.54',
|
||||
default_options : ['c_std=gnu11']
|
||||
)
|
||||
|
||||
#===============================================================================
|
||||
# Project version including git commit if possible
|
||||
#===============================================================================
|
||||
version = meson.project_version()
|
||||
|
||||
if get_option('buildtype') != 'release'
|
||||
git_command = find_program('git', required : false)
|
||||
|
||||
if git_command.found()
|
||||
git_commit = run_command(
|
||||
[git_command, 'rev-parse', 'HEAD'],
|
||||
check : false
|
||||
).stdout().strip()
|
||||
|
||||
if git_commit != ''
|
||||
version += ' (git-' + git_commit.substring(0, 8) + ')'
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
#===============================================================================
|
||||
# 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', version)
|
||||
conf_data.set('PROJECT_ASSEMBLY_VERSION', meson.project_version() + '.0')
|
||||
conf_data.set('PROJECT_VERSION', meson.project_version())
|
||||
|
||||
#===============================================================================
|
||||
# Compiler Settings
|
||||
|
@ -47,22 +24,19 @@ endif
|
|||
cc = meson.get_compiler('c')
|
||||
|
||||
lite_includes = []
|
||||
lite_cargs = ['-DSDL_MAIN_HANDLED', '-DPCRE2_STATIC']
|
||||
lite_cargs = []
|
||||
# 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
|
||||
if get_option('arch_tuple') != ''
|
||||
arch_tuple = get_option('arch_tuple')
|
||||
else
|
||||
arch_tuple = '@0@-@1@'.format(target_machine.cpu_family(), target_machine.system())
|
||||
endif
|
||||
lite_cargs += '-DLITE_ARCH_TUPLE="@0@"'.format(arch_tuple)
|
||||
|
||||
#===============================================================================
|
||||
# Linker Settings
|
||||
#===============================================================================
|
||||
lite_link_args = []
|
||||
if cc.get_id() == 'gcc' and get_option('buildtype') == 'release'
|
||||
lite_link_args += ['-static-libgcc']
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'darwin'
|
||||
lite_link_args += ['-framework', 'CoreServices', '-framework', 'Foundation']
|
||||
endif
|
||||
|
@ -72,108 +46,14 @@ endif
|
|||
if not get_option('source-only')
|
||||
libm = cc.find_library('m', required : false)
|
||||
libdl = cc.find_library('dl', required : false)
|
||||
|
||||
default_fallback_options = ['warning_level=0', 'werror=false']
|
||||
|
||||
# Lua has no official .pc file
|
||||
# so distros come up with their own names
|
||||
lua_names = [
|
||||
'lua5.4', # Debian
|
||||
'lua-5.4', # FreeBSD
|
||||
'lua', # Fedora
|
||||
]
|
||||
|
||||
if get_option('use_system_lua')
|
||||
foreach lua : lua_names
|
||||
last_lua = (lua == lua_names[-1] or get_option('wrap_mode') == 'forcefallback')
|
||||
lua_dep = dependency(lua, required : false,
|
||||
)
|
||||
if lua_dep.found()
|
||||
break
|
||||
endif
|
||||
|
||||
if last_lua
|
||||
# If we could not find lua on the system and fallbacks are disabled
|
||||
# try the compiler as a last ditch effort, since Lua has no official
|
||||
# pkg-config support.
|
||||
lua_dep = cc.find_library('lua', required : true)
|
||||
endif
|
||||
endforeach
|
||||
else
|
||||
lua_dep = dependency('', fallback: ['lua', 'lua_dep'], required : true,
|
||||
default_options: default_fallback_options + ['default_library=static', 'line_editing=false', 'interpreter=false']
|
||||
)
|
||||
endif
|
||||
|
||||
pcre2_dep = dependency('libpcre2-8', fallback: ['pcre2', 'libpcre2_8'],
|
||||
default_options: default_fallback_options + ['default_library=static', 'grep=false', 'test=false']
|
||||
threads_dep = dependency('threads')
|
||||
lua_dep = dependency('lua5.2', fallback: ['lua', 'lua_dep'],
|
||||
default_options: ['shared=false', 'use_readline=false', 'app=false']
|
||||
)
|
||||
|
||||
freetype_dep = dependency('freetype2', fallback: ['freetype2', 'freetype_dep'],
|
||||
default_options: default_fallback_options + ['default_library=static', 'zlib=disabled', 'bzip2=disabled', 'png=disabled', 'harfbuzz=disabled', 'brotli=disabled']
|
||||
)
|
||||
|
||||
|
||||
sdl_options = ['default_library=static']
|
||||
|
||||
# we explicitly need these
|
||||
sdl_options += 'use_loadso=enabled'
|
||||
sdl_options += 'prefer_dlopen=true'
|
||||
sdl_options += 'use_video=enabled'
|
||||
sdl_options += 'use_atomic=enabled'
|
||||
sdl_options += 'use_threads=enabled'
|
||||
sdl_options += 'use_timers=enabled'
|
||||
sdl_options += 'with_main=true'
|
||||
# investigate if this is truly needed
|
||||
# Do not remove before https://github.com/libsdl-org/SDL/issues/5413 is released
|
||||
sdl_options += 'use_events=enabled'
|
||||
|
||||
if host_machine.system() == 'darwin' or host_machine.system() == 'windows'
|
||||
sdl_options += 'use_video_x11=disabled'
|
||||
sdl_options += 'use_video_wayland=disabled'
|
||||
else
|
||||
sdl_options += 'use_render=enabled'
|
||||
sdl_options += 'use_video_x11=auto'
|
||||
sdl_options += 'use_video_wayland=auto'
|
||||
endif
|
||||
|
||||
# we leave this up to what the host system has except on windows
|
||||
if host_machine.system() != 'windows'
|
||||
sdl_options += 'use_video_opengl=auto'
|
||||
sdl_options += 'use_video_openglesv2=auto'
|
||||
else
|
||||
sdl_options += 'use_video_opengl=disabled'
|
||||
sdl_options += 'use_video_openglesv2=disabled'
|
||||
endif
|
||||
|
||||
# we don't need these
|
||||
sdl_options += 'test=false'
|
||||
sdl_options += 'use_sensor=disabled'
|
||||
sdl_options += 'use_haptic=disabled'
|
||||
sdl_options += 'use_audio=disabled'
|
||||
sdl_options += 'use_cpuinfo=disabled'
|
||||
sdl_options += 'use_joystick=disabled'
|
||||
sdl_options += 'use_video_vulkan=disabled'
|
||||
sdl_options += 'use_video_offscreen=disabled'
|
||||
sdl_options += 'use_power=disabled'
|
||||
sdl_options += 'system_iconv=disabled'
|
||||
|
||||
sdl_dep = dependency('sdl2', fallback: ['sdl2', 'sdl2_dep'],
|
||||
default_options: default_fallback_options + sdl_options
|
||||
)
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
if sdl_dep.type_name() == 'internal'
|
||||
sdlmain_dep = dependency('sdl2main', fallback: ['sdl2main_dep'])
|
||||
else
|
||||
sdlmain_dep = cc.find_library('SDL2main')
|
||||
endif
|
||||
else
|
||||
sdlmain_dep = dependency('', required: false)
|
||||
assert(not sdlmain_dep.found(), 'checking if fake dependency has been found')
|
||||
endif
|
||||
|
||||
lite_deps = [lua_dep, sdl_dep, sdlmain_dep, freetype_dep, pcre2_dep, libm, libdl]
|
||||
pcre2_dep = dependency('libpcre2-8')
|
||||
freetype_dep = dependency('freetype2')
|
||||
sdl_dep = dependency('sdl2', method: 'config-tool')
|
||||
lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl, threads_dep]
|
||||
endif
|
||||
#===============================================================================
|
||||
# Install Configuration
|
||||
|
@ -182,11 +62,6 @@ if get_option('portable') or host_machine.system() == 'windows'
|
|||
lite_bindir = '/'
|
||||
lite_docdir = '/doc'
|
||||
lite_datadir = '/data'
|
||||
configure_file(
|
||||
input: 'resources/windows/lite-xl.exe.manifest.in',
|
||||
output: 'lite-xl.exe.manifest',
|
||||
configuration: conf_data
|
||||
)
|
||||
elif get_option('bundle') and host_machine.system() == 'darwin'
|
||||
lite_cargs += '-DMACOS_USE_BUNDLE'
|
||||
lite_bindir = 'Contents/MacOS'
|
||||
|
@ -219,20 +94,21 @@ endif
|
|||
|
||||
install_data('licenses/licenses.md', install_dir : lite_docdir)
|
||||
|
||||
install_subdir('docs/api' , install_dir : lite_datadir, strip_directory: true)
|
||||
install_subdir('data/core' , install_dir : lite_datadir, exclude_files : 'start.lua')
|
||||
install_subdir('data' / 'core' , install_dir : lite_datadir, exclude_files : 'start.lua')
|
||||
foreach data_module : ['fonts', 'plugins', 'colors']
|
||||
install_subdir(join_paths('data', data_module), install_dir : lite_datadir)
|
||||
install_subdir('data' / data_module , install_dir : lite_datadir)
|
||||
endforeach
|
||||
|
||||
configure_file(
|
||||
input : 'data/core/start.lua',
|
||||
output : 'start.lua',
|
||||
configuration : conf_data,
|
||||
install_dir : join_paths(lite_datadir, 'core'),
|
||||
install : true,
|
||||
install_dir : lite_datadir / 'core',
|
||||
)
|
||||
|
||||
if not get_option('source-only')
|
||||
subdir('lib/dmon')
|
||||
subdir('src')
|
||||
subdir('scripts')
|
||||
endif
|
||||
|
|
|
@ -2,6 +2,3 @@ option('bundle', type : 'boolean', value : false, description: 'Build a macOS bu
|
|||
option('source-only', type : 'boolean', value : false, description: 'Configure source files only, doesn\'t checks for dependencies')
|
||||
option('portable', type : 'boolean', value : false, description: 'Portable install')
|
||||
option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer')
|
||||
option('dirmonitor_backend', type : 'combo', value : '', choices : ['', 'inotify', 'fsevents', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use')
|
||||
option('arch_tuple', type : 'string', value : '', description: 'Specify a custom architecture tuple')
|
||||
option('use_system_lua', type : 'boolean', value : false, description: 'Prefer System Lua over a the meson wrap')
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
# Resources
|
||||
|
||||
This folder contains resources that is used for building or packaging the project.
|
||||
|
||||
### Build
|
||||
|
||||
- `cross/*.txt`: Meson [cross files][1] for cross-compiling lite-xl on other platforms.
|
||||
|
||||
### Packaging
|
||||
|
||||
- `icons/icon.{icns,ico,inl,rc,svg}`: lite-xl icon in various formats.
|
||||
- `linux/com.lite_xl.LiteXL.appdata.xml`: AppStream metadata.
|
||||
- `linux/com.lite_xl.LiteXL.desktop`: Desktop file for Linux desktops.
|
||||
- `macos/dmg-cover.png`: Background image for packaging macOS DMGs.
|
||||
- `macos/Info.plist.in`: Template for generating `info.plist` on macOS. See `macos/macos-retina-display.md` for details.
|
||||
- `macos/lite-xl-dmg.py`: Configuration options for dmgbuild for packaging macOS DMGs.
|
||||
- `windows/001-lua-unicode.diff`: Patch for allowing Lua to load files with UTF-8 filenames on Windows.
|
||||
|
||||
### Development
|
||||
|
||||
- `include/lite_xl_plugin_api.h`: Native plugin API header. See the contents of `lite_xl_plugin_api.h` for more details.
|
||||
|
||||
|
||||
[1]: https://mesonbuild.com/Cross-compilation.html
|
|
@ -1,10 +0,0 @@
|
|||
; Lite-XL AutoInstall
|
||||
; $VER: Lite-XL AutoInstall 1.0 (15.02.2024)
|
||||
|
||||
; Get the path to the executable from the ENV variable
|
||||
Set litexlPath `GetEnv AppDir/lite-xl`
|
||||
|
||||
copy LiteXL2/#? "$litexlPath" CLONE ALL
|
||||
|
||||
; Free the variable
|
||||
UnSet litexlPath
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue