Compare commits

..

No commits in common. "amiga-2.0" and "os4" have entirely different histories.

133 changed files with 4811 additions and 15029 deletions

3
.github/labeler.yml vendored
View File

@ -33,6 +33,3 @@
"Category: C Core": "Category: C Core":
- src/**/* - src/**/*
"Category: Libraries":
- lib/**/*

View File

@ -1,20 +1,45 @@
name: CI name: CI
# All builds use lhelper only for releases,
# otherwise for normal builds dependencies are dynamically linked.
on: on:
push: push:
branches: branches:
- '*' - '*'
# tags:
# - 'v[0-9]*'
pull_request: pull_request:
branches: branches:
- '*' - '*'
workflow_dispatch:
jobs: 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: build_linux:
name: Linux name: Linux
runs-on: ubuntu-22.04 runs-on: ubuntu-18.04
strategy: strategy:
matrix: matrix:
config: config:
@ -24,71 +49,103 @@ jobs:
CC: ${{ matrix.config.cc }} CC: ${{ matrix.config.cc }}
CXX: ${{ matrix.config.cxx }} CXX: ${{ matrix.config.cxx }}
steps: steps:
- name: Set Environment Variables - name: Set Environment Variables
if: ${{ matrix.config.cc == 'gcc' }} if: ${{ matrix.config.cc == 'gcc' }}
run: | run: |
echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV" echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-linux-$(uname -m)-portable" >> "$GITHUB_ENV" echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-linux-$(uname -m)" >> "$GITHUB_ENV"
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Python Setup - name: Python Setup
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: 3.9 python-version: 3.6
- name: Update Packages - name: Update Packages
run: sudo apt-get update run: sudo apt-get update
- name: Install Dependencies - name: Install Dependencies
run: bash scripts/install-dependencies.sh --debug if: ${{ !startsWith(github.ref, 'refs/tags/') }}
- name: Build run: bash scripts/install-dependencies.sh --debug
run: | - name: Install Release Dependencies
bash --version if: ${{ startsWith(github.ref, 'refs/tags/') }}
bash scripts/build.sh --debug --forcefallback --portable run: |
- name: Package bash scripts/install-dependencies.sh --debug --lhelper
if: ${{ matrix.config.cc == 'gcc' }} bash scripts/lhelper.sh --debug
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --binary - name: Build
- name: Upload Artifacts run: |
uses: actions/upload-artifact@v2 bash --version
if: ${{ matrix.config.cc == 'gcc' }} bash scripts/build.sh --debug --forcefallback
with: - name: Package
name: Linux Artifacts if: ${{ matrix.config.cc == 'gcc' }}
path: ${{ env.INSTALL_NAME }}.tar.gz 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: build_macos:
name: macOS (x86_64) name: macOS (x86_64)
runs-on: macos-11 runs-on: macos-10.15
env: env:
CC: clang CC: clang
CXX: clang++ CXX: clang++
steps: steps:
- name: System Information - name: System Information
run: | run: |
system_profiler SPSoftwareDataType system_profiler SPSoftwareDataType
bash --version bash --version
gcc -v gcc -v
xcodebuild -version xcodebuild -version
- name: Set Environment Variables - name: Set Environment Variables
run: | run: |
echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV" echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-macos-$(uname -m)" >> "$GITHUB_ENV" echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-macos-$(uname -m)" >> "$GITHUB_ENV"
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Python Setup - name: Python Setup
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: 3.9 python-version: 3.9
- name: Install Dependencies - name: Install Dependencies
run: bash scripts/install-dependencies.sh --debug if: ${{ !startsWith(github.ref, 'refs/tags/') }}
- name: Build run: bash scripts/install-dependencies.sh --debug
run: | - name: Install Release Dependencies
bash --version if: ${{ startsWith(github.ref, 'refs/tags/') }}
bash scripts/build.sh --bundle --debug --forcefallback run: |
- name: Create DMG Image bash scripts/install-dependencies.sh --debug --lhelper
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --dmg bash scripts/lhelper.sh --debug
- name: Upload DMG Image - name: Build
uses: actions/upload-artifact@v2 run: |
with: bash --version
name: macOS DMG Image bash scripts/build.sh --bundle --debug --forcefallback
path: ${{ env.INSTALL_NAME }}.dmg - 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: build_windows_msys2:
name: Windows name: Windows
@ -103,6 +160,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: msys2/setup-msys2@v2 - uses: msys2/setup-msys2@v2
with: with:
#msystem: MINGW64
msystem: ${{ matrix.msystem }} msystem: ${{ matrix.msystem }}
update: true update: true
install: >- install: >-
@ -112,22 +170,83 @@ jobs:
- name: Set Environment Variables - name: Set Environment Variables
run: | run: |
echo "$HOME/.local/bin" >> "$GITHUB_PATH" 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" 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 - name: Install Dependencies
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
run: bash scripts/install-dependencies.sh --debug 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 - name: Build
run: | run: |
bash --version 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 - 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 - name: Upload Artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: Windows Artifacts 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
deploy:
name: Deployment
runs-on: ubuntu-18.04
# if: startsWith(github.ref, 'refs/tags/')
if: false
needs:
- archive_source_code
- build_linux
- build_macos
- build_windows_msys2
steps:
- name: Set Environment Variables
run: echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
- uses: actions/download-artifact@v2
with:
name: Linux Artifacts
- uses: actions/download-artifact@v2
with:
name: macOS DMG Image
- uses: actions/download-artifact@v2
with:
name: Source Code Tarball
- uses: actions/download-artifact@v2
with:
name: Windows Artifacts
- name: Display File Information
shell: bash
run: ls -lR
# Note: not using `actions/create-release@v1`
# because it cannot update an existing release
# see https://github.com/actions/create-release/issues/29
- uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.INSTALL_REF }}
name: Release ${{ env.INSTALL_REF }}
draft: false
prerelease: false
files: |
lite-xl-${{ env.INSTALL_REF }}-*
LiteXL*.AppImage
LiteXL*.exe

View File

@ -1,183 +0,0 @@
name: Release
on:
workflow_dispatch:
inputs:
version:
description: Release Version
default: v2.1.0
required: true
jobs:
release:
name: Create Release
runs-on: ubuntu-20.04
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
version: ${{ steps.tag.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- 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: 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
prerelease: false
body_path: changelog.md
generate_release_notes: true
build_linux:
name: Linux
needs: release
runs-on: ubuntu-20.04
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@v2
- name: Python Setup
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Update Packages
run: sudo apt-get update
- name: Install Dependencies
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 }}
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 (x86_64)
needs: release
runs-on: macos-11
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-$(uname -m)" >> "$GITHUB_ENV"
echo "INSTALL_NAME_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-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
run: bash scripts/install-dependencies.sh --debug
- name: Build
run: |
bash --version
bash scripts/build.sh --bundle --debug --forcefallback --release
- name: Create DMG Image
run: |
bash scripts/package.sh --version ${INSTALL_REF} --debug --dmg --release
bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --dmg --release
- name: Upload Files
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.release.outputs.version }}
files: |
${{ env.INSTALL_NAME }}.dmg
${{ env.INSTALL_NAME_ADDONS }}.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@v2
- 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 }}
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

8
.gitignore vendored
View File

@ -2,10 +2,9 @@ build*/
.build*/ .build*/
lhelper/ lhelper/
submodules/ submodules/
subprojects/*/ subprojects/lua/
subprojects/reproc/
/appimage* /appimage*
.vscode
.cache
.ccls-cache .ccls-cache
.lite-debug.log .lite-debug.log
.run* .run*
@ -24,7 +23,4 @@ lite
*.lha *.lha
release_files release_files
*.o *.o
*.snalyzerinfo
!resources/windows/*.diff

View File

@ -1,4 +1,4 @@
Copyright (c) 2020-2021 Lite XL Team Copyright (c) 2020-2021 Francesco Abbate
Permission is hereby granted, free of charge, to any person obtaining a copy of 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 this software and associated documentation files (the "Software"), to deal in

View File

@ -1,86 +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
outfile := lite
compiler := gcc
cxxcompiler := g++
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 -lSDL2 -llua54 -lagg -lfreetype -lm -lc -L/usr/local/lib
.PHONY: LiteXL clean release
default: LiteXL
clean:
@echo "Cleaning compiler objects..."
@rm -f $(LiteXL_OBJ)
LiteXL: $(LiteXL_OBJ)
@echo "Linking LiteXL"
@$(cxxcompiler) -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/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.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
release: clean LiteXL
@echo "Creating release files..."
@mkdir -p release/LiteXL2
@cp release_files/* release/LiteXL2/ -r
@mv release/LiteXL2/LiteXL2.info release/
@cp data release/LiteXL2/ -r
@cp changelog.md release/LiteXL2/
@cp lite release/LiteXL2/
@strip release/LiteXL2/lite
@cp README.md release/LiteXL2/
@cp README_Amiga.md release/LiteXL2/
@cp LICENSE release/LiteXL2/
@echo "Creating release archive..."
@lha -aeqr3 a LiteXL2_MOS.lha release/
@echo "Clean release files..."
@delete release ALL QUIET FORCE

View File

@ -5,37 +5,24 @@
# #
LiteXL_OBJ := \ LiteXL_OBJ := \
src/main.o src/rencache.o src/renderer.o src/renwindow.o \ src/dirmonitor.o src/main.o src/rencache.o src/renderer.o \
src/api/api.o src/api/dirmonitor.o \ src/renwindow.o src/api/api.o src/api/regex.o \
src/api/regex.o src/api/renderer.o src/api/system.o \ src/api/renderer.o src/api/system.o src/platform/amigaos4.o
src/api/utf8.o src/platform/amigaos4.o \
src/api/dirmonitor/os4.o
outfile := lite outfile := lite
compiler := gcc compiler := gcc
cxxcompiler := g++ cxxcompiler := g++
INCPATH := -Isrc -Ilib/dmon -I/sdk/local/newlib/include/SDL2 \ INCPATH := -Isrc -Ilib/dmon -I/sdk/local/newlib/include/SDL2 -I/sdk/local/common/include/freetype2
-I/sdk/local/common/include/lua54 -I/sdk/local/common/include/freetype2 DFLAGS := -D__USE_INLINE__ -DLITE_XL_DATA_USE_EXEDIR
# -DLITE_USE_SDL_RENDERER
DFLAGS += -D__USE_INLINE__ -DLITE_XL_DATA_USE_EXEDIR # -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"
CFLAGS += -Werror -Wwrite-strings -O2 -g -std=gnu11 -fno-strict-aliasing
LFLAGS += -mcrt=newlib -lauto \
-lpcre2 -lSDL2 -llua54 -lfreetype -lz -lm -lpthread -athread=native
ifeq ($(DEBUG),1)
CFLAGS += -gstabs
LFLAGS += -gstabs
ifeq ($(PROFYLER),1)
CFLAGS += -finstrument-functions -fno-inline -DPROFILING
LFLAGS += -lprofyle
endif
endif
.PHONY: LiteXL clean release .PHONY: LiteXL clean release
@ -48,16 +35,18 @@ clean:
LiteXL: $(LiteXL_OBJ) LiteXL: $(LiteXL_OBJ)
@echo "Linking LiteXL" @echo "Linking LiteXL"
@$(compiler) -o $(outfile) $(LiteXL_OBJ) $(LFLAGS) @$(cxxcompiler) -o $(outfile) $(LiteXL_OBJ) $(LFLAGS)
.c.o: .c.o:
@echo "Compiling $<" @echo "Compiling $<"
@$(compiler) -c $< -o $*.o $(CFLAGS) $(INCPATH) $(DFLAGS) @$(compiler) -c $< -o $*.o $(CFLAGS) $(INCPATH) $(DFLAGS)
src/dirmonitor.o: src/dirmonitor.c src/platform/amigaos4.h
src/main.o: src/main.c src/api/api.h src/rencache.h \ src/main.o: src/main.c src/api/api.h src/rencache.h \
src/renderer.h src/platform/amigaos4.h src/renderer.h src/platform/amigaos4.h src/dirmonitor.h
src/rencache.o: src/rencache.c src/rencache.o: src/rencache.c
@ -75,26 +64,19 @@ src/api/system.o: src/api/system.c
src/platform/amigaos4.o: src/platform/amigaos4.c src/platform/amigaos4.o: src/platform/amigaos4.c
src/api/dirmonitor.o: src/api/dirmonitor.c src/api/dirmonitor/os4.c
src/api/utf8.o: src/api/utf8.c
src/api/dirmonitor/os4.o: src/api/dirmonitor/os4.c
release: release:
@echo "Creating release files..." mkdir -p release/LiteXL2
@mkdir -p release/LiteXL2 cp release_files/* release/LiteXL2/ -r
@cp -r release_files/* release/LiteXL2/ mv release/LiteXL2/LiteXL2.info release/
@mv release/LiteXL2/LiteXL2.info release/ cp data release/LiteXL2/ -r
@cp -r data release/LiteXL2/ cp changelog.md release/LiteXL2/
@cp changelog.md release/LiteXL2/ cp lite release/LiteXL2/
@cp lite release/LiteXL2/ strip release/LiteXL2/lite
@strip release/LiteXL2/lite cp README.md release/LiteXL2/
@cp README.md release/LiteXL2/ cp README_OS4.md release/LiteXL2/
@cp README_Amiga.md release/LiteXL2/ cp LICENSE release/LiteXL2/
@cp LICENSE release/LiteXL2/ lha -aeqr3 a LiteXL2_OS4.lha release/
@echo "Creating release archive..."
@lha -aeqr3 a LiteXL2_OS4.lha release/
@echo "Clean release files..."
@delete release ALL QUIET FORCE

View File

@ -27,7 +27,7 @@ The changes and differences between Lite XL and rxi/lite are listed in the
## Overview ## 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 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, something practical, pretty, *small* and fast easy to modify and extend,
or to use without doing either. or to use without doing either.
@ -148,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 [screenshot-dark]: https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png
[lite]: https://github.com/rxi/lite [lite]: https://github.com/rxi/lite
[website]: https://lite-xl.com [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 Lite XL]: https://github.com/lite-xl/lite-xl/releases/latest
[Get plugins]: https://github.com/lite-xl/lite-xl-plugins [Get plugins]: https://github.com/lite-xl/lite-xl-plugins
[Get color themes]: https://github.com/lite-xl/lite-xl-colors [Get color themes]: https://github.com/lite-xl/lite-xl-colors
[changelog]: https://github.com/lite-xl/lite-xl/blob/master/changelog.md [changelog]: https://github.com/lite-xl/lite-xl/blob/master/changelog.md
[Lite XL plugins repository]: https://github.com/lite-xl/lite-xl-plugins [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 [colors repository]: https://github.com/lite-xl/lite-xl-colors
[LICENSE]: LICENSE [LICENSE]: LICENSE
[licenses]: licenses/licenses.md [licenses]: licenses/licenses.md

View File

@ -1,24 +1,32 @@
# Lite XL v2 for AmigaOS 4.1 FE & MorphOS 3 # Lite XL v2 for AmigaOS 4.1 FE
Lite XL is a lightweight text editor written in Lua. Lite XL is a lightweight text editor written in Lua.
The port is not perfect and it might have issues here and there. For example The port is not perfect and might has issues here and there. For example
the filesystem notifications are not working yet. So when you make changes the filesystem notifications are not working yet. So when you make changes
at a project folder those will not be reflected in Lite XL automatically. at a project folder those will not be reflected in Lite XL automatically.
It might crash from time to time, if there is a path problem, but overall 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. 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 ## New features against Lite XL v1
- Faster file scrolling
- Faster switch between tabs
- Reposition tabs at the side or at the bottom of other tabs, making
multiple columns/rows of opened files
- Multiple cursor editing
- Better font manipulation and appearance
- Faster transitions
## Installation
You can extract the Lite XL archive wherever you want and run the *lite* You can extract the Lite XL archive wherever you want and run the *lite*
editor. editor.
## Configuration folder ## Configuration folder
This editor creates a `.config` folder where the configuration is saved, as This editor creates a `.config` folder where the configuration is saved, as
well as plugins, themes etc.. By default this version uses the well as plugins, themes etc.. By default this AmigaOS 4.1 FE version uses the
executable folder, but if you want to override it, you can create an ENV executable folder, but if you want to ovveride it, create an ENV variable
variable named `HOME` and set there your prefferable path. named `HOME` and set there your path.
You can check if there is one already set by executing the following command You can check if there is one already set by executing the following command
in a shell in a shell
@ -57,10 +65,10 @@ The themes can also be found at
https://github.com/lite-xl/lite-xl-colors https://github.com/lite-xl/lite-xl-colors
### Plugins ### Plugins
This Lite XL release is based on version 2.0.3 of the application as The Lite XL that you are using on AmigaOS 4 is based on version 2.0.4
released on other systems, by the original development team. and not the latest version that is available by the development team.
This not the latest version. This means that some of the latest This means that some of the latest plugins might not working at all
plugins might not working at all or need modifications to work. or need some modifications to work.
To make it easier for you, I gathered some of the plugins that are working 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 well, and I included them under `addons/plugins`. For you to install the
@ -75,11 +83,7 @@ The included plugins are the following:
**autoinsert** **autoinsert**
Automatically inserts closing brackets and quotes. Also allows selected Automatically inserts closing brackets and quotes. Also allows selected
text to be wrapped with brackets or quotes. 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** **autowrap**
Automatically hardwraps lines when typing Automatically hardwraps lines when typing
@ -94,24 +98,19 @@ Underlines matching pair for bracket under the caret
Underlays color values (eg. `#ff00ff` or `rgb(255, 0, 255)`) with their Underlays color values (eg. `#ff00ff` or `rgb(255, 0, 255)`) with their
resultant color. resultant color.
**eofnewline-xl**
Make sure the file ends with one blank line.
**ephemeral_tabs** **ephemeral_tabs**
Preview tabs. Opening a doc will replace the contents of the preview tab. 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. Marks tabs as non-preview on any change or tab double clicking.
**ghmarkdown** **ghmarkdown**
Opens a preview of the current markdown file in a browser window. 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.
**indentguide** **indentguide**
Adds indent guides Adds indent guides
**language_guide**
Syntax for the AmigaGuide scripting language
**language_hws**
Syntax for the Hollywood language
**language_make** **language_make**
Syntax for the Make build system language Syntax for the Make build system language
@ -132,17 +131,6 @@ this plugin will make the editor slower on file loading and scrolling.
Allows moving back and forward between document positions, reducing the Allows moving back and forward between document positions, reducing the
amount of scrolling 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 shift+mousewheel
or a command.
**openfilelocation**
Opens the parent directory of the current file in the file manager
**rainbowparen** **rainbowparen**
Show nesting of parentheses with rainbow colours Show nesting of parentheses with rainbow colours
@ -150,10 +138,6 @@ Show nesting of parentheses with rainbow colours
Keep a list of recently closed tabs, and restore the tab in order on Keep a list of recently closed tabs, and restore the tab in order on
cntrl+shift+t. cntrl+shift+t.
**select_colorscheme**
Select a color theme, like VScode, Sublime Text.
(plugin saves changes)
**selectionhighlight** **selectionhighlight**
Highlights regions of code that match the current selection Highlights regions of code that match the current selection
@ -191,10 +175,8 @@ folders, as you like.
## I would like to thank ## I would like to thank
- IconDesigner for the proper glow icons that are included in the release - 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 - Capehill for his tireless work on SDL port
- Michael Trebilcock for his port on liblua - 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 - Lite XL original team for being helpful and providing info
Without all the above Lite XL would not be possible Without all the above Lite XL would not be possible
@ -210,64 +192,6 @@ https://git.walkero.gr/walkero/lite-xl/issues
# Changelog # Changelog
## [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 ## [2.0.3r1] - 2022-03-30
### Changed ### Changed
- Applied all the necessary changes to make it run under AmigaOS 4.1 FE - Applied all the necessary changes to make it run under AmigaOS 4.1 FE
@ -275,6 +199,6 @@ https://git.walkero.gr/walkero/lite-xl/issues
# Disclaimer # Disclaimer
YOU MAY USE IT AT YOUR OWN RISK! YOU MAY USE IT AT YOUR OWN RISK!
I will not be held responsible for any data loss or problems you might get I will not be held responsible for any data loss or problem you might get
by using this software. by using this software.

View File

@ -37,7 +37,6 @@ show_help() {
echo "-D --dmg Create a DMG disk image (macOS only)." echo "-D --dmg Create a DMG disk image (macOS only)."
echo " Requires NPM and AppDMG." echo " Requires NPM and AppDMG."
echo "-I --innosetup Create an InnoSetup installer (Windows only)." 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 "-S --source Create a source code package,"
echo " including subprojects dependencies." echo " including subprojects dependencies."
echo echo
@ -59,7 +58,6 @@ main() {
local innosetup local innosetup
local portable local portable
local pgo local pgo
local release
for i in "$@"; do for i in "$@"; do
case $i in case $i in
@ -111,10 +109,6 @@ main() {
portable="--portable" portable="--portable"
shift shift
;; ;;
-r|--release)
release="--release"
shift
;;
-S|--source) -S|--source)
source="--source" source="--source"
shift shift
@ -151,7 +145,6 @@ main() {
$force_fallback \ $force_fallback \
$bundle \ $bundle \
$portable \ $portable \
$release \
$pgo $pgo
source scripts/package.sh \ source scripts/package.sh \
@ -165,7 +158,6 @@ main() {
$appimage \ $appimage \
$dmg \ $dmg \
$innosetup \ $innosetup \
$release \
$source $source
} }

View File

@ -1,8 +0,0 @@
CC="${CC:-gcc}"
CXX="${CXX:-g++}"
CFLAGS=
CXXFLAGS=
LDFLAGS=
BUILD_TYPE=Release
packages=(pcre2 freetype2 sdl2 lua)

View File

@ -1,405 +1,20 @@
# Changes Log This files document the changes done in Lite XL for each release.
## [2.1.0] - 2022-09-25 ### 2.0.3
### New Features Replace periodic rescan of project folder with a notification based system using the
* Make distinction between [dmon library](https://github.com/septag/dmon). Improves performance especially for
[line and block comments](https://github.com/lite-xl/lite-xl/pull/771), large project folders since the application no longer needs to rescan.
and added all appropriate functionality to the commenting/uncommenting lines. The application also reports immediatly any change in the project directory even
when the application is unfocused.
* [Added in line paste mode](https://github.com/lite-xl/lite-xl/pull/713),
if you copy without a selection.
* Many [improvements to treeview](https://github.com/lite-xl/lite-xl/pull/732),
including keyboard navigation of treeview, and ability to specify single vs.
double-click behavior.
* Added in [soft line wrapping](https://github.com/lite-xl/lite-xl/pull/636)
as core plugin, under `linewrapping.lua`, use `F10` to activate.
* Revamped [StatusView](https://github.com/lite-xl/lite-xl/pull/852) API with
new features that include:
* Support for predicates, click actions, tooltips on item hover
and custom drawing of added items.
* Hide items that are too huge by rendering with clip_rect.
* Ability to drag or scroll the left or right if too many items to display.
* New status bar commands accessible from the command palette that
include: toggling status bar visibility, toggling specific item visibility,
enable/disable status messages, etc...
* Added `renderer.font.group` interface to set up
[font fallback groups](https://github.com/lite-xl/lite-xl/pull/616) in
the font renderer, if a token doesn't have a corresponding glyph.
**Example:**
```lua
local emoji_font = renderer.font.load(USERDIR .. "/fonts/NotoEmoji-Regular.ttf", 15 * SCALE)
local nonicons = renderer.font.load(USERDIR .. "/fonts/nonicons.ttf", 15 * SCALE)
style.code_font = renderer.font.group({style.code_font, nonicons, emoji_font})
```
* Added in the ability to specify
[mouse clicks](https://github.com/lite-xl/lite-xl/pull/589) in the
keymap, allowing for easy binds of `ctrl+lclick`, and the like.
**Example:**
```lua
keymap.add { ["ctrl+shift+3lclick"] = "core:open-log" }
```
* Improved ability for plugins to be loaded at a given time, by making the
convention of defining a config for the plugin using `common.merge` to merge
existing hashes together, rather than overwriting.
* Releases will now include all language plugins and the
[settings gui](https://github.com/lite-xl/lite-xl-plugins/pull/65) plugin.
* New [core.warn](https://github.com/lite-xl/lite-xl/pull/1005) was introduced.
* Added [suggestions warping](https://github.com/lite-xl/lite-xl/pull/1003)
for `CommandView`.
* Allow regexes in tokenizer to
[split tokens with group](https://github.com/lite-xl/lite-xl/pull/999).
* Added [settings gui support](https://github.com/lite-xl/lite-xl/pull/995)
to core plugins.
* Support for [stricter predicates](https://github.com/lite-xl/lite-xl/pull/990)
by appending a `!`, eg: `"core.docview!"`.
* [UTF8 support in tokenizer](https://github.com/lite-xl/lite-xl/pull/945)
and new utf8 counter parts of string functions,
eg: `string.ulen`, `string.ulower`, etc...
* Added [utf8 support](https://github.com/lite-xl/lite-xl/pull/986) on doc
lower and upper commands.
* Allow syntax patterns to match with the
[beginning of the line](https://github.com/lite-xl/lite-xl/pull/860).
**Example:**
```lua
{ pattern = "^my_pattern_starting_at_beginning", type="symbol" }
```
* [Add View:on_file_dropped](https://github.com/lite-xl/lite-xl/pull/845).
* Implemented new function to retrieve current process id of lite-xl
[system.get_process_id()](https://github.com/lite-xl/lite-xl/pull/833).
* [Allow functions in keymap](https://github.com/lite-xl/lite-xl/pull/948).
* [Add type ahead to CommandView](https://github.com/lite-xl/lite-xl/pull/963).
* Add syntax symbols to
[auto-complete](https://github.com/lite-xl/lite-xl/pull/913).
* Add [animation categories](https://github.com/lite-xl/lite-xl/pull/941)
to enable finer transitions control.
* Added in a [native plugin](https://github.com/lite-xl/lite-xl/pull/527)
interface that allows for C-level interfacing with a statically-linked
lite-xl. The implementation of this may change in future.
* Config: added new development option to prevent plugin version checking at
startup named [skip_plugins_version](https://github.com/lite-xl/lite-xl/pull/879)
* Added a smoothing and strikethrough option to font loading
([#1087](https://github.com/lite-xl/lite-xl/pull/1087))
* Allow command predicates to manage parameters, allow overwriting commands
([#1098](https://github.com/lite-xl/lite-xl/pull/1098))
* Added in simple directory search to treeview.
([#1110](https://github.com/lite-xl/lite-xl/pull/1110))
* Added in native modules suffixes.
([#1111](https://github.com/lite-xl/lite-xl/pull/1111))
* plugin scale: added option to set default scale
([#1115](https://github.com/lite-xl/lite-xl/pull/1115))
* Added in ability to have init.so as a require for cpath.
([#1126](https://github.com/lite-xl/lite-xl/pull/1126))
### Performance Improvements
* [Load space metrics only when creating font](https://github.com/lite-xl/lite-xl/pull/1032)
* [Performance improvement](https://github.com/lite-xl/lite-xl/pull/883)
of detect indent plugin.
* Improve performance of
[ren_draw_rect](https://github.com/lite-xl/lite-xl/pull/935).
* Improved [tokenizer performance](https://github.com/lite-xl/lite-xl/pull/896).
* drawwhitespace: [Cache whitespace location](https://github.com/lite-xl/lite-xl/pull/1030)
* CommandView: improve performance by
[only drawing visible](https://github.com/lite-xl/lite-xl/pull/1047)
### Backward Incompatible Changes
* [Upgraded Lua to 5.4](https://github.com/lite-xl/lite-xl/pull/781), which
should improve performance, and provide useful extra functionality. It should
also be more available out of the box with most modern
linux/unix-based package managers.
* Bumped plugin mod-version number as various interfaces like: `DocView`,
`StatusView` and `CommandView` have changed which should require a revision
from plugin developers to make sure their plugins work with this new release.
* Changed interface for key handling; now, all components should return true if
they've handled the event.
* For plugin developers, declaring config options by directly assigning
to the plugin table (eg: `config.plugins.plugin_name.myvalue = 10`) was
deprecated in favor of using `common.merge`.
**Example:**
```lua
config.plugins.autowrap = common.merge({
enabled = false,
files = { "%.md$", "%.txt$" }
}, config.plugins.autowrap)
```
* `DocView:draw_text_line` and related functions been used by plugin developers
require a revision, since some of this interfaces were updated to support
line wrapping.
* Removed `cp_replace`, and replaced this with a core plugin,
[drawwhitespace.lua](https://github.com/lite-xl/lite-xl/pull/908).
### Deprecated Features
* For plugins the usage of the `--lite-xl` version tag was dropped
in favor of `--mod-version`.
* Overriding `StatusView:get_items()` has been deprecated in favor of
the new dedicated interface to insert status bar items:
**New Interface:**
```lua
------@return StatusView.Item
function StatusView:add_item(
{ predicate, name, alignment, get_item, command, position, tooltip, separator }
) end
```
**Example:**
```lua
core.status_view:add_item({
predicate = nil,
name = "status:memory-usage",
alignment = StatusView.Item.RIGHT,
get_item = function()
return {
style.text,
string.format(
"%.2f MB",
(math.floor(collectgarbage("count") / 10.24) / 100)
)
}
end,
command = nil,
position = 1,
tooltip = "lua memory usage",
separator = core.status_view.separator2
})
```
* [CommandView:enter](https://github.com/lite-xl/lite-xl/pull/1004) now accepts
a single options table as a parameter, meaning that the old way of calling
this function will now show a deprecation message. Also `CommandView:set_text`
and `CommandView:set_hidden_suggestions` has been
[deprecated](https://github.com/lite-xl/lite-xl/pull/1014).
**Example:**
```lua
core.command_view:enter("Title", {
submit = function() end,
suggest = function() return end,
cancel = function() end,
validate = function() return true end,
text = "",
select_text = false,
show_suggestions = true,
typeahead = true,
wrap = true
})
```
### Other Changes
* Removed `dmon`, and implemented independent backends for dirmonitoring. Also
more cleanly split out dirmonitoring into its own class in lua, from core.init.
We should now support FreeBSD; and any other system that uses `kqueue` as
their dir monitoring library. We also have a dummy-backend, which reverts
transparently to scanning if there is some issue with applying OS-level
watches (such as system limits).
* Removed `libagg` and the font renderer; compacted all font rendering into a
single renderer.c file which uses `libfreetype` directly. Now allows for ad-hoc
bolding, italics, and underlining of fonts.
* Removed `reproc` and replaced this with a simple POSIX/Windows implementation
in `process.c`. This allows for greater tweakability (i.e. we can now `break`
for debugging purposes), performance (startup time of subprocesses is
noticeably shorter), and simplicity (we no longer have to link reproc, or
winsock, on windows).
* [Split out `Node` and `EmptyView`](https://github.com/lite-xl/lite-xl/pull/715)
into their own lua files, for plugin extensibility reasons.
* Improved fuzzy_matching to probably give you something closer to what you're
looking for.
* Improved handling of alternate keyboard layouts.
* Added in a default keymap for `core:restart`, `ctrl+shift+r`.
* Improvements to the [C and C++](https://github.com/lite-xl/lite-xl/pull/875)
syntax files.
* Improvements to [markdown](https://github.com/lite-xl/lite-xl/pull/862)
syntax file.
* [Improvements to borderless](https://github.com/lite-xl/lite-xl/pull/994)
mode on Windows.
* Fixed a bunch of problems relating to
[multi-cursor](https://github.com/lite-xl/lite-xl/pull/886).
* NagView: [support vscroll](https://github.com/lite-xl/lite-xl/pull/876) when
message is too long.
* Meson improvements which include:
* Added in meson wraps for freetype, pcre2, and SDL2 which target public,
rather than lite-xl maintained repos.
* [Seperate dirmonitor logic](https://github.com/lite-xl/lite-xl/pull/866),
add build time detection of features.
* Add [fallbacks](https://github.com/lite-xl/lite-xl/pull/798) to all
common dependencies.
* [Update SDL to 2.0.20](https://github.com/lite-xl/lite-xl/pull/884).
* install [docs/api](https://github.com/lite-xl/lite-xl/pull/979) to datadir
for lsp support.
* Always check if the beginning of the
[text needs to be clipped](https://github.com/lite-xl/lite-xl/pull/871).
* Added [git commit](https://github.com/lite-xl/lite-xl/pull/859)
on development builds.
* Update [autocomplete](https://github.com/lite-xl/lite-xl/pull/832)
with changes needed for latest LSP plugin.
* Use SDL to manage color format mapping in
[ren_draw_rect](https://github.com/lite-xl/lite-xl/pull/829).
* Various code [clean ups](https://github.com/lite-xl/lite-xl/pull/826).
* [Autoreload Nagview](https://github.com/lite-xl/lite-xl/pull/942).
* [Enhancements to scrollbar](https://github.com/lite-xl/lite-xl/pull/916).
* Set the correct working directory for the
[AppImage version](https://github.com/lite-xl/lite-xl/pull/937).
* Core: fixes and changes to
[temp file](https://github.com/lite-xl/lite-xl/pull/906) functions.
* [Added plugin load-time log](https://github.com/lite-xl/lite-xl/pull/966).
* TreeView improvements for
[multi-project](https://github.com/lite-xl/lite-xl/pull/1010).
* Open LogView on user/project
[module reload error](https://github.com/lite-xl/lite-xl/pull/1022).
* Check if ["open" pattern is escaped](https://github.com/lite-xl/lite-xl/pull/1034)
* Support [UTF-8 on Windows](https://github.com/lite-xl/lite-xl/pull/1041) (Lua)
* Make system.* functions support
[UTF8 filenames on windows](https://github.com/lite-xl/lite-xl/pull/1042)
* [Fix memory leak](https://github.com/lite-xl/lite-xl/pull/1039) and wrong
check in font_retrieve
* Many, many, many more changes that are too numerous to list.
* CommandView: do not change caret size with config.line_height
([#1080](https://github.com/lite-xl/lite-xl/pull/1080))
## [2.0.5] - 2022-01-29
Revamp the project's user module so that modifications are immediately applied.
Add a mechanism to ignore files or directory based on their project's path.
The new mechanism is backward compatible.*
Essentially there are two mechanisms:
- if a '/' or a '/$' appear at the end of the pattern it will match only
directories
- if a '/' appears anywhere in the pattern except at the end the pattern will
be applied to the path
In the first case, when the pattern corresponds to a directory, a '/' will be
appended to the name of each directory before checking the pattern.
In the second case, when the pattern corresponds to a path, the complete path of
the file or directory will be used with an initial '/' added to the path.
Fix several problems with the directory monitoring library.
Now the application should no longer assert when some related system call fails
and we fallback to rescan when an error happens.
On linux no longer use the recursive monitoring which was a source of problem.
Directory monitoring is now aware of symlinks and treat them appropriately.
Fix problem when encountering special files type on linux.
Improve directory monitoring so that the related thread actually waits without
using any CPU time when there are no events.
Improve the suggestion when changing project folder or opening a new one.
Now the previously used directory are suggested but if the path is changed the
actual existing directories that match the pattern are suggested.
In addition always use the text entered in the command view even if a suggested
entry is highlighted.
The NagView warning window now no longer moves the document content.
## [2.0.4] - 2021-12-20
Fix some bugs related to newly introduced directory monitoring using the
dmon library.
Fix a problem with plain text search using Lua patterns by error.
Fix a problem with visualization of UTF-8 characters that caused garbage
characters visualization.
Other fixes and improvements contributed by @Guldoman.
## [2.0.3] - 2021-10-23
Replace periodic rescan of project folder with a notification based system
using the [dmon library](https://github.com/septag/dmon). Improves performance
especially for large project folders since the application no longer needs to
rescan. The application also reports immediately any change in the project
directory even when the application is unfocused.
Improved find-replace reverse and forward search. Improved find-replace reverse and forward search.
Fixed a bug in incremental syntax highlighting affecting documents with Fixed a bug in incremental syntax highlighting affecting documents with multiple-lines
multiple-lines comments or strings. comments or strings.
The application now always shows the tabs in the documents' view even when The application now always shows the tabs in the documents' view even when a single
a single document is opened. Can be changed with the option document is opened. Can be changed with the option `config.always_show_tabs`.
`config.always_show_tabs`.
Fix problem with numeric keypad function keys not properly working. Fix problem with numeric keypad function keys not properly working.
@ -407,36 +22,32 @@ Fix problem with pixel not correctly drawn at the window's right edge.
Treat correctly and open network paths on Windows. Treat correctly and open network paths on Windows.
Add some improvements for very slow network file systems. Add some improvements for very slow network filesystems.
Fix problem with python syntax highlighting, contributed by @dflock. Fix problem with python syntax highliting, contributed by @dflock.
## [2.0.2] - 2021-09-10 ### 2.0.2
Fix problem project directory when starting the application from Launcher on Fix problem project directory when starting the application from Launcher on macOS.
macOS.
Improved LogView. Entries can now be expanded and there is a context menu to Improved LogView. Entries can now be expanded and there is a context menu to copy the item's content.
copy the item's content.
Change the behavior of `ctrl+d` to add a multi-cursor selection to the next Change the behavior of `ctrl+d` to add a multi-cursor selection to the next occurrence.
occurrence. The old behavior to move the selection to the next occurrence is The old behavior to move the selection to the next occurrence is now done using the shortcut `ctrl+f3`.
now done using the shortcut `ctrl+f3`.
Added a command to create a multi-cursor with all the occurrences of the Added a command to create a multi-cursor with all the occurrences of the current selection.
current selection. Activated with the shortcut `ctrl+shift+l`. Activated with the shortcut `ctrl+shift+l`.
Fix problem when trying to close an unsaved new document. Fix problem when trying to close an unsaved new document.
No longer shows an error for the `-psn` argument passed to the application on No longer shows an error for the `-psn` argument passed to the application on macOS.
macOS.
Fix `treeview:open-in-system` command on Windows. Fix `treeview:open-in-system` command on Windows.
Fix rename command to update name of document if opened. Fix rename command to update name of document if opened.
Improve the find and replace dialog so that previously used expressions can be Improve the find and replace dialog so that previously used expressions can be recalled
recalled using "up" and "down" keys. using "up" and "down" keys.
Build package script rewrite with many improvements. Build package script rewrite with many improvements.
@ -444,76 +55,63 @@ Use bigger fonts by default.
Other minor improvements and fixes. Other minor improvements and fixes.
With many thanks to the contributors: @adamharrison, @takase1121, @Guldoman, With many thanks to the contributors: @adamharrison, @takase1121, @Guldoman, @redtide, @Timofffee, @boppyt, @Jan200101.
@redtide, @Timofffee, @boppyt, @Jan200101.
## [2.0.1] - 2021-08-28 ### 2.0.1
Fix a few bugs and we mandate the mod-version 2 for plugins. Fix a few bugs and we mandate the mod-version 2 for plugins.
This means that users should ensure they have up-to-date plugins for Lite XL 2.0. This means that users should ensure they have up-to-date plugins for Lite XL 2.0.
Here some details about the bug fixes: Here some details about the bug fixes:
- fix a bug that created a fatal error when using the command to change project - fix a bug that created a fatal error when using the command to change project folder or when closing all the active documents
folder or when closing all the active documents - add a limit to avoid scaling fonts too much and fix a related invalid memory access for very small fonts
- add a limit to avoid scaling fonts too much and fix a related invalid memory
access for very small fonts
- fix focus problem with NagView when switching project directory - fix focus problem with NagView when switching project directory
- fix error that prevented the verification of plugins versions - fix error that prevented the verification of plugins versions
- fix error on X11 that caused a bug window event on exit - fix error on X11 that caused a bug window event on exit
## [2.0] - 2021-08-16 ### 2.0
The 2.0 version of lite contains *breaking changes* to lite, in terms of how The 2.0 version of lite contains *breaking changes* to lite, in terms of how plugin settings are structured;
plugin settings are structured; any custom plugins may need to be adjusted any custom plugins may need to be adjusted accordingly (see note below about plugin namespacing).
accordingly (see note below about plugin namespacing).
Contains the following new features: Contains the following new features:
Full PCRE (regex) support for find and replace, as well as in language syntax Full PCRE (regex) support for find and replace, as well as in language syntax definitions. Can be accessed
definitions. Can be accessed programatically via the lua `regex` module. programatically via the lua `regex` module.
A full, finalized subprocess API, using libreproc. Subprocess can be started A full, finalized subprocess API, using libreproc. Subprocess can be started and interacted with using
and interacted with using `Process.new`. `Process.new`.
Support for multi-cursor editing. Cursors can be created by either ctrl+clicking Support for multi-cursor editing. Cursors can be created by either ctrl+clicking on the screen, or by using
on the screen, or by using the keyboard shortcuts ctrl+shift+up/down to create the keyboard shortcuts ctrl+shift+up/down to create an additional cursor on the previous/next line.
an additional cursor on the previous/next line.
All build systems other than meson removed. All build systems other than meson removed.
A more organized directory structure has been implemented; in particular a docs A more organized directory structure has been implemented; in particular a docs folder which contains C api
folder which contains C api documentation, and a resource folder which houses documentation, and a resource folder which houses all build resources.
all build resources.
Plugin config namespacing has been implemented. This means that instead of Plugin config namespacing has been implemented. This means that instead of using `config.myplugin.a`,
using `config.myplugin.a`, to read settings, and `config.myplugin = false` to to read settings, and `config.myplugin = false` to disable plugins, this has been changed to
disable plugins, this has been changed to `config.plugins.myplugin.a`, and `config.plugins.myplugin.a`, and `config.plugins.myplugin = false` repsectively. This may require changes to
`config.plugins.myplugin = false` respectively. This may require changes to
your user plugin, or to any custom plugins you have. your user plugin, or to any custom plugins you have.
A context menu on right click has been added. A context menu on right click has been added.
Changes to how we deal with indentation have been implemented; in particular, Changes to how we deal with indentation have been implemented; in particular, hitting home no longer brings you
hitting home no longer brings you to the start of a line, it'll bring you to to the start of a line, it'll bring you to the start of indentation, which is more in line with other editors.
the start of indentation, which is more in line with other editors.
Lineguide, and scale plugins moved into the core, and removed from Lineguide, and scale plugins moved into the core, and removed from `lite-plugins`. This may also require you to
`lite-plugins`. This may also require you to adjust your personal plugin adjust your personal plugin folder to remove these if they're present.
folder to remove these if they're present.
In addition, there have been many other small fixes and improvements, too In addition, there have been many other small fixes and improvements, too numerous to list here.
numerous to list here.
## [1.16.11] - 2021-05-28 ### 1.16.11
When opening directories with too many files lite-xl now keep displaying files When opening directories with too many files lite-xl now keep diplaying files and directories in the treeview.
and directories in the treeview. The application remains functional and the The application remains functional and the directories can be explored without using too much memory.
directories can be explored without using too much memory. In this operating In this operating mode the files of the project are not indexed so the command "Core: Find File" will act as the "Core: Open File" command.
mode the files of the project are not indexed so the command "Core: Find File" The "Project Search: Find" will work by searching all the files present in the project directory even if they are not indexed.
will act as the "Core: Open File" command.The "Project Search: Find" will work
by searching all the files present in the project directory even if they are
not indexed.
Implemented changing fonts per syntax group by @liquidev. Implemented changing fonts per syntax group by @liquidev.
@ -533,30 +131,30 @@ Fix bug with close button not working in borderless window mode.
Fix problem with normalization of filename for opened documents. Fix problem with normalization of filename for opened documents.
## [1.16.10] - 2021-05-22 ### 1.16.10
Improved syntax highlight system thanks to @liquidev and @adamharrison. Improved syntax highlight system thanks to @liquidev and @adamharrison.
Thanks to the new system we provide more a accurate syntax highlighting for Thanks to the new system we provide more a accurate syntax highlighting for Lua, C and C++.
Lua, C and C++. Other syntax improvements contributed by @vincens2005. Other syntax improvements contributed by @vincens2005.
Move to JetBrains Mono and Fira Sans fonts for code and UI respectively. Move to JetBrains Mono and Fira Sans fonts for code and UI respectively.
They are provided under the SIL Open Font License, Version 1.1. Thet are provided under the SIL Open Font License, Version 1.1.
See `doc/licenses.md` for license details. See `doc/licenses.md` for license details.
Fixed bug with fonts and rencache module. Under very specific situations the Fixed bug with fonts and rencache module.
application was crashing due to invalid memory access. Under very specific situations the application was crashing due to invalid memory access.
Add documentation for keymap binding, thanks to @Janis-Leuenberger. Add documentation for keymap binding, thanks to @Janis-Leuenberger.
Added a contributors page in `doc/contributors.md`. Added a contributors page in `doc/contributors.md`.
## [1.16.9] - 2021-05-06 ### 1.16.9
Fix a bug related to nested panes resizing. Fix a bug related to nested panes resizing.
Fix problem preventing creating a new file. Fix problem preventing creating a new file.
## [1.16.8] - 2021-05-06 ### 1.16.8
Fix application crash when using the command `core:restart`. Fix application crash when using the command `core:restart`.
@ -578,28 +176,27 @@ Both kind of tags can appear in new plugins in the form:
where the old tag needs to appear at the end for compatibility. where the old tag needs to appear at the end for compatibility.
## [1.16.7] - 2021-05-01 ### 1.16.7
Add support for retina displays on Mac OS X. Add support for retina displays on Mac OS X.
Fix a few problems related to file paths. Fix a few problems related to file paths.
## [1.16.6] - 2021-04-21 ### 1.16.6
Implement a system to check the compatibility of plugins by checking a release Implement a system to check the compatibility of plugins by checking a release tag.
tag. Plugins that don't have the release tag will not be loaded. Plugins that don't have the release tag will not be loaded.
Improve and extend the NagView with keyboard commands. Improve and extend the NagView with keyboard commands.
Special thanks to @takase1121 for the implementation and @liquidev for proposing Special thanks to @takase1121 for the implementation and @liquidev for proposing and
and discussing the enhancements. discussing the enhancements.
Add support to build on Mac OS X and create an application bundle. Add support to build on Mac OS X and create an application bundle.
Special thanks to @mathewmariani for his lite-macos fork, the Mac OS specific Special thanks to @mathewmariani for his lite-macos fork, the Mac OS specific
resources and his support. resources and his support.
Add hook function `DocView.on_text_change` so that plugin can accurately react Add hook function `DocView.on_text_change` so that plugin can accurately react on document changes.
on document changes. Thanks to @vincens2005 for the suggestion and testing the Thanks to @vincens2005 for the suggestion and testing the implementation.
implementation.
Enable borderless window mode using the `config.borderless` variable. Enable borderless window mode using the `config.borderless` variable.
If enable the system window's bar will be replaced by a title bar provided If enable the system window's bar will be replaced by a title bar provided
@ -617,14 +214,13 @@ commands `draw-whitespace:toggle`, `draw-whitespace:enable`,
Improve the NagView to accept keyboard commands and introduce dialog commands. Improve the NagView to accept keyboard commands and introduce dialog commands.
Add hook function `Doc:on_text_change` called on document changes, to be Add hook function `Doc:on_text_change` called on document changes, to be used by plugins.
used by plugins.
## [1.16.5] - 2021-03-20 ### 1.16.5
Hotfix for Github's issue https://github.com/franko/lite-xl/issues/122 Hotfix for Github's issue https://github.com/franko/lite-xl/issues/122
## [1.16.4] - 2021-03-20 ### 1.16.4
Add tooltips to show full file names from the tree-view. Add tooltips to show full file names from the tree-view.
@ -639,7 +235,7 @@ Made borders between tabs look cleaner.
Fix problem with files using hard tabs. Fix problem with files using hard tabs.
## [1.16.2] - 2021-03-05 ### 1.16.2
Implement close button for tabs. Implement close button for tabs.
@ -647,12 +243,12 @@ Make the command view list of suggestion scrollable to see all the items.
Improve update/resize behavior of treeview and toolbar. Improve update/resize behavior of treeview and toolbar.
## [1.16.1] - 2021-02-25 ### 1.16.1
Improve behavior of commands to move, delete and duplicate multiple lines: Improve behavior of commands to move, delete and duplicate multiple lines:
no longer include the last line if it does not contain any selection. no longer include the last line if it does not contain any selection.
Fix graphical artifacts when rendering some fonts like FiraSans. Fix graphical artefacts when rendering some fonts like FiraSans.
Introduce the `config.transitions` boolean variable. Introduce the `config.transitions` boolean variable.
When false the transitions will be disabled and changes will be done immediately. When false the transitions will be disabled and changes will be done immediately.
@ -661,7 +257,7 @@ Very useful for remote sessions where visual transitions doesn't work well.
Fix many small problems related to the new toolbar and the tooptips. Fix many small problems related to the new toolbar and the tooptips.
Fix problem with spacing in treeview when using monospace fonts. Fix problem with spacing in treeview when using monospace fonts.
## [1.16] - 2021-02-19 ### 1.16
Implement a toolbar shown in the bottom part of the tree-view. Implement a toolbar shown in the bottom part of the tree-view.
The toolbar is especially meant for new users to give an easy, visual, access The toolbar is especially meant for new users to give an easy, visual, access
@ -673,8 +269,8 @@ are actually resizable.
Add config mechanism to disable a plugin by setting Add config mechanism to disable a plugin by setting
`config.<plugin-name> = false`. `config.<plugin-name> = false`.
Improve the "detect indent" plugin to take into account the syntax and exclude Improve the "detect indent" plugin to take into account the syntax and exclude comments
comments for much accurate results. for much accurate results.
Add command `root:close-all` to close all the documents currently opened. Add command `root:close-all` to close all the documents currently opened.
@ -682,24 +278,21 @@ Show the full path filename of the active document in the window's title.
Fix problem with user's module reload not always enabled. Fix problem with user's module reload not always enabled.
## [1.15] - 2021-01-04 ### 1.15
**Project directories** **Project directories**
Extend your project by adding more directories using the command Extend your project by adding more directories using the command `core:add-directory`.
`core:add-directory`. To remove them use the corresponding command To remove them use the corresponding command `core:remove-directory`.
`core:remove-directory`.
**Workspaces** **Workspaces**
The workspace plugin from rxi/lite-plugins is now part of Lite XL. The workspace plugin from rxi/lite-plugins is now part of Lite XL.
In addition to the functionalities of the original plugin the extended version In addition to the functionalities of the original plugin the extended version will
will also remember the window size and position and the additional project also remember the window size and position and the additonal project directories.
directories. To not interfere with the project's files the workspace file is saved in the personal
Lite's configuration folder.
To not interfere with the project's files the workspace file is saved in the On unix-like systems it will be in: `$HOME/.config/lite-xl/ws`.
personal Lite's configuration folder. On unix-like systems it will be in:
`$HOME/.config/lite-xl/ws`.
**Scrolling the Tree View** **Scrolling the Tree View**
@ -711,11 +304,10 @@ As in the unix shell `~` is now used to identify the home directory.
**Files and Directories** **Files and Directories**
Add command to create a new empty directory within the project using the Add command to create a new empty directory within the project using the command
command `files:create-directory`. `files:create-directory`.
In addition a control-click on a project directory will prompt the user to create
In addition a control-click on a project directory will prompt the user to a new directory inside the directory pointed.
create a new directory inside the directory pointed.
**New welcome screen** **New welcome screen**
@ -723,56 +315,51 @@ Show 'Lite XL' instead of 'lite' and the version number.
**Various fixes and improvements** **Various fixes and improvements**
A few quirks previously with some of the new features have been fixed for a A few quirks previously with some of the new features have been fixed for a better user experience.
better user experience.
## [1.14] - 2020-12-13 ### 1.14
**Project Management** **Project Management**
Add a new command, Core: Change Project Folder, to change project directory by Add a new command, Core: Change Project Folder, to change project directory by staying on the same window.
staying on the same window. All the current opened documents will be closed. All the current opened documents will be closed.
The new command is associated with the keyboard combination ctrl+shit+c. The new command is associated with the keyboard combination ctrl+shit+c.
A similar command is also added, Core: Open Project Folder, with key binding A similar command is also added, Core: Open Project Folder, with key binding ctrl+shift+o.
ctrl+shift+o. It will open the chosen folder in a new window. It will open the chosen folder in a new window.
In addition Lite XL will now remember the recently used projects across In addition Lite XL will now remember the recently used projects across different sessions.
different sessions. When invoked without arguments it will now open the project When invoked without arguments it will now open the project more recently used.
more recently used. If a directory is specified it will behave like before and If a directory is specified it will behave like before and open the directory indicated as an argument.
open the directory indicated as an argument.
**Restart command** **Restart command**
A Core: Restart command is added to restart the editor without leaving the A Core: Restart command is added to restart the editor without leaving the current window.
current window. Very convenient when modifying the Lua code for the editor Very convenient when modifying the Lua code for the editor itself.
itself.
**User's setting auto-reload** **User's setting auto-reload**
When saving the user configuration, the user's module, the changes will be When saving the user configuration, the user's module, the changes will be automatically applied to the
automatically applied to the current instance. current instance.
**Bundle community provided colors schemes** **Bundle community provided colors schemes**
Included now in the release files the colors schemes from Included now in the release files the colors schemes from github.com/rxi/lite-colors.
github.com/rxi/lite-colors.
**Usability improvements** **Usability improvements**
Improve left and right scrolling of text to behave like other editors and Improve left and right scrolling of text to behave like other editors and improves text selection with mouse.
improves text selection with mouse.
**Fixes** **Fixes**
Correct font's rendering for full hinting mode when using subpixel antialiasing. Correct font's rendering for full hinting mode when using subpixel antialiasing.
## [1.13] - 2020-12-06 ### 1.13
**Rendering options for fonts** **Rendering options for fonts**
When loading fonts with the function renderer.font.load some rendering options When loading fonts with the function renderer.font.load some rendering options can
can be optionally specified: be optionally specified:
- antialiasing: grayscale or subpixel - antialiasing: grayscale or subpixel
- hinting: none, slight or full - hinting: none, slight or full
@ -781,39 +368,36 @@ See data/core/style.lua for the details about its utilisation.
The default remains antialiasing subpixel and hinting slight to reproduce the The default remains antialiasing subpixel and hinting slight to reproduce the
behavior of previous versions. behavior of previous versions.
The option grayscale with full hinting is specially interesting for crisp font The option grayscale with full hinting is specially interesting for crisp font rendering
rendering without color artifacts. without color artifacts.
**Unix-like install directories** **Unix-like install directories**
Use unix-like install directories for the executable and for the data directory. Use unix-like install directories for the executable and for the data directory.
The executable will be placed under $prefix/bin and the data folder will be The executable will be placed under $prefix/bin and the data folder will be
$prefix/share/lite-xl. $prefix/share/lite-xl.
The folder $prefix is not hard-coded in the binary but is determined at runtime The folder $prefix is not hard-coded in the binary but is determined at runtime
as the directory such as the executable is inside $prefix/bin. as the directory such as the executable is inside $prefix/bin.
If no such $prefix exist it will fall back to the old behavior and use the "data"
folder from the executable directory.
If no such $prefix exist it will fall back to the old behavior and use the In addtion to the `EXEDIR` global variable an additional variable is exposed, `DATADIR`,
"data" folder from the executable directory. to point to the data directory.
In addtion to the `EXEDIR` global variable an additional variable is exposed, The old behavior using the "data" directory can be still selected at compile time
`DATADIR`, to point to the data directory. using the "portable" option. The released Windows package will use the "data"
directory as before.
The old behavior using the "data" directory can be still selected at compile
time using the "portable" option. The released Windows package will use the
"data" directory as before.
**Configuration stored into the user's home directory** **Configuration stored into the user's home directory**
Now the Lite XL user's configuration will be stored in the user's home directory Now the Lite XL user's configuration will be stored in the user's home directory under
under .config/lite-xl". ".config/lite-xl".
The home directory is determined using the "HOME" environment variable except on Windows
The home directory is determined using the "HOME" environment variable except wher "USERPROFILE" is used instead.
on Windows wher "USERPROFILE" is used instead.
A new global variable `USERDIR` is exposed to point to the user's directory. A new global variable `USERDIR` is exposed to point to the user's directory.
## [1.11] - 2020-07-05 ### 1.11
- include changes from rxi's Lite 1.11 - include changes from rxi's Lite 1.11
- fix behavior of tab to indent multiple lines - fix behavior of tab to indent multiple lines
@ -821,36 +405,11 @@ A new global variable `USERDIR` is exposed to point to the user's directory.
- limit project scan to a maximum number of files to limit memory usage - limit project scan to a maximum number of files to limit memory usage
- list recently visited files when using "Find File" command - list recently visited files when using "Find File" command
## [1.08] - 2020-06-14 ### 1.08
- Subpixel font rendering, removed gamma correction - Subpixel font rendering, removed gamma correction
- Avoid using CPU when the editor is idle - Avoid using CPU when the editor is idle
## [1.06] - 2020-05-31 ### 1.06
- subpixel font rendering with gamma correction - subpixel font rendering with gamma correction
[2.1.0]: https://github.com/lite-xl/lite-xl/releases/tag/v2.1.0
[2.0.5]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.5
[2.0.4]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.4
[2.0.3]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.3
[2.0.2]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.2
[2.0.1]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.1
[2.0]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.0
[1.16.11]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.11
[1.16.10]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.10
[1.16.9]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.9
[1.16.8]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.8
[1.16.7]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.7
[1.16.6]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.6
[1.16.5]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.5
[1.16.4]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.4
[1.16.2]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.2-lite-xl
[1.16.1]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.1-lite-xl
[1.16]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.0-lite-xl
[1.15]: https://github.com/lite-xl/lite-xl/releases/tag/v1.15-lite-xl
[1.14]: https://github.com/lite-xl/lite-xl/releases/tag/v1.14-lite-xl
[1.13]: https://github.com/lite-xl/lite-xl/releases/tag/v1.13-lite-xl
[1.11]: https://github.com/lite-xl/lite-xl/releases/tag/v1.11-lite-xl
[1.08]: https://github.com/lite-xl/lite-xl/releases/tag/v1.08-subpixel
[1.06]: https://github.com/lite-xl/lite-xl/releases/tag/1.06-subpixel-rc1

View File

@ -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

View File

@ -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

View File

@ -6,48 +6,17 @@ command.map = {}
local always_true = function() return true end local always_true = function() return true end
---Used iternally by command.add, statusview, and contextmenu to generate a function command.add(predicate, map)
---function with a condition to evaluate returning the boolean result of this
---evaluation.
---
---If a string predicate is given it is treated as a require import that should
---return a valid object which is checked against the current active view,
---eg: "core.docview" will match any view that inherits from DocView. Appending
---a `!` at the end of the string means we want to match the given object
---from the import strcitly eg: "core.docview!" only DocView is matched.
---A function that returns a boolean can be used instead to perform a custom
---evaluation, setting to nil means always evaluates to true.
---
---@param predicate string | table | function
---@return function
function command.generate_predicate(predicate)
predicate = predicate or always_true predicate = predicate or always_true
local strict = false
if type(predicate) == "string" then if type(predicate) == "string" then
if predicate:match("!$") then
strict = true
predicate = predicate:gsub("!$", "")
end
predicate = require(predicate) predicate = require(predicate)
end end
if type(predicate) == "table" then if type(predicate) == "table" then
local class = predicate local class = predicate
if not strict then predicate = function() return core.active_view:is(class) end
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
end end
return predicate
end
function command.add(predicate, map)
predicate = command.generate_predicate(predicate)
for name, fn in pairs(map) do for name, fn in pairs(map) do
if command.map[name] then assert(not command.map[name], "command already exists: " .. name)
core.log_quiet("Replacing existing command \"%s\"", name)
end
command.map[name] = { predicate = predicate, perform = fn } command.map[name] = { predicate = predicate, perform = fn }
end end
end end
@ -64,12 +33,8 @@ end
function command.get_all_valid() function command.get_all_valid()
local res = {} local res = {}
local memoized_predicates = {}
for name, cmd in pairs(command.map) do for name, cmd in pairs(command.map) do
if memoized_predicates[cmd.predicate] == nil then if cmd.predicate() then
memoized_predicates[cmd.predicate] = cmd.predicate()
end
if memoized_predicates[cmd.predicate] then
table.insert(res, name) table.insert(res, name)
end end
end end
@ -82,16 +47,8 @@ end
local function perform(name, ...) local function perform(name, ...)
local cmd = command.map[name] local cmd = command.map[name]
if not cmd then return false end if cmd and cmd.predicate(...) then
local res = { cmd.predicate(...) } cmd.perform(...)
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
return true return true
end end
return false return false
@ -107,7 +64,7 @@ end
function command.add_defaults() function command.add_defaults()
local reg = { local reg = {
"core", "root", "command", "doc", "findreplace", "core", "root", "command", "doc", "findreplace",
"files", "drawwhitespace", "dialog", "log", "statusbar" "files", "drawwhitespace", "dialog"
} }
for _, name in ipairs(reg) do for _, name in ipairs(reg) do
require("core.commands." .. name) require("core.commands." .. name)

View File

@ -2,23 +2,23 @@ local core = require "core"
local command = require "core.command" local command = require "core.command"
command.add("core.commandview", { command.add("core.commandview", {
["command:submit"] = function(active_view) ["command:submit"] = function()
active_view:submit() core.active_view:submit()
end, end,
["command:complete"] = function(active_view) ["command:complete"] = function()
active_view:complete() core.active_view:complete()
end, end,
["command:escape"] = function(active_view) ["command:escape"] = function()
active_view:exit() core.active_view:exit()
end, end,
["command:select-previous"] = function(active_view) ["command:select-previous"] = function()
active_view:move_suggestion_idx(1) core.active_view:move_suggestion_idx(1)
end, end,
["command:select-next"] = function(active_view) ["command:select-next"] = function()
active_view:move_suggestion_idx(-1) core.active_view:move_suggestion_idx(-1)
end, end,
}) })

View File

@ -10,18 +10,8 @@ local restore_title_view = false
local function suggest_directory(text) local function suggest_directory(text)
text = common.home_expand(text) text = common.home_expand(text)
local basedir = common.dirname(core.project_dir) return common.home_encode_list((text == "" or text == common.home_expand(common.dirname(core.project_dir)))
return common.home_encode_list((basedir and text == basedir .. PATHSEP or text == "") and and core.recent_projects or common.dir_path_suggest(text))
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
end end
command.add(nil, { command.add(nil, {
@ -48,42 +38,36 @@ command.add(nil, {
end, end,
["core:reload-module"] = function() ["core:reload-module"] = function()
core.command_view:enter("Reload Module", { core.command_view:enter("Reload Module", function(text, item)
submit = function(text, item) local text = item and item.text or text
local text = item and item.text or text core.reload_module(text)
core.reload_module(text) core.log("Reloaded module %q", text)
core.log("Reloaded module %q", text) end, function(text)
end, local items = {}
suggest = function(text) for name in pairs(package.loaded) do
local items = {} table.insert(items, name)
for name in pairs(package.loaded) do
table.insert(items, name)
end
return common.fuzzy_match(items, text)
end end
}) return common.fuzzy_match(items, text)
end)
end, end,
["core:find-command"] = function() ["core:find-command"] = function()
local commands = command.get_all_valid() local commands = command.get_all_valid()
core.command_view:enter("Do Command", { core.command_view:enter("Do Command", function(text, item)
submit = function(text, item) if item then
if item then command.perform(item.command)
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
end 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, end,
["core:find-file"] = function() ["core:find-file"] = function()
@ -97,72 +81,56 @@ command.add(nil, {
table.insert(files, common.home_encode(path .. item.filename)) table.insert(files, common.home_encode(path .. item.filename))
end end
end end
core.command_view:enter("Open File From Project", { core.command_view:enter("Open File From Project", function(text, item)
submit = function(text, item) text = item and item.text or text
text = item and item.text or text core.root_view:open_doc(core.open_doc(common.home_expand(text)))
core.root_view:open_doc(core.open_doc(common.home_expand(text))) end, function(text)
end, return common.fuzzy_match_with_recents(files, core.visited_files, text)
suggest = function(text) end)
return common.fuzzy_match_with_recents(files, core.visited_files, text)
end
})
end, end,
["core:new-doc"] = function() ["core:new-doc"] = function()
core.root_view:open_doc(core.open_doc()) core.root_view:open_doc(core.open_doc())
end, 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() ["core:open-file"] = function()
local view = core.active_view local view = core.active_view
local text
if view.doc and view.doc.abs_filename then if view.doc and view.doc.abs_filename then
local dirname, filename = view.doc.abs_filename:match("(.*)[/\\](.+)$") local dirname, filename = view.doc.abs_filename:match("(.*)[/\\](.+)$")
if dirname then if dirname then
dirname = core.normalize_to_project_dir(dirname) 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
end end
core.command_view:enter("Open File", { core.command_view:enter("Open File", function(text)
text = text, local filename = system.absolute_path(common.home_expand(text))
submit = function(text) core.root_view:open_doc(core.open_doc(filename))
local filename = system.absolute_path(common.home_expand(text)) end, function (text)
core.root_view:open_doc(core.open_doc(filename)) return common.home_encode_list(common.path_suggest(common.home_expand(text)))
end, end, nil, function(text)
suggest = function (text) local filename = common.home_expand(text)
return common.home_encode_list(common.path_suggest(common.home_expand(text))) local path_stat, err = system.get_file_info(filename)
end, if err then
validate = function(text) if err:find("No such file", 1, true) then
local filename = common.home_expand(text) -- check if the containing directory exists
local path_stat, err = system.get_file_info(filename) local dirname = common.dirname(filename)
if err then local dir_stat = dirname and system.get_file_info(dirname)
if err:find("No such file", 1, true) then if not dirname or (dir_stat and dir_stat.type == 'dir') 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
return true return true
end 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, end,
["core:open-log"] = function() ["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()) node:add_view(LogView())
end, end,
@ -173,79 +141,62 @@ command.add(nil, {
end, end,
["core:open-project-module"] = function() ["core:open-project-module"] = function()
if not system.get_file_info(".lite_project.lua") then local filename = ".lite_project.lua"
core.try(core.write_init_project_module, ".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 end
local doc = core.open_doc(".lite_project.lua")
core.root_view:open_doc(doc)
doc:save()
end, end,
["core:change-project-folder"] = function() ["core:change-project-folder"] = function()
local dirname = common.dirname(core.project_dir) local dirname = common.dirname(core.project_dir)
local text
if dirname then if dirname then
text = common.home_encode(dirname) .. PATHSEP core.command_view:set_text(common.home_encode(dirname))
end end
core.command_view:enter("Change Project Folder", { core.command_view:enter("Change Project Folder", function(text, item)
text = text, text = system.absolute_path(common.home_expand(item and item.text or text))
submit = function(text) if text == core.project_dir then return end
local path = common.home_expand(text) local path_stat = system.get_file_info(text)
local abs_path = check_directory_path(path) if not path_stat or path_stat.type ~= 'dir' then
if not abs_path then core.error("Cannot open folder %q", text)
core.error("Cannot open directory %q", path) return
return end
end core.confirm_close_docs(core.docs, core.open_folder_project, text)
if abs_path == core.project_dir then return end end, suggest_directory)
core.confirm_close_docs(core.docs, function(dirpath)
core.open_folder_project(dirpath)
end, abs_path)
end,
suggest = suggest_directory
})
end, end,
["core:open-project-folder"] = function() ["core:open-project-folder"] = function()
local dirname = common.dirname(core.project_dir) local dirname = common.dirname(core.project_dir)
local text
if dirname then if dirname then
text = common.home_encode(dirname) .. PATHSEP core.command_view:set_text(common.home_encode(dirname))
end end
core.command_view:enter("Open Project", { core.command_view:enter("Open Project", function(text, item)
text = text, text = common.home_expand(item and item.text or text)
submit = function(text) local path_stat = system.get_file_info(text)
local path = common.home_expand(text) if not path_stat or path_stat.type ~= 'dir' then
local abs_path = check_directory_path(path) core.error("Cannot open folder %q", text)
if not abs_path then return
core.error("Cannot open directory %q", path) end
return system.exec(string.format("%q %q", EXEFILE, text))
end end, suggest_directory)
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
})
end, end,
["core:add-directory"] = function() ["core:add-directory"] = function()
core.command_view:enter("Add Directory", { core.command_view:enter("Add Directory", function(text)
submit = function(text) text = common.home_expand(text)
text = common.home_expand(text) local path_stat, err = system.get_file_info(text)
local path_stat, err = system.get_file_info(text) if not path_stat then
if not path_stat then core.error("cannot open %q: %s", text, err)
core.error("cannot open %q: %s", text, err) return
return elseif path_stat.type ~= 'dir' then
elseif path_stat.type ~= 'dir' then core.error("%q is not a directory", text)
core.error("%q is not a directory", text) return
return end
end core.add_project_directory(system.absolute_path(text))
core.add_project_directory(system.absolute_path(text)) end, suggest_directory)
end,
suggest = suggest_directory
})
end, end,
["core:remove-directory"] = function() ["core:remove-directory"] = function()
@ -254,17 +205,14 @@ command.add(nil, {
for i = n, 2, -1 do for i = n, 2, -1 do
dir_list[n - i + 1] = core.project_directories[i].name dir_list[n - i + 1] = core.project_directories[i].name
end end
core.command_view:enter("Remove Directory", { core.command_view:enter("Remove Directory", function(text, item)
submit = function(text, item) text = common.home_expand(item and item.text or text)
text = common.home_expand(item and item.text or text) if not core.remove_project_directory(text) then
if not core.remove_project_directory(text) then core.error("No directory %q to be removed", text)
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))
end end
}) end, function(text)
text = common.home_expand(text)
return common.home_encode_list(common.dir_list_suggest(text, dir_list))
end)
end, end,
}) })

View File

@ -3,25 +3,30 @@ local command = require "core.command"
local common = require "core.common" local common = require "core.common"
command.add("core.nagview", { 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 local hover = v.hovered_item or 1
v:change_hovered(hover == 1 and #v.options or hover - 1) v:change_hovered(hover == 1 and #v.options or hover - 1)
end, end,
["dialog:next-entry"] = function(v) ["dialog:next-entry"] = function()
local v = core.active_view
local hover = v.hovered_item or 1 local hover = v.hovered_item or 1
v:change_hovered(hover == #v.options and 1 or hover + 1) v:change_hovered(hover == #v.options and 1 or hover + 1)
end, end,
["dialog:select-yes"] = function(v) ["dialog:select-yes"] = function()
local v = core.active_view
if v ~= core.nag_view then return end if v ~= core.nag_view then return end
v:change_hovered(common.find_index(v.options, "default_yes")) v:change_hovered(common.find_index(v.options, "default_yes"))
command.perform "dialog:select" command.perform "dialog:select"
end, end,
["dialog:select-no"] = function(v) ["dialog:select-no"] = function()
local v = core.active_view
if v ~= core.nag_view then return end if v ~= core.nag_view then return end
v:change_hovered(common.find_index(v.options, "default_no")) v:change_hovered(common.find_index(v.options, "default_no"))
command.perform "dialog:select" command.perform "dialog:select"
end, end,
["dialog:select"] = function(v) ["dialog:select"] = function()
local v = core.active_view
if v.hovered_item then if v.hovered_item then
v.on_selected(v.options[v.hovered_item]) v.on_selected(v.options[v.hovered_item])
v:next() v:next()

View File

@ -47,34 +47,19 @@ end
local function cut_or_copy(delete) local function cut_or_copy(delete)
local full_text = "" local full_text = ""
local text = ""
core.cursor_clipboard = {}
core.cursor_clipboard_whole_line = {}
for idx, line1, col1, line2, col2 in doc():get_selections() do for idx, line1, col1, line2, col2 in doc():get_selections() do
if line1 ~= line2 or col1 ~= col2 then if line1 ~= line2 or col1 ~= col2 then
text = doc():get_text(line1, col1, line2, col2) local text = doc():get_text(line1, col1, line2, col2)
full_text = full_text == "" and text or (full_text .. " " .. text)
core.cursor_clipboard_whole_line[idx] = false
if delete then if delete then
doc():delete_to_cursor(idx, 0) doc():delete_to_cursor(idx, 0)
end end
else -- Cut/copy whole line full_text = full_text == "" and text or (full_text .. "\n" .. text)
text = doc().lines[line1] doc().cursor_clipboard[idx] = text
full_text = full_text == "" and text or (full_text .. text) else
core.cursor_clipboard_whole_line[idx] = true doc().cursor_clipboard[idx] = ""
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
end
end end
core.cursor_clipboard[idx] = text
end end
core.cursor_clipboard["full"] = full_text doc().cursor_clipboard["full"] = full_text
system.set_clipboard(full_text) system.set_clipboard(full_text)
end end
@ -89,100 +74,17 @@ local function split_cursor(direction)
core.blink_reset() core.blink_reset()
end end
local function set_cursor(dv, x, y, snap_type) local function set_cursor(x, y, snap_type)
local line, col = dv:resolve_screen_position(x, y) local line, col = dv():resolve_screen_position(x, y)
dv.doc:set_selection(line, col, line, col) doc():set_selection(line, col, line, col)
if snap_type == "word" or snap_type == "lines" then if snap_type == "word" or snap_type == "lines" then
command.perform("doc:select-" .. snap_type) command.perform("doc:select-" .. snap_type)
end end
dv.mouse_selecting = { line, col, snap_type } dv().mouse_selecting = { line, col, snap_type }
core.blink_reset() core.blink_reset()
end end
local function line_comment(comment, line1, col1, line2, col2) local selection_commands = {
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 commands = {
["doc:select-none"] = function(dv)
local line, col = dv.doc:get_selection()
dv.doc:set_selection(line, col)
end,
["doc:cut"] = function() ["doc:cut"] = function()
cut_or_copy(true) cut_or_copy(true)
end, end,
@ -191,231 +93,219 @@ local commands = {
cut_or_copy(false) cut_or_copy(false)
end, end,
["doc:undo"] = function(dv) ["doc:select-none"] = function()
dv.doc:undo() local line, col = doc():get_selection()
doc():set_selection(line, col)
end
}
local commands = {
["doc:undo"] = function()
doc():undo()
end, end,
["doc:redo"] = function(dv) ["doc:redo"] = function()
dv.doc:redo() doc():redo()
end, end,
["doc:paste"] = function(dv) ["doc:paste"] = function()
local clipboard = system.get_clipboard() local clipboard = system.get_clipboard()
-- If the clipboard has changed since our last look, use that instead -- If the clipboard has changed since our last look, use that instead
local external_paste = core.cursor_clipboard["full"] ~= clipboard if doc().cursor_clipboard["full"] ~= clipboard then
if external_paste then doc().cursor_clipboard = {}
core.cursor_clipboard = {}
core.cursor_clipboard_whole_line = {}
end end
local value, whole_line for idx, line1, col1, line2, col2 in doc():get_selections() do
for idx, line1, col1, line2, col2 in dv.doc:get_selections() do local value = doc().cursor_clipboard[idx] or clipboard
if #core.cursor_clipboard_whole_line == (#dv.doc.selections/4) then doc():text_input(value:gsub("\r", ""), idx)
value = core.cursor_clipboard[idx]
whole_line = core.cursor_clipboard_whole_line[idx] == true
else
value = clipboard
whole_line = not external_paste and clipboard:find("\n") ~= nil
end
if whole_line then
dv.doc:insert(line1, 1, value:gsub("\r", ""))
if col1 == 1 then
dv.doc:move_to_cursor(idx, #value)
end
else
dv.doc:text_input(value:gsub("\r", ""), idx)
end
end end
end, end,
["doc:newline"] = function(dv) ["doc:newline"] = function()
for idx, line, col in dv.doc:get_selections(false, true) do for idx, line, col in doc():get_selections(false, true) do
local indent = dv.doc.lines[line]:match("^[\t ]*") local indent = doc().lines[line]:match("^[\t ]*")
if col <= #indent then if col <= #indent then
indent = indent:sub(#indent + 2 - col) indent = indent:sub(#indent + 2 - col)
end end
-- Remove current line if it contains only whitespace doc():text_input("\n" .. indent, idx)
if dv.doc.lines[line]:match("^%s+$") then end
dv.doc:remove(line, 1, line, math.huge) 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 end
dv.doc:text_input("\n" .. indent, idx) doc():delete_to_cursor(idx, translate.next_char)
end end
end, end,
["doc:newline-below"] = function(dv) ["doc:backspace"] = function()
for idx, line in dv.doc:get_selections(false, true) do local _, indent_size = doc():get_indent_info()
local indent = dv.doc.lines[line]:match("^[\t ]*") for idx, line1, col1, line2, col2 in doc():get_selections() do
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() 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() do
if line1 == line2 and col1 == col2 then 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 if #text >= indent_size and text:find("^ *$") then
dv.doc:delete_to_cursor(idx, 0, -indent_size) doc():delete_to_cursor(idx, 0, -indent_size)
return return
end end
end end
dv.doc:delete_to_cursor(idx, translate.previous_char) doc():delete_to_cursor(idx, translate.previous_char)
end end
end, end,
["doc:select-all"] = function(dv) ["doc:select-all"] = function()
dv.doc:set_selection(1, 1, math.huge, math.huge) 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]
end, end,
["doc:select-lines"] = function(dv) ["doc:select-lines"] = function()
for idx, line1, _, line2 in dv.doc:get_selections(true) do for idx, line1, _, line2 in doc():get_selections(true) do
append_line_if_last_line(line2) append_line_if_last_line(line2)
dv.doc:set_selections(idx, line1, 1, line2 + 1, 1) doc():set_selections(idx, line1, 1, line2 + 1, 1)
end end
end, end,
["doc:select-word"] = function(dv) ["doc:select-word"] = function()
for idx, line1, col1 in dv.doc:get_selections(true) do for idx, line1, col1 in doc():get_selections(true) do
local line1, col1 = translate.start_of_word(dv.doc, line1, col1) local line1, col1 = translate.start_of_word(doc(), line1, col1)
local line2, col2 = translate.end_of_word(dv.doc, line1, col1) local line2, col2 = translate.end_of_word(doc(), line1, col1)
dv.doc:set_selections(idx, line2, col2, line1, col1) doc():set_selections(idx, line2, col2, line1, col1)
end end
end, end,
["doc:join-lines"] = function(dv) ["doc:join-lines"] = function()
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true) do for idx, line1, col1, line2, col2 in doc():get_selections(true) do
if line1 == line2 then line2 = line2 + 1 end 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) text = text:gsub("(.-)\n[\t ]*", function(x)
return x:find("^%s*$") and x or x .. " " return x:find("^%s*$") and x or x .. " "
end) end)
dv.doc:insert(line1, 1, text) doc():insert(line1, 1, text)
dv.doc:remove(line1, #text + 1, line2, math.huge) doc():remove(line1, #text + 1, line2, math.huge)
if line1 ~= line2 or col1 ~= col2 then 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 end
end, end,
["doc:indent"] = function(dv) ["doc:indent"] = function()
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do 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 if l1 then
dv.doc:set_selections(idx, l1, c1, l2, c2) doc():set_selections(idx, l1, c1, l2, c2)
end end
end end
end, end,
["doc:unindent"] = function(dv) ["doc:unindent"] = function()
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do 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 if l1 then
dv.doc:set_selections(idx, l1, c1, l2, c2) doc():set_selections(idx, l1, c1, l2, c2)
end end
end end
end, end,
["doc:duplicate-lines"] = function(dv) ["doc:duplicate-lines"] = function()
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2) append_line_if_last_line(line2)
local text = doc():get_text(line1, 1, line2 + 1, 1) 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 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
end, end,
["doc:delete-lines"] = function(dv) ["doc:delete-lines"] = function()
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2) append_line_if_last_line(line2)
dv.doc:remove(line1, 1, line2 + 1, 1) doc():remove(line1, 1, line2 + 1, 1)
dv.doc:set_selections(idx, line1, col1) doc():set_selections(idx, line1, col1)
end end
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 for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2) append_line_if_last_line(line2)
if line1 > 1 then if line1 > 1 then
local text = doc().lines[line1 - 1] local text = doc().lines[line1 - 1]
dv.doc:insert(line2 + 1, 1, text) doc():insert(line2 + 1, 1, text)
dv.doc:remove(line1 - 1, 1, line1, 1) doc():remove(line1 - 1, 1, line1, 1)
dv.doc:set_selections(idx, line1 - 1, col1, line2 - 1, col2) doc():set_selections(idx, line1 - 1, col1, line2 - 1, col2)
end end
end 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 for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2 + 1) append_line_if_last_line(line2 + 1)
if line2 < #dv.doc.lines then if line2 < #doc().lines then
local text = dv.doc.lines[line2 + 1] local text = doc().lines[line2 + 1]
dv.doc:remove(line2 + 1, 1, line2 + 2, 1) doc():remove(line2 + 1, 1, line2 + 2, 1)
dv.doc:insert(line1, 1, text) doc():insert(line1, 1, text)
dv.doc:set_selections(idx, line1 + 1, col1, line2 + 1, col2) doc():set_selections(idx, line1 + 1, col1, line2 + 1, col2)
end end
end end
end, end,
["doc:toggle-block-comments"] = function(dv) ["doc:toggle-line-comments"] = function()
local comment = dv.doc.syntax.block_comment local comment = doc().syntax.comment
if not comment then if not comment then return end
if dv.doc.syntax.comment then local indentation = doc():get_indent_string()
command.perform "doc:toggle-line-comments" 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 end
return for line = line1, line2 do
end local text = doc().lines[line]
local s = text:find("%S")
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do if uncomment then
-- if nothing is selected, toggle the whole line local cs, ce = text:find(comment_text, s, true)
if line1 == line2 and col1 == col2 then if ce then
col1 = 1 doc():remove(line, cs, line, ce + 1)
col2 = #dv.doc.lines[line2] end
end elseif s then
dv.doc:set_selections(idx, block_comment(comment, line1, col1, line2, col2)) doc():insert(line, start_offset, comment_text)
end end
end,
["doc:toggle-line-comments"] = function(dv)
local comment = dv.doc.syntax.comment or dv.doc.syntax.block_comment
if comment then
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
dv.doc:set_selections(idx, line_comment(comment, line1, col1, line2, col2))
end end
end end
end, end,
["doc:upper-case"] = function(dv) ["doc:upper-case"] = function()
dv.doc:replace(string.uupper) doc():replace(string.upper)
end, end,
["doc:lower-case"] = function(dv) ["doc:lower-case"] = function()
dv.doc:replace(string.ulower) doc():replace(string.lower)
end, end,
["doc:go-to-line"] = function(dv) ["doc:go-to-line"] = function()
local dv = dv()
local items local items
local function init_items() local function init_items()
if items then return end if items then return end
@ -427,195 +317,165 @@ local commands = {
end end
end end
core.command_view:enter("Go To Line", { core.command_view:enter("Go To Line", function(text, item)
submit = function(text, item) local line = item and item.line or tonumber(text)
local line = item and item.line or tonumber(text) if not line then
if not line then core.error("Invalid line number or unmatched string")
core.error("Invalid line number or unmatched string") return
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
end 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, end,
["doc:toggle-line-ending"] = function(dv) ["doc:toggle-line-ending"] = function()
dv.doc.crlf = not dv.doc.crlf doc().crlf = not doc().crlf
end, end,
["doc:save-as"] = function(dv) ["doc:save-as"] = function()
local last_doc = core.last_active_view and core.last_active_view.doc local last_doc = core.last_active_view and core.last_active_view.doc
local text if doc().filename then
if dv.doc.filename then core.command_view:set_text(doc().filename)
text = dv.doc.filename
elseif last_doc and last_doc.filename then elseif last_doc and last_doc.filename then
local dirname, filename = core.last_active_view.doc.abs_filename:match("(.*)[/\\](.+)$") 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 end
core.command_view:enter("Save As", { core.command_view:enter("Save As", function(filename)
text = text, save(common.home_expand(filename))
submit = function(filename) end, function (text)
save(common.home_expand(filename)) return common.home_encode_list(common.path_suggest(common.home_expand(text)))
end, end)
suggest = function (text)
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
end
})
end, end,
["doc:save"] = function(dv) ["doc:save"] = function()
if dv.doc.filename then if doc().filename then
save() save()
else else
command.perform("doc:save-as") command.perform("doc:save-as")
end end
end, end,
["doc:reload"] = function(dv) ["file:rename"] = function()
dv.doc:reload() local old_filename = doc().filename
end,
["file:rename"] = function(dv)
local old_filename = dv.doc.filename
if not old_filename then if not old_filename then
core.error("Cannot rename unsaved doc") core.error("Cannot rename unsaved doc")
return return
end end
core.command_view:enter("Rename", { core.command_view:set_text(old_filename)
text = old_filename, core.command_view:enter("Rename", function(filename)
submit = function(filename) save(common.home_expand(filename))
save(common.home_expand(filename)) core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename) if filename ~= old_filename then
if filename ~= old_filename then os.remove(old_filename)
os.remove(old_filename)
end
end,
suggest = function (text)
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
end end
}) end, function (text)
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
end)
end, end,
["file:delete"] = function(dv) ["file:delete"] = function()
local filename = dv.doc.abs_filename local filename = doc().abs_filename
if not filename then if not filename then
core.error("Cannot remove unsaved doc") core.error("Cannot remove unsaved doc")
return return
end 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) 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 end
os.remove(filename) os.remove(filename)
core.log("Removed \"%s\"", filename) core.log("Removed \"%s\"", filename)
end, 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 line1, col1 = select(3, doc():get_selection())
local line2, col2 = dv:resolve_screen_position(x, y) local line2, col2 = dv():resolve_screen_position(x, y)
dv.mouse_selecting = { line1, col1, nil } dv().mouse_selecting = { line1, col1, nil }
dv.doc:set_selection(line2, col2, line1, col1) doc():set_selection(line2, col2, line1, col1)
end,
["doc:set-cursor"] = function(x, y)
set_cursor(x, y, "set")
end,
["doc:set-cursor-word"] = function(x, y)
set_cursor(x, y, "word")
end,
["doc:set-cursor-line"] = function(x, y, clicks)
set_cursor(x, y, "lines")
end,
["doc:split-cursor"] = function(x, y, clicks)
local line, col = dv():resolve_screen_position(x, y)
doc():add_selection(line, col, line, col)
end, end,
["doc:create-cursor-previous-line"] = function(dv) ["doc:create-cursor-previous-line"] = function()
split_cursor(-1) split_cursor(-1)
dv.doc:merge_cursors() doc():merge_cursors()
end, end,
["doc:create-cursor-next-line"] = function(dv) ["doc:create-cursor-next-line"] = function()
split_cursor(1) split_cursor(1)
dv.doc:merge_cursors() doc():merge_cursors()
end end
} }
command.add(function(x, y)
if x == nil or y == nil or not core.active_view:is(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 = { local translations = {
["previous-char"] = translate, ["previous-char"] = translate.previous_char,
["next-char"] = translate, ["next-char"] = translate.next_char,
["previous-word-start"] = translate, ["previous-word-start"] = translate.previous_word_start,
["next-word-end"] = translate, ["next-word-end"] = translate.next_word_end,
["previous-block-start"] = translate, ["previous-block-start"] = translate.previous_block_start,
["next-block-end"] = translate, ["next-block-end"] = translate.next_block_end,
["start-of-doc"] = translate, ["start-of-doc"] = translate.start_of_doc,
["end-of-doc"] = translate, ["end-of-doc"] = translate.end_of_doc,
["start-of-line"] = translate, ["start-of-line"] = translate.start_of_line,
["end-of-line"] = translate, ["end-of-line"] = translate.end_of_line,
["start-of-word"] = translate, ["start-of-word"] = translate.start_of_word,
["start-of-indentation"] = translate, ["start-of-indentation"] = translate.start_of_indentation,
["end-of-word"] = translate, ["end-of-word"] = translate.end_of_word,
["previous-line"] = DocView.translate, ["previous-line"] = DocView.translate.previous_line,
["next-line"] = DocView.translate, ["next-line"] = DocView.translate.next_line,
["previous-page"] = DocView.translate, ["previous-page"] = DocView.translate.previous_page,
["next-page"] = DocView.translate, ["next-page"] = DocView.translate.next_page,
} }
for name, obj in pairs(translations) do for name, fn in pairs(translations) do
commands["doc:move-to-" .. name] = function(dv) dv.doc:move_to(obj[name:gsub("-", "_")], dv) end commands["doc:move-to-" .. name] = function() doc():move_to(fn, dv()) end
commands["doc:select-to-" .. name] = function(dv) dv.doc:select_to(obj[name:gsub("-", "_")], dv) end commands["doc:select-to-" .. name] = function() doc():select_to(fn, dv()) end
commands["doc:delete-to-" .. name] = function(dv) dv.doc:delete_to(obj[name:gsub("-", "_")], dv) end commands["doc:delete-to-" .. name] = function() doc():delete_to(fn, dv()) end
end end
commands["doc:move-to-previous-char"] = function(dv) commands["doc:move-to-previous-char"] = function()
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true) do for idx, line1, col1, line2, col2 in doc():get_selections(true) do
if line1 ~= line2 or col1 ~= col2 then if line1 ~= line2 or col1 ~= col2 then
dv.doc:set_selections(idx, line1, col1) doc():set_selections(idx, line1, col1)
else
dv.doc:move_to_cursor(idx, translate.previous_char)
end end
end end
doc():move_to(translate.previous_char)
end end
commands["doc:move-to-next-char"] = function(dv) commands["doc:move-to-next-char"] = function()
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true) do for idx, line1, col1, line2, col2 in doc():get_selections(true) do
if line1 ~= line2 or col1 ~= col2 then if line1 ~= line2 or col1 ~= col2 then
dv.doc:set_selections(idx, line2, col2) doc():set_selections(idx, line2, col2)
else
dv.doc:move_to_cursor(idx, translate.next_char)
end end
end end
doc():move_to(translate.next_char)
end end
command.add("core.docview", commands) 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)

View File

@ -4,13 +4,11 @@ local common = require "core.common"
command.add(nil, { command.add(nil, {
["files:create-directory"] = function() ["files:create-directory"] = function()
core.command_view:enter("New directory name", { core.command_view:enter("New directory name", function(text)
submit = function(text) local success, err, path = common.mkdirp(text)
local success, err, path = common.mkdirp(text) if not success then
if not success then core.error("cannot create directory %q: %s", path, err)
core.error("cannot create directory %q: %s", path, err)
end
end end
}) end)
end, end,
}) })

View File

@ -46,91 +46,70 @@ end
local function insert_unique(t, v) local function insert_unique(t, v)
local n = #t local n = #t
for i = 1, n do for i = 1, n do
if t[i] == v then if t[i] == v then return end
table.remove(t, i)
break
end
end end
table.insert(t, 1, v) t[n + 1] = v
end end
local function find(label, search_fn) local function find(label, search_fn)
last_view, last_sel = core.active_view, last_view, last_sel = core.active_view,
{ core.active_view.doc:get_selection() } { 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 found_expression = false
core.command_view:set_text(text, true)
core.status_view:show_tooltip(get_find_tooltip()) core.status_view:show_tooltip(get_find_tooltip())
core.command_view:enter(label, { core.command_view:set_hidden_suggestions()
text = text, core.command_view:enter(label, function(text, item)
select_text = true, insert_unique(core.previous_find, text)
show_suggestions = false, core.status_view:remove_tooltip()
submit = function(text, item) if found_expression then
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)
last_fn, last_text = search_fn, text last_fn, last_text = search_fn, text
return core.previous_find else
end, core.error("Couldn't find %q", text)
cancel = function(explicit) last_view.doc:set_selection(table.unpack(last_sel))
core.status_view:remove_tooltip() last_view:scroll_to_make_visible(table.unpack(last_sel))
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
}) 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 end
local function replace(kind, default, fn) local function replace(kind, default, fn)
core.status_view:show_tooltip(get_find_tooltip()) core.command_view:set_text(default, true)
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)
local s = string.format("Replace %s %q With", kind, old) core.status_view:show_tooltip(get_find_tooltip())
core.command_view:enter(s, { core.command_view:set_hidden_suggestions()
text = old, core.command_view:enter("Find To Replace " .. kind, function(old)
select_text = true, insert_unique(core.previous_find, old)
show_suggestions = false, core.command_view:set_text(old, true)
submit = function(new)
core.status_view:remove_tooltip() local s = string.format("Replace %s %q With", kind, old)
insert_unique(core.previous_replace, new) core.command_view:set_hidden_suggestions()
local results = doc():replace(function(text) core.command_view:enter(s, function(new)
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:remove_tooltip() 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 end
local function has_selection() local function has_selection()
@ -200,7 +179,7 @@ command.add(has_unique_selection, {
["find-replace:select-add-all"] = function() select_add_next(true) end ["find-replace:select-add-all"] = function() select_add_next(true) end
}) })
command.add("core.docview!", { command.add("core.docview", {
["find-replace:find"] = function() ["find-replace:find"] = function()
find("Find Text", function(doc, line, col, text, case_sensitive, find_regex, find_reverse) 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 } local opt = { wrap = true, no_case = not case_sensitive, regex = find_regex, reverse = find_reverse }

View File

@ -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
})

View File

@ -7,11 +7,13 @@ local config = require "core.config"
local t = { 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) node:close_active_view(core.root_view.root_node)
end, 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 if node and (not node:is_empty() or not node.is_primary_node) then
node:close_active_view(core.root_view.root_node) node:close_active_view(core.root_view.root_node)
else 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 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) core.confirm_close_docs(docs, core.root_view.close_all_docviews, core.root_view, true)
end, 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) local idx = node:get_view_idx(core.active_view)
idx = idx - 1 idx = idx - 1
if idx < 1 then idx = #node.views end if idx < 1 then idx = #node.views end
node:set_active_view(node.views[idx]) node:set_active_view(node.views[idx])
end, 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) local idx = node:get_view_idx(core.active_view)
idx = idx + 1 idx = idx + 1
if idx > #node.views then idx = 1 end if idx > #node.views then idx = 1 end
node:set_active_view(node.views[idx]) node:set_active_view(node.views[idx])
end, 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) local idx = node:get_view_idx(core.active_view)
if idx > 1 then if idx > 1 then
table.remove(node.views, idx) table.remove(node.views, idx)
@ -51,21 +56,24 @@ local t = {
end end
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) local idx = node:get_view_idx(core.active_view)
if idx < #node.views then if idx < #node.views then
table.remove(node.views, idx) table.remove(node.views, idx)
table.insert(node.views, idx + 1, core.active_view) table.insert(node.views, idx + 1, core.active_view)
end end
end, end,
["root:shrink"] = function(node) ["root:shrink"] = function()
local node = core.root_view:get_active_node()
local parent = node:get_parent_node(core.root_view.root_node) local parent = node:get_parent_node(core.root_view.root_node)
local n = (parent.a == node) and -0.1 or 0.1 local n = (parent.a == node) and -0.1 or 0.1
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9) parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
end, end,
["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 parent = node:get_parent_node(core.root_view.root_node)
local n = (parent.a == node) and 0.1 or -0.1 local n = (parent.a == node) and 0.1 or -0.1
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9) parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
@ -74,7 +82,8 @@ local t = {
for i = 1, 9 do 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] local view = node.views[i]
if view then if view then
node:set_active_view(view) node:set_active_view(view)
@ -84,7 +93,8 @@ end
for _, dir in ipairs { "left", "right", "up", "down" } do 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 local av = node.active_view
node:split(dir) node:split(dir)
if av:is(DocView) then if av:is(DocView) then
@ -92,7 +102,8 @@ for _, dir in ipairs { "left", "right", "up", "down" } do
end end
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 local x, y
if dir == "left" or dir == "right" then if dir == "left" or dir == "right" then
y = node.position.y + node.size.y / 2 y = node.position.y + node.size.y / 2
@ -112,7 +123,7 @@ end
command.add(function() command.add(function()
local node = core.root_view:get_active_node() local node = core.root_view:get_active_node()
local sx, sy = node:get_locked_size() local sx, sy = node:get_locked_size()
return not sx and not sy, node return not sx and not sy
end, t) end, t)
command.add(nil, { command.add(nil, {

View File

@ -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,
})

View File

@ -6,16 +6,13 @@ local DocView = require "core.docview"
local View = require "core.view" local View = require "core.view"
---@class core.commandview.input : core.doc
---@field super core.doc
local SingleLineDoc = Doc:extend() local SingleLineDoc = Doc:extend()
function SingleLineDoc:insert(line, col, text) function SingleLineDoc:insert(line, col, text)
SingleLineDoc.super.insert(self, line, col, text:gsub("\n", "")) SingleLineDoc.super.insert(self, line, col, text:gsub("\n", ""))
end end
---@class core.commandview : core.docview
---@field super core.docview
local CommandView = DocView:extend() local CommandView = DocView:extend()
CommandView.context = "application" CommandView.context = "application"
@ -24,26 +21,11 @@ local max_suggestions = 10
local noop = function() end 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 = { local default_state = {
submit = noop, submit = noop,
suggest = noop, suggest = noop,
cancel = noop, cancel = noop,
validate = function() return true end, validate = function() return true end
text = "",
select_text = false,
show_suggestions = true,
typeahead = true,
wrap = true,
} }
@ -52,8 +34,8 @@ function CommandView:new()
self.suggestion_idx = 1 self.suggestion_idx = 1
self.suggestions = {} self.suggestions = {}
self.suggestions_height = 0 self.suggestions_height = 0
self.show_suggestions = true
self.last_change_id = 0 self.last_change_id = 0
self.last_text = ""
self.gutter_width = 0 self.gutter_width = 0
self.gutter_text_brightness = 0 self.gutter_text_brightness = 0
self.selection_offset = 0 self.selection_offset = 0
@ -64,10 +46,8 @@ function CommandView:new()
end end
---@deprecated
function CommandView:set_hidden_suggestions() function CommandView:set_hidden_suggestions()
core.warn("Using deprecated function CommandView:set_hidden_suggestions") self.show_suggestions = false
self.state.show_suggestions = false
end end
@ -76,8 +56,8 @@ function CommandView:get_name()
end end
function CommandView:get_line_screen_position(line, col) function CommandView:get_line_screen_position()
local x = CommandView.super.get_line_screen_position(self, 1, col) local x = CommandView.super.get_line_screen_position(self, 1)
local _, y = self:get_content_offset() local _, y = self:get_content_offset()
local lh = self:get_line_height() local lh = self:get_line_height()
return x, y + (self.size.y - lh) / 2 return x, y + (self.size.y - lh) / 2
@ -100,7 +80,6 @@ end
function CommandView:set_text(text, select) function CommandView:set_text(text, select)
self.last_text = text
self.doc:remove(1, 1, math.huge, math.huge) self.doc:remove(1, 1, math.huge, math.huge)
self.doc:text_input(text) self.doc:text_input(text)
if select then if select then
@ -110,18 +89,9 @@ end
function CommandView:move_suggestion_idx(dir) function CommandView:move_suggestion_idx(dir)
local function overflow_suggestion_idx(n, count) if self.show_suggestions then
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
if self.state.show_suggestions then
local n = self.suggestion_idx + dir 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:complete()
self.last_change_id = self.doc:get_change_id() self.last_change_id = self.doc:get_change_id()
else else
@ -132,7 +102,7 @@ function CommandView:move_suggestion_idx(dir)
if n == 0 and self.save_suggestion then if n == 0 and self.save_suggestion then
self:set_text(self.save_suggestion) self:set_text(self.save_suggestion)
else else
self.suggestion_idx = overflow_suggestion_idx(n, #self.suggestions) self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
self:complete() self:complete()
end end
else else
@ -162,53 +132,21 @@ function CommandView:submit()
end end
end end
---@param label string
---@varargs any function CommandView:enter(text, submit, suggest, cancel, validate)
---@overload fun(label:string, options: core.commandview.state)
function CommandView:enter(label, ...)
if self.state ~= default_state then if self.state ~= default_state then
return return
end end
local options = select(1, ...) self.state = {
submit = submit or noop,
if type(options) ~= "table" then suggest = suggest or noop,
core.warn("Using CommandView:enter in a deprecated way") cancel = cancel or noop,
local submit, suggest, cancel, validate = ... validate = validate or function() return true end
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
core.set_active_view(self) core.set_active_view(self)
self:update_suggestions() self:update_suggestions()
self.gutter_text_brightness = 100 self.gutter_text_brightness = 100
self.label = label .. ": " self.label = text .. ": "
end end
@ -221,13 +159,8 @@ function CommandView:exit(submitted, inexplicit)
self.doc:reset() self.doc:reset()
self.suggestions = {} self.suggestions = {}
if not submitted then cancel(not inexplicit) end if not submitted then cancel(not inexplicit) end
self.show_suggestions = true
self.save_suggestion = nil self.save_suggestion = nil
self.last_text = ""
end
function CommandView:get_line_height()
return math.floor(self:get_font():get_height() * 1.2)
end end
@ -265,45 +198,35 @@ function CommandView:update()
-- update suggestions if text has changed -- update suggestions if text has changed
if self.last_change_id ~= self.doc:get_change_id() then if self.last_change_id ~= self.doc:get_change_id() then
self:update_suggestions() 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() self.last_change_id = self.doc:get_change_id()
end end
-- update gutter text color brightness -- 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 -- update gutter width
local dest = self:get_font():get_width(self.label) + style.padding.x local dest = self:get_font():get_width(self.label) + style.padding.x
if self.size.y <= 0 then if self.size.y <= 0 then
self.gutter_width = dest self.gutter_width = dest
else else
self:move_towards("gutter_width", dest, nil, "commandview") self:move_towards("gutter_width", dest)
end end
-- update suggestions box height -- update suggestions box height
local lh = self:get_suggestion_line_height() local lh = self:get_suggestion_line_height()
local dest = self.state.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0 local dest = self.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0
self:move_towards("suggestions_height", dest, nil, "commandview") self:move_towards("suggestions_height", dest)
-- update suggestion cursor offset -- update suggestion cursor offset
local dest = math.min(self.suggestion_idx, max_suggestions) * self:get_suggestion_line_height() local dest = math.min(self.suggestion_idx, max_suggestions) * self:get_suggestion_line_height()
self:move_towards("selection_offset", dest, nil, "commandview") self:move_towards("selection_offset", dest)
-- update size based on whether this is the active_view -- update size based on whether this is the active_view
local dest = 0 local dest = 0
if self == core.active_view then if self == core.active_view then
dest = style.font:get_height() + style.padding.y * 2 dest = style.font:get_height() + style.padding.y * 2
end end
self:move_towards(self.size, "y", dest, nil, "commandview") self:move_towards(self.size, "y", dest)
end end
@ -320,7 +243,6 @@ function CommandView:draw_line_gutter(idx, x, y)
x = x + style.padding.x x = x + style.padding.x
renderer.draw_text(self:get_font(), self.label, x, y + yoffset, color) renderer.draw_text(self:get_font(), self.label, x, y + yoffset, color)
core.pop_clip_rect() core.pop_clip_rect()
return self:get_line_height()
end end
@ -340,20 +262,20 @@ local function draw_suggestions_box(self)
end end
-- draw suggestion text -- draw suggestion text
local offset = math.max(self.suggestion_idx - max_suggestions, 0) local suggestion_offset = math.max(self.suggestion_idx - max_suggestions, 0)
local last = math.min(offset + max_suggestions, #self.suggestions)
core.push_clip_rect(rx, ry, rw, rh) core.push_clip_rect(rx, ry, rw, rh)
local first = 1 + offset local i = 1 + suggestion_offset
for i=first, last do while i <= #self.suggestions do
local item = self.suggestions[i] local item = self.suggestions[i]
local color = (i == self.suggestion_idx) and style.accent or style.text local color = (i == self.suggestion_idx) and style.accent or style.text
local y = self.position.y - (i - offset) * 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) common.draw_text(self:get_font(), color, item.text, nil, x, y, 0, lh)
if item.info then if item.info then
local w = self.size.x - x - style.padding.x local w = self.size.x - x - style.padding.x
common.draw_text(self:get_font(), style.dim, item.info, "right", x, y, w, lh) common.draw_text(self:get_font(), style.dim, item.info, "right", x, y, w, lh)
end end
i = i + 1
end end
core.pop_clip_rect() core.pop_clip_rect()
end end
@ -361,7 +283,7 @@ end
function CommandView:draw() function CommandView:draw()
CommandView.super.draw(self) CommandView.super.draw(self)
if self.state.show_suggestions then if self.show_suggestions then
core.root_view:defer_draw(draw_suggestions_box, self) core.root_view:defer_draw(draw_suggestions_box, self)
end end
end end

View File

@ -1,5 +1,6 @@
local common = {} local common = {}
function common.is_utf8_cont(s, offset) function common.is_utf8_cont(s, offset)
local byte = s:byte(offset or 1) local byte = s:byte(offset or 1)
return byte >= 0x80 and byte < 0xc0 return byte >= 0x80 and byte < 0xc0
@ -16,21 +17,6 @@ function common.clamp(n, lo, hi)
end end
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
function common.round(n) function common.round(n)
return n >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5) return n >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5)
end end
@ -56,7 +42,7 @@ end
function common.distance(x1, y1, x2, y2) 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 end
@ -146,29 +132,9 @@ function common.fuzzy_match_with_recents(haystack, recents, needle)
end end
function common.path_suggest(text, root) function common.path_suggest(text)
if root and root:sub(-1) ~= PATHSEP then local path, name = text:match("^(.-)([^/\\]*)$")
root = root .. PATHSEP local files = system.list_dir(path == "" and "." or path) or {}
end
local path, name = text:match("^(.-)([^:/\\]*)$")
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 {}
local res = {} local res = {}
for _, file in ipairs(files) do for _, file in ipairs(files) do
file = path .. file file = path .. file
@ -177,19 +143,6 @@ function common.path_suggest(text, root)
if info.type == "dir" then if info.type == "dir" then
file = file .. PATHSEP file = file .. PATHSEP
end 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 if file:lower():find(text:lower(), nil, true) == 1 then
table.insert(res, file) table.insert(res, file)
end end
@ -200,7 +153,7 @@ end
function common.dir_path_suggest(text) function common.dir_path_suggest(text)
local path, name = text:match("^(.-)([^:/\\]*)$") local path, name = text:match("^(.-)([^/\\]*)$")
local files = system.list_dir(path == "" and "." or path) or {} local files = system.list_dir(path == "" and "." or path) or {}
local res = {} local res = {}
for _, file in ipairs(files) do for _, file in ipairs(files) do
@ -215,7 +168,7 @@ end
function common.dir_list_suggest(text, dir_list) function common.dir_list_suggest(text, dir_list)
local path, name = text:match("^(.-)([^:/\\]*)$") local path, name = text:match("^(.-)([^/\\]*)$")
local res = {} local res = {}
for _, dir_path in ipairs(dir_list) do for _, dir_path in ipairs(dir_list) do
if dir_path:lower():find(text:lower(), nil, true) == 1 then if dir_path:lower():find(text:lower(), nil, true) == 1 then
@ -261,58 +214,19 @@ function common.bench(name, fn, ...)
end end
local function serialize(val, pretty, indent_str, escape, sort, limit, level) function common.serialize(val)
local space = pretty and " " or ""
local indent = pretty and string.rep(indent_str, level) or ""
local newline = pretty and "\n" or ""
if type(val) == "string" then if type(val) == "string" then
local out = string.format("%q", val) return 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 type(val) == "table" then elseif type(val) == "table" then
-- early exit
if level >= limit then return tostring(val) end
local next_indent = pretty and (indent .. indent_str) or ""
local t = {} local t = {}
for k, v in pairs(val) do for k, v in pairs(val) do
table.insert(t, table.insert(t, "[" .. common.serialize(k) .. "]=" .. common.serialize(v))
next_indent .. "[" ..
serialize(k, pretty, indent_str, escape, sort, limit, level + 1) ..
"]" .. space .. "=" .. space .. serialize(v, pretty, indent_str, escape, sort, limit, level + 1))
end end
if #t == 0 then return "{}" end return "{" .. table.concat(t, ",") .. "}"
if sort then table.sort(t) end
return "{" .. newline .. table.concat(t, "," .. newline) .. newline .. indent .. "}"
end end
return tostring(val) return tostring(val)
end end
-- Serialize `val` into a parsable string.
-- Available options
-- * pretty: enable pretty printing
-- * indent_str: indent to use (" " by default)
-- * escape: use normal escape characters instead of the ones used by string.format("%q", ...)
-- * sort: sort the keys inside tables
-- * limit: limit how deep to serialize
-- * initial_indent: the initial indentation level
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
function common.basename(path) function common.basename(path)
-- a path should never end by / or \ except if it is '/' (unix root) or -- a path should never end by / or \ except if it is '/' (unix root) or
@ -323,7 +237,7 @@ end
-- can return nil if there is no directory part in the path -- can return nil if there is no directory part in the path
function common.dirname(path) function common.dirname(path)
return path:match("(.+)[:\\/][^\\/]+$") return path:match("(.+)[\\/][^\\/]+$")
end end
@ -350,9 +264,6 @@ end
function common.home_expand(text) function common.home_expand(text)
if text == nil then
return HOME
end
return HOME and text:gsub("^~", HOME) or text return HOME and text:gsub("^~", HOME) or text
end end
@ -374,11 +285,11 @@ end
-- absolute path without . or .. elements. -- absolute path without . or .. elements.
-- This function exists because on Windows the drive letter returned -- This function exists because on Windows the drive letter returned
-- by system.absolute_path is sometimes with a lower case and sometimes -- by system.absolute_path is sometimes with a lower case and sometimes
-- with an upper case so we normalize to upper case. -- with an upper case to we normalize to upper case.
function common.normalize_volume(filename) function common.normalize_volume(filename)
if not filename then return end if not filename then return end
if PATHSEP == '\\' then if PATHSEP == '\\' then
local drive, rem = filename:match('^([a-zA-Z]:\\)(.-)'..PATHSEP..'?$') local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
if drive then if drive then
return drive:upper() .. rem return drive:upper() .. rem
end end
@ -427,11 +338,6 @@ function common.normalize_path(filename)
end end
function common.is_absolute_path(path)
return path:sub(1, 1) == PATHSEP or path:match("^(%a):\\") or path:match('^(%w*):')
end
function common.path_belongs_to(filename, path) function common.path_belongs_to(filename, path)
return string.find(filename, path .. PATHSEP, 1, true) == 1 return string.find(filename, path .. PATHSEP, 1, true) == 1
end end
@ -531,6 +437,4 @@ function common.rm(path, recursively)
return true return true
end end
return common return common

View File

@ -1,37 +1,26 @@
local config = {} local config = {}
config.fps = 60 config.fps = 60
config.max_log_items = 800 config.max_log_items = 80
config.message_timeout = 5 config.message_timeout = 5
config.mouse_wheel_scroll = 50 * SCALE config.mouse_wheel_scroll = 50 * SCALE
config.animate_drag_scroll = false
config.scroll_past_end = true config.scroll_past_end = true
config.file_size_limit = 10 config.file_size_limit = 10
config.ignore_files = { "^%." } config.ignore_files = "^%."
config.symbol_pattern = "[%a_][%w_]*" config.symbol_pattern = "[%a_][%w_]*"
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-" config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
config.undo_merge_timeout = 0.3 config.undo_merge_timeout = 0.3
config.max_undos = 10000 config.max_undos = 10000
config.max_tabs = 8 config.max_tabs = 8
config.always_show_tabs = true config.always_show_tabs = true
-- Possible values: false, true, "no_selection"
config.highlight_current_line = true config.highlight_current_line = true
config.line_height = 1.2 config.line_height = 1.2
config.indent_size = 2 config.indent_size = 2
config.tab_type = "soft" config.tab_type = "soft"
config.line_limit = 80 config.line_limit = 80
config.max_symbols = 4000
config.max_project_files = 2000 config.max_project_files = 2000
config.transitions = true 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.animation_rate = 1.0
config.blink_period = 0.8 config.blink_period = 0.8
config.disable_blink = false config.disable_blink = false
@ -40,20 +29,12 @@ config.borderless = false
config.tab_close_button = true config.tab_close_button = true
config.max_clicks = 3 config.max_clicks = 3
-- set as true to be able to test non supported plugins -- Disable plugin loading setting to false the config entry
config.skip_plugins_version = false -- of the same name.
config.plugins = {} config.plugins = {}
-- Allow you to set plugin configs even if we haven't seen the plugin before.
setmetatable(config.plugins, {
__index = function(t, k)
if rawget(t, k) == nil then rawset(t, k, {}) end
return rawget(t, k)
end
})
-- Disable these plugins by default.
config.plugins.trimwhitespace = false config.plugins.trimwhitespace = false
config.plugins.lineguide = false
config.plugins.drawwhitespace = false config.plugins.drawwhitespace = false
return config return config

View File

@ -5,13 +5,11 @@ local config = require "core.config"
local keymap = require "core.keymap" local keymap = require "core.keymap"
local style = require "core.style" local style = require "core.style"
local Object = require "core.object" local Object = require "core.object"
local View = require "core.view"
local border_width = 1 local border_width = 1
local divider_width = 1 local divider_width = 1
local DIVIDER = {} local DIVIDER = {}
---@class core.contextmenu : core.object
local ContextMenu = Object:extend() local ContextMenu = Object:extend()
ContextMenu.DIVIDER = DIVIDER ContextMenu.DIVIDER = DIVIDER
@ -22,7 +20,6 @@ function ContextMenu:new()
self.selected = -1 self.selected = -1
self.height = 0 self.height = 0
self.position = { x = 0, y = 0 } self.position = { x = 0, y = 0 }
self.current_scale = SCALE
end end
local function get_item_size(item) local function get_item_size(item)
@ -40,10 +37,18 @@ local function get_item_size(item)
return lw, lh return lw, lh
end end
local function update_items_size(items, update_binding) function ContextMenu:register(predicate, items)
local width, height = 0, 0 if type(predicate) == "string" then
for _, item in ipairs(items) do predicate = require(predicate)
if update_binding and item ~= DIVIDER then 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) item.info = keymap.get_binding(item.command)
end end
local lw, lh = get_item_size(item) local lw, lh = get_item_size(item)
@ -52,11 +57,6 @@ local function update_items_size(items, update_binding)
end end
width = width + style.padding.x * 2 width = width + style.padding.x * 2
items.width, items.height = width, height items.width, items.height = width, height
end
function ContextMenu:register(predicate, items)
predicate = command.generate_predicate(predicate)
update_items_size(items, true)
table.insert(self.itemset, { predicate = predicate, items = items }) table.insert(self.itemset, { predicate = predicate, items = items })
end end
@ -91,7 +91,6 @@ function ContextMenu:show(x, y)
self.position.x, self.position.y = x, y self.position.x, self.position.y = x, y
self.show_context_menu = true self.show_context_menu = true
core.request_cursor("arrow")
return true return true
end end
return false return false
@ -102,7 +101,6 @@ function ContextMenu:hide()
self.items = nil self.items = nil
self.selected = -1 self.selected = -1
self.height = 0 self.height = 0
core.request_cursor(core.active_view.cursor)
end end
function ContextMenu:each_item() function ContextMenu:each_item()
@ -128,6 +126,9 @@ function ContextMenu:on_mouse_moved(px, py)
break break
end end
end end
if self.selected >= 0 then
core.request_cursor("arrow")
end
return true return true
end end
@ -139,73 +140,53 @@ function ContextMenu:on_selected(item)
end end
end end
local function change_value(value, change) function ContextMenu:on_mouse_pressed(button, x, y, clicks)
return value + change local selected = (self.items or {})[self.selected]
end
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
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
function ContextMenu:get_item_selected()
return (self.items or {})[self.selected]
end
function ContextMenu:call_selected_item()
local selected = self:get_item_selected()
self:hide()
if selected then
self:on_selected(selected)
end
end
function ContextMenu:on_mouse_pressed(button, px, py, clicks)
local caught = false local caught = false
if self.show_context_menu then self:hide()
if button == "left" then if button == "left" then
local selected = self:get_item_selected() if selected then
if selected then self:on_selected(selected)
self:on_selected(selected) caught = true
end
end
self:hide()
caught = true
else
if button == "right" then
caught = self:show(px, py)
end end
end end
if button == "right" then
caught = self:show(x, y)
end
return caught return caught
end end
ContextMenu.move_towards = View.move_towards -- 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() function ContextMenu:update()
if self.show_context_menu then if self.show_context_menu then
self:move_towards("height", self.items.height, nil, "contextmenu") self:move_towards("height", self.items.height)
end end
end end
function ContextMenu:draw() function ContextMenu:draw()
if not self.show_context_menu then return end 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) core.root_view:defer_draw(self.draw_context_menu, self)
end end

View File

@ -1,232 +0,0 @@
local common = require "core.common"
local config = require "core.config"
local dirwatch = {}
function dirwatch:__index(idx)
local value = rawget(self, idx)
if value ~= nil then return value end
return dirwatch[idx]
end
function dirwatch.new()
local t = {
scanned = {},
watched = {},
reverse_watched = {},
monitor = dirmonitor.new(),
windows_watch_top = nil,
windows_watch_count = 0
}
setmetatable(t, dirwatch)
return t
end
function dirwatch:scan(directory, bool)
if bool == false then return self:unwatch(directory) end
self.scanned[directory] = system.get_file_info(directory).modified
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).
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 PLATFORM == "Windows" then
if info.type ~= "dir" then return self:scan(directory) end
if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then
-- Get the highest level of directory that is common to this directory, and the original.
local target = directory
while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do
target = common.dirname(target)
end
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.windows_watch_top = target
end
end
self.windows_watch_count = self.windows_watch_count + 1
self.watched[directory] = true
else
local value = self.monitor:watch(directory)
-- If for whatever reason, we can't watch this directory, revert back to scanning.
-- Don't bother trying to find out why, for now.
if value and value < 0 then
return self:scan(directory)
end
self.watched[directory] = value
self.reverse_watched[value] = directory
end
end
end
-- this should be an absolute path
function dirwatch:unwatch(directory)
if self.watched[directory] then
if PLATFORM ~= "Windows" then
self.monitor:unwatch(self.watched[directory])
self.reverse_watched[directory] = nil
else
self.windows_watch_count = self.windows_watch_count - 1
if self.windows_watch_count == 0 then
self.windows_watch_top = nil
self.monitor:unwatch(directory)
end
end
self.watched[directory] = nil
elseif self.scanned[directory] then
self.scanned[directory] = nil
end
end
-- designed to be run inside a coroutine.
function dirwatch:check(change_callback, scan_time, wait_time)
local had_change = false
self.monitor:check(function(id)
had_change = true
if PLATFORM == "Windows" then
change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id))
elseif self.reverse_watched[id] then
change_callback(self.reverse_watched[id])
end
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
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)
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 a.filename < b.filename
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(root .. PATHSEP .. 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 will 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, t, entries_count, recurse_pred)
local t0 = system.get_time()
local t_elapsed = system.get_time() - t0
local dirs, files = {}, {}
local ignore_compiled = compile_ignore_files()
local all = system.list_dir(root .. PATHSEP .. path)
if not all then return nil end
for _, file in ipairs(all or {}) do
local info = get_project_file_info(root, (path ~= "" and (path .. PATHSEP) or "") .. file, ignore_compiled)
if info then
table.insert(info.type == "dir" and dirs or files, info)
entries_count = entries_count + 1
end
end
local recurse_complete = true
table.sort(dirs, compare_file)
for _, f in ipairs(dirs) do
table.insert(t, f)
if recurse_pred(dir, f.filename, entries_count, t_elapsed) then
local _, complete, n = dirwatch.get_directory_files(dir, root, f.filename, t, entries_count, recurse_pred)
recurse_complete = recurse_complete and complete
if n ~= nil then
entries_count = n
end
else
recurse_complete = false
end
end
table.sort(files, compare_file)
for _, f in ipairs(files) do
table.insert(t, f)
end
return t, recurse_complete, entries_count
end
return dirwatch

View File

@ -22,21 +22,13 @@ function Highlighter:new(doc)
else else
local max = math.min(self.first_invalid_line + 40, self.max_wanted_line) local max = math.min(self.first_invalid_line + 40, self.max_wanted_line)
local retokenized_from
for i = self.first_invalid_line, max do for i = self.first_invalid_line, max do
local state = (i > 1) and self.lines[i - 1].state local state = (i > 1) and self.lines[i - 1].state
local line = self.lines[i] local line = self.lines[i]
if not (line and line.init_state == state and line.text == self.doc.lines[i]) then if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
retokenized_from = retokenized_from or i
self.lines[i] = self:tokenize_line(i, state) self.lines[i] = self:tokenize_line(i, state)
elseif retokenized_from then
self:update_notify(retokenized_from, i - retokenized_from - 1)
retokenized_from = nil
end end
end end
if retokenized_from then
self:update_notify(retokenized_from, max - retokenized_from)
end
self.first_invalid_line = max + 1 self.first_invalid_line = max + 1
core.redraw = true core.redraw = true
@ -79,10 +71,6 @@ function Highlighter:remove_notify(line, n)
common.splice(self.lines, line, n) common.splice(self.lines, line, n)
end 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) function Highlighter:tokenize_line(idx, state)
local res = {} local res = {}
@ -99,7 +87,6 @@ function Highlighter:get_line(idx)
local prev = self.lines[idx - 1] local prev = self.lines[idx - 1]
line = self:tokenize_line(idx, prev and prev.state) line = self:tokenize_line(idx, prev and prev.state)
self.lines[idx] = line self.lines[idx] = line
self:update_notify(idx, 0)
end end
self.max_wanted_line = math.max(self.max_wanted_line, idx) self.max_wanted_line = math.max(self.max_wanted_line, idx)
return line return line

View File

@ -5,7 +5,7 @@ local syntax = require "core.syntax"
local config = require "core.config" local config = require "core.config"
local common = require "core.common" local common = require "core.common"
---@class core.doc : core.object
local Doc = Object:extend() local Doc = Object:extend()
@ -33,6 +33,7 @@ end
function Doc:reset() function Doc:reset()
self.lines = { "\n" } self.lines = { "\n" }
self.selections = { 1, 1, 1, 1 } self.selections = { 1, 1, 1, 1 }
self.cursor_clipboard = {}
self.undo_stack = { idx = 1 } self.undo_stack = { idx = 1 }
self.redo_stack = { idx = 1 } self.redo_stack = { idx = 1 }
self.clean_change_id = 1 self.clean_change_id = 1
@ -54,7 +55,6 @@ end
function Doc:set_filename(filename, abs_filename) function Doc:set_filename(filename, abs_filename)
self.filename = filename self.filename = filename
self.abs_filename = abs_filename self.abs_filename = abs_filename
self:reset_syntax()
end end
@ -80,23 +80,11 @@ function Doc:load(filename)
end 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) function Doc:save(filename, abs_filename)
if not filename then if not filename then
assert(self.filename, "no filename set to default to") assert(self.filename, "no filename set to default to")
filename = self.filename filename = self.filename
abs_filename = self.abs_filename abs_filename = self.abs_filename
else
assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path")
end end
local fp = assert( io.open(filename, "wb") ) local fp = assert( io.open(filename, "wb") )
for _, line in ipairs(self.lines) do for _, line in ipairs(self.lines) do
@ -106,6 +94,7 @@ function Doc:save(filename, abs_filename)
fp:close() fp:close()
self:set_filename(filename, abs_filename) self:set_filename(filename, abs_filename)
self.new_file = false self.new_file = false
self:reset_syntax()
self:clean() self:clean()
end end
@ -146,8 +135,8 @@ end
-- curors can never swap positions; only merge or split, or change their position in cursor -- curors can never swap positions; only merge or split, or change their position in cursor
-- order. -- order.
function Doc:get_selection(sort) function Doc:get_selection(sort)
local idx, line1, col1, line2, col2, swap = self:get_selections(sort)({ self.selections, sort }, 0) local idx, line1, col1, line2, col2 = self:get_selections(sort)({ self.selections, sort }, 0)
return line1, col1, line2, col2, swap return line1, col1, line2, col2, sort
end end
function Doc:get_selection_text(limit) function Doc:get_selection_text(limit)
@ -183,9 +172,9 @@ end
local function sort_positions(line1, col1, line2, col2) local function sort_positions(line1, col1, line2, col2)
if line1 > line2 or line1 == line2 and col1 > col2 then if line1 > line2 or line1 == line2 and col1 > col2 then
return line2, col2, line1, col1, true return line2, col2, line1, col1
end end
return line1, col1, line2, col2, false return line1, col1, line2, col2
end end
function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm) function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm)
@ -208,14 +197,8 @@ function Doc:add_selection(line1, col1, line2, col2, swap)
self:set_selections(target, line1, col1, line2, col2, swap, 0) self:set_selections(target, line1, col1, line2, col2, swap, 0)
end end
function Doc:remove_selection(idx)
common.splice(self.selections, (idx - 1) * 4 + 1, 4)
end
function Doc:set_selection(line1, col1, line2, col2, swap) 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:set_selections(1, line1, col1, line2, col2, swap)
end end
@ -225,10 +208,12 @@ function Doc:merge_cursors(idx)
if self.selections[i] == self.selections[j] and if self.selections[i] == self.selections[j] and
self.selections[i+1] == self.selections[j+1] then self.selections[i+1] == self.selections[j+1] then
common.splice(self.selections, i, 4) common.splice(self.selections, i, 4)
common.splice(self.cursor_clipboard, i, 1)
break break
end end
end end
end end
if #self.selections <= 4 then self.cursor_clipboard = {} end
end end
local function selection_iterator(invariant, idx) local function selection_iterator(invariant, idx)
@ -371,7 +356,7 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
-- splice lines into line array -- splice lines into line array
common.splice(self.lines, line, 1, lines) common.splice(self.lines, line, 1, lines)
-- keep cursors where they should be -- keep cursors where they should be
for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
if cline1 < line then break end if cline1 < line then break end
@ -403,7 +388,7 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
-- splice line into line array -- splice line into line array
common.splice(self.lines, line1, line2 - line1 + 1, { before .. after }) common.splice(self.lines, line1, line2 - line1 + 1, { before .. after })
-- move all cursors back if they share a line with the removed text -- 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 for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
if cline1 < line2 then break end if cline1 < line2 then break end
@ -458,7 +443,7 @@ end
function Doc:replace_cursor(idx, line1, col1, line2, col2, fn) function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
local old_text = self:get_text(line1, col1, line2, col2) 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 if old_text ~= new_text then
self:insert(line2, col2, new_text) self:insert(line2, col2, new_text)
self:remove(line1, col1, line2, col2) self:remove(line1, col1, line2, col2)
@ -467,22 +452,22 @@ function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
self:set_selections(idx, line1, col1, line2, col2) self:set_selections(idx, line1, col1, line2, col2)
end end
end end
return res return n
end end
function Doc:replace(fn) 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 for idx, line1, col1, line2, col2 in self:get_selections(true) do
if line1 ~= line2 or col1 ~= col2 then if line1 ~= line2 or col1 ~= col2 then
results[idx] = self:replace_cursor(idx, line1, col1, line2, col2, fn) n = n + self:replace_cursor(idx, line1, col1, line2, col2, fn)
has_selection = true has_selection = true
end end
end end
if not has_selection then if not has_selection then
self:set_selection(table.unpack(self.selections)) 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 end
return results return n
end end
@ -563,12 +548,10 @@ function Doc:indent_text(unindent, line1, col1, line2, col2)
if unindent or has_selection or in_beginning_whitespace then if unindent or has_selection or in_beginning_whitespace then
local l1d, l2d = #self.lines[line1], #self.lines[line2] local l1d, l2d = #self.lines[line1], #self.lines[line2]
for line = line1, line2 do 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)
local e, rnded = self:get_line_indent(self.lines[line], unindent) self:remove(line, 1, line, (e or 0) + 1)
self:remove(line, 1, line, (e or 0) + 1) self:insert(line, 1,
self:insert(line, 1, unindent and rnded:sub(1, #rnded - #text) or rnded .. text)
unindent and rnded:sub(1, #rnded - #text) or rnded .. text)
end
end end
l1d, l2d = #self.lines[line1] - l1d, #self.lines[line2] - l2d l1d, l2d = #self.lines[line1] - l1d, #self.lines[line2] - l2d
if (unindent or in_beginning_whitespace) and not has_selection then if (unindent or in_beginning_whitespace) and not has_selection then

View File

@ -6,8 +6,7 @@ local keymap = require "core.keymap"
local translate = require "core.doc.translate" local translate = require "core.doc.translate"
local View = require "core.view" local View = require "core.view"
---@class core.docview : core.view
---@field super core.view
local DocView = View:extend() local DocView = View:extend()
DocView.context = "session" DocView.context = "session"
@ -30,9 +29,6 @@ DocView.translate = {
end, end,
["next_page"] = function(doc, line, col, dv) ["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() local min, max = dv:get_visible_line_range()
return line + (max - min), 1 return line + (max - min), 1
end, end,
@ -66,22 +62,19 @@ end
function DocView:try_close(do_close) function DocView:try_close(do_close)
if self.doc:is_dirty() if self.doc:is_dirty()
and #core.get_views_referencing_doc(self.doc) == 1 then and #core.get_views_referencing_doc(self.doc) == 1 then
core.command_view:enter("Unsaved Changes; Confirm Close", { core.command_view:enter("Unsaved Changes; Confirm Close", function(_, item)
submit = function(_, item) if item.text:match("^[cC]") then
if item.text:match("^[cC]") then do_close()
do_close() elseif item.text:match("^[sS]") then
elseif item.text:match("^[sS]") then self.doc:save()
self.doc:save() do_close()
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
end 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 else
do_close() do_close()
end end
@ -128,18 +121,14 @@ function DocView:get_gutter_width()
end end
function DocView:get_line_screen_position(line, col) function DocView:get_line_screen_position(idx)
local x, y = self:get_content_offset() local x, y = self:get_content_offset()
local lh = self:get_line_height() local lh = self:get_line_height()
local gw = self:get_gutter_width() local gw = self:get_gutter_width()
y = y + (line-1) * lh + style.padding.y return x + gw, y + (idx-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
end end
function DocView:get_line_text_y_offset() function DocView:get_line_text_y_offset()
local lh = self:get_line_height() local lh = self:get_line_height()
local th = self:get_font():get_height() local th = self:get_font():get_height()
@ -209,9 +198,8 @@ end
function DocView:scroll_to_line(line, ignore_if_visible, instant) function DocView:scroll_to_line(line, ignore_if_visible, instant)
local min, max = self:get_visible_line_range() local min, max = self:get_visible_line_range()
if not (ignore_if_visible and line > min and line < max) then if not (ignore_if_visible and line > min and line < max) then
local x, y = self:get_line_screen_position(line) local lh = self:get_line_height()
local ox, oy = self:get_content_offset() self.scroll.to.y = math.max(0, lh * (line - 1) - self.size.y / 2)
self.scroll.to.y = math.max(0, y - oy - self.size.y / 2)
if instant then if instant then
self.scroll.y = self.scroll.to.y self.scroll.y = self.scroll.to.y
end end
@ -220,10 +208,10 @@ end
function DocView:scroll_to_make_visible(line, col) function DocView:scroll_to_make_visible(line, col)
local ox, oy = self:get_content_offset() local min = self:get_line_height() * (line - 1)
local _, ly = self:get_line_screen_position(line, col) local max = self:get_line_height() * (line + 2) - self.size.y
local lh = self:get_line_height() self.scroll.to.y = math.min(self.scroll.to.y, min)
self.scroll.to.y = common.clamp(self.scroll.to.y, ly - oy - self.size.y + lh * 2, ly - oy - lh) self.scroll.to.y = math.max(self.scroll.to.y, max)
local gw = self:get_gutter_width() local gw = self:get_gutter_width()
local xoffset = self:get_col_x_offset(line, col) local xoffset = self:get_col_x_offset(line, col)
local xmargin = 3 * self:get_font():get_width(' ') local xmargin = 3 * self:get_font():get_width(' ')
@ -236,10 +224,11 @@ function DocView:scroll_to_make_visible(line, col)
end end
end end
function DocView:on_mouse_moved(x, y, ...) function DocView:on_mouse_moved(x, y, ...)
DocView.super.on_mouse_moved(self, x, y, ...) DocView.super.on_mouse_moved(self, x, y, ...)
if self.hovered_scrollbar_track or self.dragging_scrollbar then if self:scrollbar_overlaps_point(x, y) or self.dragging_scrollbar then
self.cursor = "arrow" self.cursor = "arrow"
else else
self.cursor = "ibeam" self.cursor = "ibeam"
@ -282,8 +271,8 @@ function DocView:mouse_selection(doc, snap_type, line1, col1, line2, col2)
end end
function DocView:on_mouse_released(...) function DocView:on_mouse_released(button)
DocView.super.on_mouse_released(self, ...) DocView.super.on_mouse_released(self, button)
self.mouse_selecting = nil self.mouse_selecting = nil
end end
@ -295,15 +284,13 @@ end
function DocView:update() function DocView:update()
-- scroll to make caret visible and reset blink timer if it moved -- scroll to make caret visible and reset blink timer if it moved
local line1, col1, line2, col2 = self.doc:get_selection() local line, col = self.doc:get_selection()
if (line1 ~= self.last_line1 or col1 ~= self.last_col1 or if (line ~= self.last_line or col ~= self.last_col) and self.size.x > 0 then
line2 ~= self.last_line2 or col2 ~= self.last_col2) and self.size.x > 0 then
if core.active_view == self then if core.active_view == self then
self:scroll_to_make_visible(line1, col1) self:scroll_to_make_visible(line, col)
end end
core.blink_reset() core.blink_reset()
self.last_line1, self.last_col1 = line1, col1 self.last_line, self.last_col = line, col
self.last_line2, self.last_col2 = line2, col2
end end
-- update blink timer -- update blink timer
@ -326,15 +313,14 @@ function DocView:draw_line_highlight(x, y)
end end
function DocView:draw_line_text(line, x, y) function DocView:draw_line_text(idx, x, y)
local default_font = self:get_font() local default_font = self:get_font()
local tx, ty = x, y + self:get_line_text_y_offset() local tx, ty = x, y + self:get_line_text_y_offset()
for _, 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 color = style.syntax[type]
local font = style.syntax_fonts[type] or default_font local font = style.syntax_fonts[type] or default_font
tx = renderer.draw_text(font, text, tx, ty, color) tx = renderer.draw_text(font, text, tx, ty, color)
end end
return self:get_line_height()
end end
function DocView:draw_caret(x, y) function DocView:draw_caret(x, y)
@ -342,37 +328,28 @@ function DocView:draw_caret(x, y)
renderer.draw_rect(x, y, style.caret_width, lh, style.caret) renderer.draw_rect(x, y, style.caret_width, lh, style.caret)
end 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 -- draw highlight if any selection ends on this line
local draw_highlight = false local draw_highlight = false
local hcl = config.highlight_current_line for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
if hcl ~= false then if line1 == idx then
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do draw_highlight = true
if line1 == line then break
if hcl == "no_selection" then
if (line1 ~= line2) or (col1 ~= col2) then
draw_highlight = false
break
end
end
draw_highlight = true
break
end
end end
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) self:draw_line_highlight(x + self.scroll.x, y)
end end
-- draw selection if it overlaps this line -- 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 for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
if line >= line1 and line <= line2 then if idx >= line1 and idx <= line2 then
local text = self.doc.lines[line] local text = self.doc.lines[idx]
if line1 ~= line then col1 = 1 end if line1 ~= idx then col1 = 1 end
if line2 ~= line then col2 = #text + 1 end if line2 ~= idx then col2 = #text + 1 end
local x1 = x + self:get_col_x_offset(line, col1) local x1 = x + self:get_col_x_offset(idx, col1)
local x2 = x + self:get_col_x_offset(line, col2) local x2 = x + self:get_col_x_offset(idx, col2)
local lh = self:get_line_height()
if x1 ~= x2 then if x1 ~= x2 then
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection) renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
end end
@ -380,22 +357,21 @@ function DocView:draw_line_body(line, x, y)
end end
-- draw line's text -- draw line's text
return self:draw_line_text(line, x, y) self:draw_line_text(idx, x, y)
end end
function DocView:draw_line_gutter(line, x, y, width) function DocView:draw_line_gutter(idx, x, y, width)
local color = style.line_number local color = style.line_number
for _, line1, _, line2 in self.doc:get_selections(true) do 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 color = style.line_number2
break break
end end
end end
local yoffset = self:get_line_text_y_offset()
x = x + style.padding.x x = x + style.padding.x
local lh = self:get_line_height() common.draw_text(self:get_font(), color, idx, "right", x, y + yoffset, width, self:get_line_height())
common.draw_text(self:get_font(), color, line, "right", x, y, width, lh)
return lh
end end
@ -409,7 +385,8 @@ function DocView:draw_overlay()
and system.window_has_focus() then and system.window_has_focus() then
if config.disable_blink if config.disable_blink
or (core.blink_timer - core.blink_start) % T < T / 2 then or (core.blink_timer - core.blink_start) % T < T / 2 then
self:draw_caret(self:get_line_screen_position(line, col)) local x, y = self:get_line_screen_position(line)
self:draw_caret(x + self:get_col_x_offset(line, col), y)
end end
end end
end end
@ -427,7 +404,8 @@ function DocView:draw()
local x, y = self:get_line_screen_position(minline) local x, y = self:get_line_screen_position(minline)
local gw, gpad = self:get_gutter_width() local gw, gpad = self:get_gutter_width()
for i = minline, maxline do 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 end
local pos = self.position local pos = self.position
@ -436,7 +414,8 @@ function DocView:draw()
-- right side it is redundant with the Node's clip. -- 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) core.push_clip_rect(pos.x + gw, pos.y, self.size.x - gw, self.size.y)
for i = minline, maxline do 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 end
self:draw_overlay() self:draw_overlay()
core.pop_clip_rect() core.pop_clip_rect()

View File

@ -2,26 +2,14 @@ local style = require "core.style"
local keymap = require "core.keymap" local keymap = require "core.keymap"
local View = require "core.view" local View = require "core.view"
---@class core.emptyview : core.view
---@field super core.view
local EmptyView = View:extend() local EmptyView = View:extend()
local function draw_text(x, y, color) local function draw_text(x, y, color)
local th = style.big_font:get_height() local th = style.big_font:get_height()
local dh = 2 * th + style.padding.y * 2 local dh = 2 * th + style.padding.y * 2
local x1, y1 = x, y + (dh - th) / 2 local x1, y1 = x, y + (dh - th) / 2
local xv = x1 x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color)
local title = "Lite XL" renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color)
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 x = x + style.padding.x
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color) renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
local lines = { local lines = {

File diff suppressed because it is too large Load Diff

View File

@ -1,115 +1,35 @@
local core = require "core"
local command = require "core.command" local command = require "core.command"
local config = require "core.config" local config = require "core.config"
local keymap = {} 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 = {} keymap.modkeys = {}
---List of commands assigned to a shortcut been the key of the map the shortcut.
---@type keymap.map
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 = {} keymap.reverse_map = {}
local macos = PLATFORM == "Mac OS X" local macos = PLATFORM == "Mac OS X"
local os4 = PLATFORM == "AmigaOS 4" local os4 = PLATFORM == "AmigaOS 4"
local mos = PLATFORM == "MORPHOS"
-- Thanks to mathewmariani, taken from his lite-macos github repository. -- Thanks to mathewmariani, taken from his lite-macos github repository.
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or os4 and "os4" or mos and "mos" or "generic")) local modkeys_os = require("core.modkeys-" .. (macos and "macos" or os4 and "os4" or "generic"))
---@type table<keymap.modkey, keymap.modkey>
local modkey_map = modkeys_os.map local modkey_map = modkeys_os.map
---@type keymap.modkey[]
local modkeys = modkeys_os.keys local modkeys = modkeys_os.keys
local function key_to_stroke(k)
---Generates a stroke sequence including currently pressed mod keys.
---@param key string
---@return string
local function key_to_stroke(key)
local stroke = "" local stroke = ""
for _, mk in ipairs(modkeys) do for _, mk in ipairs(modkeys) do
if keymap.modkeys[mk] then if keymap.modkeys[mk] then
stroke = stroke .. mk .. "+" stroke = stroke .. mk .. "+"
end end
end end
return stroke .. key return stroke .. k
end 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
if type(commands) == "string" or type(commands) == "function" then
commands = { commands }
end
if keymap.map[stroke] then
for _, registered_cmd in ipairs(keymap.map[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) function keymap.add_direct(map)
for stroke, commands in pairs(map) do for stroke, commands in pairs(map) do
if type(commands) == "string" or type(commands) == "function" then if type(commands) == "string" then
commands = { commands } commands = { commands }
end 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 keymap.map[stroke] = commands
for _, cmd in ipairs(commands) do for _, cmd in ipairs(commands) do
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {} keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
@ -118,23 +38,15 @@ function keymap.add_direct(map)
end end
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) function keymap.add(map, overwrite)
remove_duplicates(map)
for stroke, commands in pairs(map) do for stroke, commands in pairs(map) do
if macos then if macos then
stroke = stroke:gsub("%f[%a]ctrl%f[%A]", "cmd") stroke = stroke:gsub("%f[%a]ctrl%f[%A]", "cmd")
end end
if type(commands) == "string" then
commands = { commands }
end
if overwrite then 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 keymap.map[stroke] = commands
else else
keymap.map[stroke] = keymap.map[stroke] or {} keymap.map[stroke] = keymap.map[stroke] or {}
@ -150,34 +62,35 @@ function keymap.add(map, overwrite)
end end
---Unregisters the given shortcut and associated command. local function remove_only(tbl, k, v)
---@param shortcut string for key, values in pairs(tbl) do
---@param cmd string if key == k then
function keymap.unbind(shortcut, cmd) if v then
remove_only(keymap.map, shortcut, cmd) for i, value in ipairs(values) do
remove_only(keymap.reverse_map, cmd, shortcut) 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 end
---Returns all the shortcuts associated to a command unpacked for easy assignment.
---@param cmd string
---@return ...
function keymap.get_binding(cmd) function keymap.get_binding(cmd)
return table.unpack(keymap.reverse_map[cmd] or {}) return table.unpack(keymap.reverse_map[cmd] or {})
end 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, ...) function keymap.on_key_pressed(k, ...)
local mk = modkey_map[k] local mk = modkey_map[k]
if mk then if mk then
@ -188,19 +101,10 @@ function keymap.on_key_pressed(k, ...)
end end
else else
local stroke = key_to_stroke(k) local stroke = key_to_stroke(k)
local commands, performed = keymap.map[stroke], false local commands, performed = keymap.map[stroke]
if commands then if commands then
for _, cmd in ipairs(commands) do for _, cmd in ipairs(commands) do
if type(cmd) == "function" then performed = command.perform(cmd, ...)
local ok, res = core.try(cmd, ...)
if ok then
performed = not (res == false)
else
performed = true
end
else
performed = command.perform(cmd, ...)
end
if performed then break end if performed then break end
end end
return performed return performed
@ -230,9 +134,6 @@ function keymap.on_key_released(k)
end end
--------------------------------------------------------------------------------
-- Register default bindings
--------------------------------------------------------------------------------
if macos then if macos then
local keymap_macos = require("core.keymap-macos") local keymap_macos = require("core.keymap-macos")
keymap_macos(keymap) keymap_macos(keymap)
@ -246,7 +147,7 @@ keymap.add_direct {
["ctrl+n"] = "core:new-doc", ["ctrl+n"] = "core:new-doc",
["ctrl+shift+c"] = "core:change-project-folder", ["ctrl+shift+c"] = "core:change-project-folder",
["ctrl+shift+o"] = "core:open-project-folder", ["ctrl+shift+o"] = "core:open-project-folder",
["ctrl+alt+r"] = "core:restart", ["ctrl+shift+r"] = "core:restart",
["alt+return"] = "core:toggle-fullscreen", ["alt+return"] = "core:toggle-fullscreen",
["f11"] = "core:toggle-fullscreen", ["f11"] = "core:toggle-fullscreen",
@ -315,7 +216,6 @@ keymap.add_direct {
["ctrl+l"] = "doc:select-lines", ["ctrl+l"] = "doc:select-lines",
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" }, ["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
["ctrl+/"] = "doc:toggle-line-comments", ["ctrl+/"] = "doc:toggle-line-comments",
["ctrl+shift+/"] = "doc:toggle-block-comments",
["ctrl+up"] = "doc:move-lines-up", ["ctrl+up"] = "doc:move-lines-up",
["ctrl+down"] = "doc:move-lines-down", ["ctrl+down"] = "doc:move-lines-down",
["ctrl+shift+d"] = "doc:duplicate-lines", ["ctrl+shift+d"] = "doc:duplicate-lines",

View File

@ -1,7 +1,5 @@
local core = require "core" local core = require "core"
local common = require "core.common" local common = require "core.common"
local config = require "core.config"
local keymap = require "core.keymap"
local style = require "core.style" local style = require "core.style"
local View = require "core.view" local View = require "core.view"
@ -38,15 +36,12 @@ local LogView = View:extend()
LogView.context = "session" LogView.context = "session"
function LogView:new() function LogView:new()
LogView.super.new(self) LogView.super.new(self)
self.last_item = core.log_items[#core.log_items] self.last_item = core.log_items[#core.log_items]
self.expanding = {} self.expanding = {}
self.scrollable = true self.scrollable = true
self.yoffset = 0 self.yoffset = 0
core.status_view:show_message("i", style.text, "ctrl+click to copy entry")
end end
@ -82,43 +77,25 @@ function LogView:each_item()
end end
function LogView:get_scrollable_size() function LogView:on_mouse_moved(px, py, ...)
local _, y_off = self:get_content_offset() LogView.super.on_mouse_moved(self, px, py, ...)
local last_y, last_h = 0, 0 local hovered = false
for i, item, x, y, w, h in self:each_item() do for _, 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
if px >= x and py >= y and px < x + w and py < y + h then if px >= x and py >= y and px < x + w and py < y + h then
index = i hovered = true
selected = item self.hovered_item = item
break break
end end
end end
if not hovered then self.hovered_item = nil end
end
if selected then
if keymap.modkeys["ctrl"] then function LogView:on_mouse_pressed(button, mx, my, clicks)
system.set_clipboard(core.get_log(selected)) if LogView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
core.status_view:show_message("i", style.text, "copied entry #"..index.." to clipboard.") if self.hovered_item then
else self:expand_item(self.hovered_item)
self:expand_item(selected)
end
end end
return true
end end
@ -132,13 +109,13 @@ function LogView:update()
local expanding = self.expanding[1] local expanding = self.expanding[1]
if expanding then 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 if expanding.current == expanding.target then
table.remove(self.expanding, 1) table.remove(self.expanding, 1)
end end
end end
self:move_towards("yoffset", 0, nil, "logview") self:move_towards("yoffset", 0)
LogView.super.update(self) LogView.super.update(self)
end end
@ -154,62 +131,41 @@ local function draw_text_multiline(font, text, x, y, color)
return resx, y return resx, y
end end
-- this is just to get a date string that's consistent
local datestr = os.date()
function LogView:draw() function LogView:draw()
self:draw_background(style.background) self:draw_background(style.background)
local th = style.font:get_height() local th = style.font:get_height()
local lh = th + style.padding.y -- for one line local lh = th + style.padding.y -- for one line
local iw = math.max( for _, item, x, y, w in self:each_item() do
style.icon_font:get_width(style.log.ERROR.icon), x = x + style.padding.x
style.icon_font:get_width(style.log.INFO.icon)
)
local tw = style.font:get_width(datestr) local time = os.date(nil, item.time)
for _, item, x, y, w, h in self:each_item() do x = common.draw_text(style.font, style.dim, time, "left", x, y, w, lh)
if y + h >= self.position.y and y <= self.position.y + self.size.y then x = x + style.padding.x
core.push_clip_rect(x, y, w, h)
x = x + style.padding.x
x = common.draw_text( x = common.draw_text(style.code_font, style.dim, is_expanded(item) and "-" or "+", "left", x, y, w, lh)
style.icon_font, x = x + style.padding.x
style.log[item.level].color, w = w - (x - self:get_content_offset())
style.log[item.level].icon,
"center",
x, y, iw, lh
)
x = x + style.padding.x
-- timestamps are always 15% of the width if is_expanded(item) then
local time = os.date(nil, item.time) y = y + common.round(style.padding.y / 2)
common.draw_text(style.font, style.dim, time, "left", x, y, tw, lh) _, y = draw_text_multiline(style.font, item.text, x, y, style.text)
x = x + tw + style.padding.x
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 if item.info then
y = y + common.round(style.padding.y / 2) _, y = draw_text_multiline(style.font, item.info, x, y, style.dim)
_, y = draw_text_multiline(style.font, item.text, x, y, style.text)
local at = "at " .. common.home_encode(item.at)
_, y = common.draw_text(style.font, style.dim, at, "left", x, y, w, lh)
if item.info then
_, y = draw_text_multiline(style.font, item.info, x, y, style.dim)
end
else
local line, has_newline = string.match(item.text, "([^\n]+)(\n?)")
if has_newline ~= "" then
line = line .. " ..."
end
_, y = common.draw_text(style.font, style.text, line, "left", x, y, w, lh)
end end
else
core.pop_clip_rect() 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
end end
LogView.super.draw_scrollbar(self)
end end

View File

@ -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

View File

@ -11,19 +11,13 @@ local UNDERLINE_MARGIN = common.round(1 * SCALE)
local noop = function() end local noop = function() end
---@class core.nagview : core.view
---@field super core.view
local NagView = View:extend() local NagView = View:extend()
function NagView:new() function NagView:new()
NagView.super.new(self) NagView.super.new(self)
self.size.y = 0 self.size.y = 0
self.show_height = 0
self.force_focus = false self.force_focus = false
self.queue = {} self.queue = {}
self.scrollable = true
self.target_height = 0
self.on_mouse_pressed_root = nil
end end
function NagView:get_title() function NagView:get_title()
@ -52,20 +46,20 @@ function NagView:get_target_height()
return self.target_height + 2 * style.padding.y return self.target_height + 2 * style.padding.y
end end
function NagView:get_scrollable_size() function NagView:update()
local w, h = system.get_window_size() NagView.super.update(self)
if self.visible and self:get_target_height() > h then
self.size.y = h if core.active_view == self and self.title then
return self:get_target_height() self:move_towards(self.size, "y", self:get_target_height())
self:move_towards(self, "underline_progress", 1)
else else
self.size.y = 0 self:move_towards(self.size, "y", 0)
end end
return 0
end end
function NagView:dim_window_content() function NagView:draw_overlay()
local ox, oy = self:get_content_offset() 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 local w, h = core.root_view.size.x, core.root_view.size.y - oy
core.root_view:defer_draw(function() core.root_view:defer_draw(function()
renderer.draw_rect(ox, oy, w, h, style.nagbar_dim) renderer.draw_rect(ox, oy, w, h, style.nagbar_dim)
@ -87,7 +81,7 @@ function NagView:each_option()
bh = self:get_buttons_height() bh = self:get_buttons_height()
ox,oy = self:get_content_offset() ox,oy = self:get_content_offset()
ox = ox + self.size.x 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 for i = #self.options, 1, -1 do
opt = self.options[i] opt = self.options[i]
@ -100,8 +94,6 @@ function NagView:each_option()
end end
function NagView:on_mouse_moved(mx, my, ...) 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, ...) NagView.super.on_mouse_moved(self, mx, my, ...)
for i, _, x,y,w,h in self:each_option() do 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 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
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) 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 end
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return true end
for i, _, x,y,w,h in self:each_option() do 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 if mx >= x and my >= y and mx < x + w and my < y + h then
self:change_hovered(i) self:change_hovered(i)
command.perform "dialog:select" command.perform "dialog:select"
break
end end
end end
return true
end end
function NagView:on_text_input(text) function NagView:on_text_input(text)
if not self.visible then return end
if text:lower() == "y" then if text:lower() == "y" then
command.perform "dialog:select-yes" command.perform "dialog:select-yes"
elseif text:lower() == "n" then elseif text:lower() == "n" then
@ -167,39 +122,20 @@ function NagView:on_text_input(text)
end end
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 function NagView:draw()
self:move_towards(self, "show_height", self:get_target_height(), nil, "nagbar") if self.size.y <= 0 or not self.title then return end
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
local function draw_nagview_message(self) self:draw_overlay()
self:dim_window_content() self:draw_background(style.nagbar)
-- draw message's background
local ox, oy = self:get_content_offset() 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 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 there are other items, show it
if #self.queue > 0 then if #self.queue > 0 then
local str = string.format("[%d]", #self.queue) 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 ox = ox + style.padding.x
end end
@ -232,17 +168,6 @@ local function draw_nagview_message(self)
common.draw_text(opt.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 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 end
function NagView:get_message_height() function NagView:get_message_height()
@ -253,31 +178,23 @@ function NagView:get_message_height()
return h return h
end end
function NagView:next() function NagView:next()
local opts = table.remove(self.queue, 1) or {} local opts = table.remove(self.queue, 1) or {}
if opts.title and opts.message and opts.options then self.title = opts.title
self.visible = true self.message = opts.message and opts.message .. "\n"
self.title = opts.title self.options = opts.options
self.message = opts.message and opts.message .. "\n" self.on_selected = opts.on_selected
self.options = opts.options if self.message and self.options then
self.on_selected = opts.on_selected
local message_height = self:get_message_height() local message_height = self:get_message_height()
-- self.target_height is the nagview height needed to display the message and -- self.target_height is the nagview height needed to display the message and
-- the buttons, excluding the top and bottom padding space. -- the buttons, excluding the top and bottom padding space.
self.target_height = math.max(message_height, self:get_buttons_height()) self.target_height = math.max(message_height, self:get_buttons_height())
self:change_hovered(common.find_index(self.options, "default_yes")) 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 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 end
function NagView:show(title, message, options, on_select) function NagView:show(title, message, options, on_select)
@ -287,7 +204,7 @@ function NagView:show(title, message, options, on_select)
opts.options = assert(options, "No options") opts.options = assert(options, "No options")
opts.on_selected = on_select or noop opts.on_selected = on_select or noop
table.insert(self.queue, opts) table.insert(self.queue, opts)
self:next() if #self.queue > 0 and not self.title then self:next() end
end end
return NagView return NagView

View File

@ -6,7 +6,6 @@ local Object = require "core.object"
local EmptyView = require "core.emptyview" local EmptyView = require "core.emptyview"
local View = require "core.view" local View = require "core.view"
---@class core.node : core.object
local Node = Object:extend() local Node = Object:extend()
function Node:new(type) function Node:new(type)
@ -52,15 +51,6 @@ function Node:on_mouse_released(...)
end 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) function Node:consume(node)
for k, _ in pairs(self) do self[k] = nil end for k, _ in pairs(self) do self[k] = nil end
for k, v in pairs(node) do self[k] = v end for k, v in pairs(node) do self[k] = v end
@ -170,12 +160,8 @@ end
function Node:set_active_view(view) function Node:set_active_view(view)
assert(self.type == "leaf", "Tried to set active view on non-leaf node") assert(self.type == "leaf", "Tried to set active view on non-leaf node")
local last_active_view = self.active_view
self.active_view = view self.active_view = view
core.set_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 end
@ -274,8 +260,8 @@ end
local function close_button_location(x, w) local function close_button_location(x, w)
local cw = style.icon_font:get_width("C") local cw = style.icon_font:get_width("C")
local pad = style.padding.x / 2 local pad = style.padding.y
return x + w - cw - pad, cw, pad return x + w - pad - cw, cw, pad
end end
@ -482,67 +468,59 @@ function Node:update()
end end
self:tab_hovered_update(self.hovered.x, self.hovered.y) self:tab_hovered_update(self.hovered.x, self.hovered.y)
local tab_width = self:target_tab_width() local tab_width = self:target_tab_width()
self:move_towards("tab_shift", tab_width * (self.tab_offset - 1), nil, "tabs") self:move_towards("tab_shift", tab_width * (self.tab_offset - 1))
self:move_towards("tab_width", tab_width, nil, "tabs") self:move_towards("tab_width", tab_width)
else else
self.a:update() self.a:update()
self.b:update() self.b:update()
end end
end end
function Node:draw_tab_title(view, font, is_active, is_hovered, x, y, w, h) function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
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
local ds = style.divider_size local ds = style.divider_size
local dots_width = style.font:get_width("")
local color = style.dim local color = style.dim
local padding_y = style.padding.y 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 if standalone then
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2) renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
end end
-- Full border
if is_active then if is_active then
color = style.text color = style.text
renderer.draw_rect(x, y, w, h, style.background) renderer.draw_rect(x, y, w, h, style.background)
renderer.draw_rect(x + w, y, ds, h, style.divider) renderer.draw_rect(x + w, y, ds, h, style.divider)
renderer.draw_rect(x - ds, y, ds, h, style.divider) renderer.draw_rect(x - ds, y, ds, h, style.divider)
end end
return x + ds, y, w - ds*2, h local cx, cw, cspace = close_button_location(x, w)
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 show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button) local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
if show_close_button then if show_close_button then
local close_style = is_close_hovered and style.text or style.dim 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 end
-- Title if is_hovered then
x = x + cpad color = style.text
w = cx - x end
core.push_clip_rect(x, y, w, h) local padx = style.padding.x
self:draw_tab_title(view, style.font, is_active, is_hovered, x, y, w, h) -- 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() core.pop_clip_rect()
end end
@ -569,7 +547,7 @@ function Node:draw_tabs()
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
local view = self.views[i] local view = self.views[i]
local x, y, w, h = self:get_tab_rect(i) local x, y, w, h = self:get_tab_rect(i)
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, i == self.hovered_tab, i == self.hovered_close,
x, y, w, h) x, y, w, h)
end end
@ -710,7 +688,7 @@ function Node:get_split_type(mouse_x, mouse_y)
local local_mouse_x = mouse_x - x local local_mouse_x = mouse_x - x
local local_mouse_y = mouse_y - y local local_mouse_y = mouse_y - y
if local_mouse_y < 0 then if local_mouse_y < 0 then
return "tab" return "tab"
else else

View File

@ -1,12 +1,11 @@
---@class core.object
---@field super core.object
local Object = {} local Object = {}
Object.__index = 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() function Object:extend()
local cls = {} local cls = {}
for k, v in pairs(self) do for k, v in pairs(self) do
@ -20,17 +19,8 @@ function Object:extend()
return cls return cls
end 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. function Object:is(T)
---@param T any
---@return boolean
function Object:extends(T)
local mt = getmetatable(self) local mt = getmetatable(self)
while mt do while mt do
if mt == T then if mt == T then
@ -41,14 +31,12 @@ function Object:extends(T)
return false return false
end end
---Metamethod to get a string representation of an object.
---@return string
function Object:__tostring() function Object:__tostring()
return "Object" return "Object"
end end
---Methamethod to allow using the object call as a constructor.
---@return core.object
function Object:__call(...) function Object:__call(...)
local obj = setmetatable({}, self) local obj = setmetatable({}, self)
obj:new(...) obj:new(...)

View File

@ -5,9 +5,8 @@ regex.__index = function(table, key) return regex[key]; end
regex.match = function(pattern_string, string, offset, options) regex.match = function(pattern_string, string, offset, options)
local pattern = type(pattern_string) == "table" and local pattern = type(pattern_string) == "table" and
pattern_string or regex.compile(pattern_string) pattern_string or regex.compile(pattern_string)
local res = { regex.cmatch(pattern, string, offset or 1, options or 0) } local s, e = regex.cmatch(pattern, string, offset or 1, options or 0)
res[2] = res[2] and res[2] - 1 return s, e and e - 1
return table.unpack(res)
end end
-- Will iterate back through any UTF-8 bytes so that we don't replace bits -- Will iterate back through any UTF-8 bytes so that we don't replace bits

View File

@ -5,10 +5,7 @@ local Node = require "core.node"
local View = require "core.view" local View = require "core.view"
local DocView = require "core.docview" 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() local RootView = View:extend()
function RootView:new() function RootView:new()
@ -32,15 +29,11 @@ function RootView:defer_draw(fn, ...)
end end
---@return core.node
function RootView:get_active_node() function RootView:get_active_node()
local node = self.root_node:get_node_for_view(core.active_view) return self.root_node:get_node_for_view(core.active_view)
if not node then node = self:get_primary_node() end
return node
end end
---@return core.node
local function get_primary_node(node) local function get_primary_node(node)
if node.is_primary_node then if node.is_primary_node then
return node return node
@ -51,10 +44,8 @@ local function get_primary_node(node)
end end
---@return core.node
function RootView:get_active_node_default() function RootView:get_active_node_default()
local node = self.root_node:get_node_for_view(core.active_view) 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 if node.locked then
local default_view = self:get_primary_node().views[1] local default_view = self:get_primary_node().views[1]
assert(default_view, "internal error: cannot find original document node.") assert(default_view, "internal error: cannot find original document node.")
@ -65,14 +56,11 @@ function RootView:get_active_node_default()
end end
---@return core.node
function RootView:get_primary_node() function RootView:get_primary_node()
return get_primary_node(self.root_node) return get_primary_node(self.root_node)
end end
---@param node core.node
---@return core.node
local function select_next_primary_node(node) local function select_next_primary_node(node)
if node.is_primary_node then return end if node.is_primary_node then return end
if node.type ~= "leaf" then if node.type ~= "leaf" then
@ -86,14 +74,11 @@ local function select_next_primary_node(node)
end end
---@return core.node
function RootView:select_next_primary_node() function RootView:select_next_primary_node()
return select_next_primary_node(self.root_node) return select_next_primary_node(self.root_node)
end end
---@param doc core.doc
---@return core.docview
function RootView:open_doc(doc) function RootView:open_doc(doc)
local node = self:get_active_node_default() local node = self:get_active_node_default()
for i, view in ipairs(node.views) do for i, view in ipairs(node.views) do
@ -110,27 +95,17 @@ function RootView:open_doc(doc)
end end
---@param keep_active boolean
function RootView:close_all_docviews(keep_active) function RootView:close_all_docviews(keep_active)
self.root_node:close_all_docviews(keep_active) self.root_node:close_all_docviews(keep_active)
end end
---Function to intercept mouse pressed events on the active view. -- Function to intercept mouse pressed events on the active view.
---Do nothing by default. -- Do nothing by default.
---@param button core.view.mousebutton
---@param x number
---@param y number
---@param clicks integer
function RootView.on_view_mouse_pressed(button, x, y, clicks) function RootView.on_view_mouse_pressed(button, x, y, clicks)
end 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) function RootView:on_mouse_pressed(button, x, y, clicks)
local div = self.root_node:get_divider_overlapping_point(x, y) local div = self.root_node:get_divider_overlapping_point(x, y)
local node = self.root_node:get_child_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 end
---@param button core.view.mousebutton
---@param x number
---@param y number
function RootView:on_mouse_released(button, x, y, ...) function RootView:on_mouse_released(button, x, y, ...)
if self.dragged_divider then if self.dragged_divider then
self.dragged_divider = nil self.dragged_divider = nil
@ -245,10 +217,6 @@ local function resize_child_node(node, axis, value, delta)
end end
---@param x number
---@param y number
---@param dx number
---@param dy number
function RootView:on_mouse_moved(x, y, dx, dy) function RootView:on_mouse_moved(x, y, dx, dy)
if core.active_view == core.nag_view then if core.active_view == core.nag_view then
core.request_cursor("arrow") core.request_cursor("arrow")
@ -285,13 +253,8 @@ function RootView:on_mouse_moved(x, y, dx, dy)
self.root_node: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) 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 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) 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 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 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(...) function RootView:on_mouse_wheel(...)
local x, y = self.mouse.x, self.mouse.y local x, y = self.mouse.x, self.mouse.y
local node = self.root_node:get_child_overlapping_point(x, y) local node = self.root_node:get_child_overlapping_point(x, y)
@ -342,12 +288,12 @@ end
function RootView:interpolate_drag_overlay(overlay) function RootView:interpolate_drag_overlay(overlay)
self:move_towards(overlay, "x", overlay.to.x, nil, "tab_drag") self:move_towards(overlay, "x", overlay.to.x)
self:move_towards(overlay, "y", overlay.to.y, nil, "tab_drag") self:move_towards(overlay, "y", overlay.to.y)
self:move_towards(overlay, "w", overlay.to.w, nil, "tab_drag") self:move_towards(overlay, "w", overlay.to.w)
self:move_towards(overlay, "h", overlay.to.h, nil, "tab_drag") 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 overlay.color[4] = overlay.base_color[4] * overlay.opacity / 100
end end
@ -435,8 +381,8 @@ function RootView:draw_grabbed_tab()
local _,_, w, h = dn.node:get_tab_rect(dn.idx) local _,_, w, h = dn.node:get_tab_rect(dn.idx)
local x = self.mouse.x - w / 2 local x = self.mouse.x - w / 2
local y = self.mouse.y - h / 2 local y = self.mouse.y - h / 2
local view = dn.node.views[dn.idx] local text = dn.node.views[dn.idx] and dn.node.views[dn.idx]:get_name() or ""
self.root_node:draw_tab(view, true, true, false, x, y, w, h, true) self.root_node:draw_tab(text, true, true, false, x, y, w, h, true)
end end

View File

@ -1,6 +1,6 @@
-- this file is used by lite-xl to setup the Lua environment when starting -- this file is used by lite-xl to setup the Lua environment when starting
VERSION = "2.1.0r1" VERSION = "2.0.3r1"
MOD_VERSION = "3" MOD_VERSION = "2"
SCALE = tonumber(os.getenv("LITE_SCALE") or os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or SCALE 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) PATHSEP = package.config:sub(1, 1)
@ -12,26 +12,16 @@ else
local prefix = EXEDIR:match("^(.+)[/\\]bin$") local prefix = EXEDIR:match("^(.+)[/\\]bin$")
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data') DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
end end
USERDIR = (system.get_file_info(EXEDIR .. '/user') and (EXEDIR .. '/user')) USERDIR = (os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl")
or ((os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl")) or (HOME and (HOME .. '/.config/lite-xl') or (EXEDIR .. '/user'))
or (HOME and (HOME .. '/.config/lite-xl'))
package.path = DATADIR .. '/?.lua;' package.path = DATADIR .. '/?.lua;' .. package.path
package.path = DATADIR .. '/?/init.lua;' .. package.path package.path = DATADIR .. '/?/init.lua;' .. package.path
package.path = USERDIR .. '/?.lua;' .. package.path package.path = USERDIR .. '/?.lua;' .. package.path
package.path = USERDIR .. '/?/init.lua;' .. package.path package.path = USERDIR .. '/?/init.lua;' .. package.path
local suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so') local dynamic_suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so')
package.cpath = package.cpath = DATADIR .. '/?.' .. dynamic_suffix .. ";" .. USERDIR .. '/?.' .. dynamic_suffix
USERDIR .. '/?.' .. ARCH .. "." .. suffix .. ";" ..
USERDIR .. '/?/init.' .. ARCH .. "." .. suffix .. ";" ..
USERDIR .. '/?.' .. suffix .. ";" ..
USERDIR .. '/?/init.' .. suffix .. ";" ..
DATADIR .. '/?.' .. ARCH .. "." .. suffix .. ";" ..
DATADIR .. '/?/init.' .. ARCH .. "." .. suffix .. ";" ..
DATADIR .. '/?.' .. suffix .. ";" ..
DATADIR .. '/?/init.' .. suffix .. ";"
package.native_plugins = {} package.native_plugins = {}
package.searchers = { package.searchers[1], package.searchers[2], function(modname) package.searchers = { package.searchers[1], package.searchers[2], function(modname)
local path = package.searchpath(modname, package.cpath) local path = package.searchpath(modname, package.cpath)
@ -42,16 +32,3 @@ end }
table.pack = table.pack or pack or function(...) return {...} end table.pack = table.pack or pack or function(...) return {...} end
table.unpack = table.unpack or unpack 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

View File

@ -4,7 +4,6 @@ local style = {}
style.padding = { x = common.round(14 * SCALE), y = common.round(7 * SCALE) } style.padding = { x = common.round(14 * SCALE), y = common.round(7 * SCALE) }
style.divider_size = common.round(1 * SCALE) style.divider_size = common.round(1 * SCALE)
style.scrollbar_size = common.round(4 * SCALE) style.scrollbar_size = common.round(4 * SCALE)
style.expanded_scrollbar_size = common.round(12 * SCALE)
style.caret_width = common.round(2 * SCALE) style.caret_width = common.round(2 * SCALE)
style.tab_width = common.round(170 * 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.icon_big_font = style.icon_font:copy(23 * SCALE)
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 15 * 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 = {}
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. -- This can be used to override fonts per syntax group.
-- The syntax highlighter will take existing values from this table and -- The syntax highlighter will take existing values from this table and
@ -37,7 +72,4 @@ style.syntax = {}
style.syntax_fonts = {} style.syntax_fonts = {}
-- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options) -- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options)
style.log = {}
return style return style

View File

@ -7,24 +7,6 @@ local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} }
function syntax.add(t) 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) table.insert(syntax.items, t)
end end

View File

@ -17,8 +17,6 @@ local title_commands = {
{symbol = "X", action = function() core.quit() end}, {symbol = "X", action = function() core.quit() end},
} }
---@class core.titleview : core.view
---@field super core.view
local TitleView = View:extend() local TitleView = View:extend()
local function title_view_height() local function title_view_height()

View File

@ -1,15 +1,12 @@
local core = require "core"
local syntax = require "core.syntax" local syntax = require "core.syntax"
local common = require "core.common" local common = require "core.common"
local tokenizer = {} local tokenizer = {}
local bad_patterns = {}
local function push_token(t, type, text) local function push_token(t, type, text)
type = type or "normal"
local prev_type = t[#t-1] local prev_type = t[#t-1]
local prev_text = t[#t] local prev_text = t[#t]
if prev_type and (prev_type == type or prev_text:ufind("^%s*$")) then if prev_type and (prev_type == type or prev_text:find("^%s*$")) then
t[#t-1] = type t[#t-1] = type
t[#t] = prev_text .. text t[#t] = prev_text .. text
else else
@ -41,12 +38,12 @@ local function push_tokens(t, syn, pattern, full_text, find_results)
local fin = find_results[i + 1] - 1 local fin = find_results[i + 1] - 1
local type = pattern.type[i - 2] local type = pattern.type[i - 2]
-- ↑ (i - 2) to convert from [3; n] to [1; n] -- ↑ (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) push_token(t, syn.symbols[text] or type, text)
end end
else else
local start, fin = find_results[1], find_results[2] 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) push_token(t, syn.symbols[text] or pattern.type, text)
end end
end end
@ -55,12 +52,12 @@ end
-- State is a 32-bit number that is four separate bytes, illustrating how many -- 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. -- differnet delimiters we have open, and which subsyntaxes we have active.
-- At most, there are 3 subsyntaxes active at the same time. Beyond that, -- At most, there are 3 subsyntaxes active at the same time. Beyond that,
-- does not support further highlighting. -- does not support further highlighting.
-- You can think of it as a maximum 4 integer (0-255) stack. It always has -- 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 -- 1 integer in it. Calling `push_subsyntax` increases the stack depth. Calling
-- `pop_subsyntax` decreases it. The integers represent the index of a pattern -- `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 -- 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 -- pattern index, any integer lower in the stack must represent a pattern that
-- specifies a subsyntax. -- specifies a subsyntax.
@ -95,19 +92,6 @@ local function retrieve_syntax_state(incoming_syntax, state)
return current_syntax, subsyntax_info, current_pattern_idx, current_level return current_syntax, subsyntax_info, current_pattern_idx, current_level
end 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 in %s language plugin. " .. msg,
pattern_idx, syntax.name or "unnamed", ...)
end
---@param incoming_syntax table
---@param text string
---@param state integer
function tokenizer.tokenize(incoming_syntax, text, state) function tokenizer.tokenize(incoming_syntax, text, state)
local res = {} local res = {}
local i = 1 local i = 1
@ -118,22 +102,22 @@ function tokenizer.tokenize(incoming_syntax, text, state)
state = state or 0 state = state or 0
-- incoming_syntax : the parent syntax of the file. -- incoming_syntax : the parent syntax of the file.
-- state : a 32-bit number representing syntax state (see above) -- state : a 32-bit number representing syntax state (see above)
-- current_syntax : the syntax we're currently in. -- current_syntax : the syntax we're currently in.
-- subsyntax_info : info about the delimiters of this subsyntax. -- subsyntax_info : info about the delimiters of this subsyntax.
-- current_pattern_idx: the index of the pattern we're on for this syntax. -- current_pattern_idx: the index of the pattern we're on for this syntax.
-- current_level : how many subsyntaxes deep we are. -- current_level : how many subsyntaxes deep we are.
local current_syntax, subsyntax_info, current_pattern_idx, current_level = local current_syntax, subsyntax_info, current_pattern_idx, current_level =
retrieve_syntax_state(incoming_syntax, state) retrieve_syntax_state(incoming_syntax, state)
-- Should be used to set the state variable. Don't modify it directly. -- Should be used to set the state variable. Don't modify it directly.
local function set_subsyntax_pattern_idx(pattern_idx) local function set_subsyntax_pattern_idx(pattern_idx)
current_pattern_idx = pattern_idx current_pattern_idx = pattern_idx
state = bit32.replace(state, pattern_idx, current_level*8, 8) state = bit32.replace(state, pattern_idx, current_level*8, 8)
end end
local function push_subsyntax(entering_syntax, pattern_idx) local function push_subsyntax(entering_syntax, pattern_idx)
set_subsyntax_pattern_idx(pattern_idx) set_subsyntax_pattern_idx(pattern_idx)
current_level = current_level + 1 current_level = current_level + 1
@ -142,182 +126,127 @@ function tokenizer.tokenize(incoming_syntax, text, state)
entering_syntax.syntax or syntax.get(entering_syntax.syntax) entering_syntax.syntax or syntax.get(entering_syntax.syntax)
current_pattern_idx = 0 current_pattern_idx = 0
end end
local function pop_subsyntax() local function pop_subsyntax()
set_subsyntax_pattern_idx(0) set_subsyntax_pattern_idx(0)
current_level = current_level - 1 current_level = current_level - 1
set_subsyntax_pattern_idx(0) set_subsyntax_pattern_idx(0)
current_syntax, subsyntax_info, current_pattern_idx, current_level = current_syntax, subsyntax_info, current_pattern_idx, current_level =
retrieve_syntax_state(incoming_syntax, state) retrieve_syntax_state(incoming_syntax, state)
end end
local function find_text(text, p, offset, at_start, close) local function find_text(text, p, offset, at_start, close)
local target, res = p.pattern or p.regex, { 1, offset - 1 } local target, res = p.pattern or p.regex, { 1, offset - 1 }, p.regex
local p_idx = close and 2 or 1 local code = type(target) == "table" and target[close and 2 or 1] or target
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)
else
p.pattern = p.pattern and code:usub(2)
p.regex = p.regex and code:usub(2)
end
end
end
if p.regex and type(p.regex) ~= "table" then if p.regex and type(p.regex) ~= "table" then
p._regex = p._regex or regex.compile(p.regex) p._regex = p._regex or regex.compile(p.regex)
code = p._regex code = p._regex
end end
repeat repeat
local next = res[2] + 1 local next = res[2] + 1
-- If the pattern contained '^', allow matching only the whole line -- go to the start of the next utf-8 character
if p.whole_line[p_idx] and next > 1 then while text:byte(next) and common.is_utf8_cont(text, next) do
return next = next + 1
end end
res = p.pattern and { text:ufind((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) } res = p.pattern and { text:find(at_start and "^" .. code or code, next) }
or { regex.match(code, text, text:ucharpos(next), (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) } or { regex.match(code, text, next, at_start and regex.ANCHORED or 0) }
if p.regex and #res > 0 then -- set correct utf8 len for regex result if res[1] and close and target[3] then
local char_pos_1 = string.ulen(text:sub(1, res[1]))
local char_pos_2 = char_pos_1 + string.ulen(text:sub(res[1], res[2])) - 1
-- `regex.match` returns group results as a series of `begin, end`
-- we only want `begin`s
if #res >= 3 then
res[3] = char_pos_1 + string.ulen(text:sub(res[1], res[3])) - 1
end
for i=1,(#res-3) do
local curr = i + 3
local from = i * 2 + 3
if from < #res then
res[curr] = char_pos_1 + string.ulen(text:sub(res[1], res[from])) - 1
else
res[curr] = nil
end
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.
local count = 0 local count = 0
for i = res[1] - 1, 1, -1 do 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 count = count + 1
end end
if count % 2 == 0 then -- Check to see if the escaped character is there,
-- The match is not escaped, so confirm it -- and if it is not itself escaped.
break if count % 2 == 0 then break end
elseif not close then
-- The *open* match is escaped, so avoid it
return
end
end end
until not res[1] or not close or not target[3] until not res[1] or not close or not target[3]
return table.unpack(res) return table.unpack(res)
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]
local s, e = find_text(text, p, i, false, true)
local text_len = text:ulen() local cont = true
if text_len ~= nil then -- If we're in subsyntax mode, always check to see if we end our syntax
while i <= text_len do -- first, before the found delimeter, as ending the subsyntax takes
-- continue trying to match the end pattern of a pair if we have a state set -- precedence over ending the delimiter in the subsyntax.
if current_pattern_idx > 0 then
local p = current_syntax.patterns[current_pattern_idx]
local s, e = find_text(text, p, i, false, true)
local cont = true
-- If we're in subsyntax mode, always check to see if we end our syntax
-- first, before the found delimeter, as ending the subsyntax takes
-- 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
-- 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))
i = ss
cont = false
end
end
-- If we don't have any concerns about syntax delimiters,
-- continue on as normal.
if cont then
if s then
push_token(res, p.type, text:usub(i, e))
set_subsyntax_pattern_idx(0)
i = e + 1
else
push_token(res, p.type, text:usub(i))
break
end
end
end
-- General end of syntax check. Applies in the case where
-- we're ending early in the middle of a delimiter, or
-- just normally, upon finding a token.
if subsyntax_info then if subsyntax_info then
local s, e = find_text(text, subsyntax_info, i, true, true) local ss, se = find_text(text, subsyntax_info, i, false, true)
if s then -- If we find that we end the subsyntax before the
push_token(res, subsyntax_info.type, text:usub(i, e)) -- delimiter, push the token, and signal we shouldn't
-- On finding unescaped delimiter, pop it. -- treat the bit after as a token to be normally parsed
pop_subsyntax() -- (as it's the syntax delimiter).
i = e + 1 if ss and (s == nil or ss < s) then
push_token(res, p.type, text:sub(i, ss - 1))
i = ss
cont = false
end end
end end
-- If we don't have any concerns about syntax delimiters,
-- find matching pattern -- continue on as normal.
local matched = false if cont then
for n, p in ipairs(current_syntax.patterns) do if s then
local find_results = { find_text(text, p, i, true, false) } push_token(res, p.type, text:sub(i, e))
if find_results[1] then set_subsyntax_pattern_idx(0)
local type_is_table = type(p.type) == "table" i = e + 1
local n_types = type_is_table and #p.type or 1 else
if #find_results == 2 and type_is_table then push_token(res, p.type, text:sub(i))
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
if type(p.pattern or p.regex) == "table" then
-- If we have a subsyntax, push that onto the subsyntax stack.
if p.syntax then
push_subsyntax(p, n)
else
set_subsyntax_pattern_idx(n)
end
end
-- move cursor past this token
i = find_results[2] + 1
matched = true
break break
end end
end end
end
-- consume character if we didn't match -- General end of syntax check. Applies in the case where
if not matched then -- we're ending early in the middle of a delimiter, or
push_token(res, "normal", text:usub(i, i)) -- just normally, upon finding a token.
i = i + 1 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:sub(i, e))
-- On finding unescaped delimiter, pop it.
pop_subsyntax()
i = e + 1
end end
end end
-- find matching pattern
local matched = false
for n, p in ipairs(current_syntax.patterns) do
local find_results = { find_text(text, p, i, true, false) }
if find_results[1] then
-- 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
if type(p.pattern or p.regex) == "table" then
-- If we have a subsyntax, push that onto the subsyntax stack.
if p.syntax then
push_subsyntax(p, n)
else
set_subsyntax_pattern_idx(n)
end
end
-- move cursor past this token
i = find_results[2] + 1
matched = true
break
end
end
-- consume character if we didn't match
if not matched then
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 end
return res, state return res, state
end end

View File

@ -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

View File

@ -4,51 +4,7 @@ local style = require "core.style"
local common = require "core.common" local common = require "core.common"
local Object = require "core.object" local Object = require "core.object"
---@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
---@class core.view.increment
---@field value number
---@field to number
---@alias core.view.cursor "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'"
---@alias core.view.mousebutton "'left'" | "'right'"
---@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 scrollbar core.view.scrollbar
---@field scrollbar_alpha core.view.increment
local View = Object:extend() local View = Object:extend()
-- context can be "application" or "session". The instance of objects -- context can be "application" or "session". The instance of objects
@ -62,22 +18,14 @@ function View:new()
self.scroll = { x = 0, y = 0, to = { x = 0, y = 0 } } self.scroll = { x = 0, y = 0, to = { x = 0, y = 0 } }
self.cursor = "arrow" self.cursor = "arrow"
self.scrollable = false self.scrollable = false
self.scrollbar = {
x = { thumb = 0, track = 0 },
y = { thumb = 0, track = 0 },
w = { thumb = 0, track = 0, to = { thumb = 0, track = 0 } },
h = { thumb = 0, track = 0 },
}
self.scrollbar_alpha = { value = 0, to = 0 }
end end
function View:move_towards(t, k, dest, rate, name) function View:move_towards(t, k, dest, rate)
if type(t) ~= "table" then 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 end
local val = t[k] local val = t[k]
local diff = math.abs(val - dest) if not config.transitions or math.abs(val - dest) < 0.5 then
if not config.transitions or diff < 0.5 or config.disabled_transitions[name] then
t[k] = dest t[k] = dest
else else
rate = rate or 0.5 rate = rate or 0.5
@ -87,7 +35,7 @@ function View:move_towards(t, k, dest, rate, name)
end end
t[k] = common.lerp(val, dest, rate) t[k] = common.lerp(val, dest, rate)
end end
if diff > 1e-8 then if val ~= dest then
core.redraw = true core.redraw = true
end end
end end
@ -98,146 +46,62 @@ function View:try_close(do_close)
end end
---@return string
function View:get_name() function View:get_name()
return "---" return "---"
end end
---@return number
function View:get_scrollable_size() function View:get_scrollable_size()
return math.huge return math.huge
end end
---@return number x
---@return number y
---@return number width
---@return number height
function View:get_scrollbar_track_rect()
local sz = self:get_scrollable_size()
if sz <= self.size.y or sz == math.huge then
return 0, 0, 0, 0
end
local width = style.scrollbar_size
if self.hovered_scrollbar_track or self.dragging_scrollbar then
width = style.expanded_scrollbar_size
end
return
self.position.x + self.size.x - width,
self.position.y,
width,
self.size.y
end
---@return number x
---@return number y
---@return number width
---@return number height
function View:get_scrollbar_rect() function View:get_scrollbar_rect()
local sz = self:get_scrollable_size() local sz = self:get_scrollable_size()
if sz <= self.size.y or sz == math.huge then if sz <= self.size.y or sz == math.huge then
return 0, 0, 0, 0 return 0, 0, 0, 0
end end
local h = math.max(20, self.size.y * self.size.y / sz) local h = math.max(20, self.size.y * self.size.y / sz)
local width = style.scrollbar_size
if self.hovered_scrollbar_track or self.dragging_scrollbar then
width = style.expanded_scrollbar_size
end
return return
self.position.x + self.size.x - width, self.position.x + self.size.x - style.scrollbar_size,
self.position.y + self.scroll.y * (self.size.y - h) / (sz - self.size.y), self.position.y + self.scroll.y * (self.size.y - h) / (sz - self.size.y),
width, style.scrollbar_size,
h h
end end
---@param x number
---@param y number
---@return boolean
function View:scrollbar_overlaps_point(x, y) function View:scrollbar_overlaps_point(x, y)
local sx, sy, sw, sh = self:get_scrollbar_rect() local sx, sy, sw, sh = self:get_scrollbar_rect()
return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh return x >= sx - sw * 3 and x < sx + sw and y >= sy and y < sy + sh
end
---@param x number
---@param y number
---@return boolean
function View:scrollbar_track_overlaps_point(x, y)
local sx, sy, sw, sh = self:get_scrollbar_track_rect()
return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh
end 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) function View:on_mouse_pressed(button, x, y, clicks)
if self:scrollbar_track_overlaps_point(x, y) then if self:scrollbar_overlaps_point(x, y) then
if self:scrollbar_overlaps_point(x, y) then self.dragging_scrollbar = true
self.dragging_scrollbar = true
else
local _, _, _, sh = self:get_scrollbar_rect()
local ly = (y - self.position.y) - sh / 2
local pct = common.clamp(ly / self.size.y, 0, 100)
self.scroll.to.y = self:get_scrollable_size() * pct
end
return true return true
end end
end end
---@param button core.view.mousebutton
---@param x number
---@param y number
function View:on_mouse_released(button, x, y) function View:on_mouse_released(button, x, y)
self.dragging_scrollbar = false self.dragging_scrollbar = false
end end
---@param x number
---@param y number
---@param dx number
---@param dy number
function View:on_mouse_moved(x, y, dx, dy) function View:on_mouse_moved(x, y, dx, dy)
if self.dragging_scrollbar then if self.dragging_scrollbar then
local delta = self:get_scrollable_size() / self.size.y * dy local delta = self:get_scrollable_size() / self.size.y * dy
self.scroll.to.y = self.scroll.to.y + delta self.scroll.to.y = self.scroll.to.y + delta
if not config.animate_drag_scroll then
self:clamp_scroll_position()
self.scroll.y = self.scroll.to.y
end
end end
self.hovered_scrollbar = self:scrollbar_overlaps_point(x, y) self.hovered_scrollbar = self:scrollbar_overlaps_point(x, y)
self.hovered_scrollbar_track = self.hovered_scrollbar or self:scrollbar_track_overlaps_point(x, y)
end end
function View:on_mouse_left()
self.hovered_scrollbar = false
self.hovered_scrollbar_track = false
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) function View:on_text_input(text)
-- no-op -- no-op
end end
---@param y number
---@return boolean
function View:on_mouse_wheel(y) function View:on_mouse_wheel(y)
end end
@ -249,8 +113,6 @@ function View:get_content_bounds()
end end
---@return number x
---@return number y
function View:get_content_offset() function View:get_content_offset()
local x = common.round(self.position.x - self.scroll.x) local x = common.round(self.position.x - self.scroll.x)
local y = common.round(self.position.y - self.scroll.y) local y = common.round(self.position.y - self.scroll.y)
@ -264,37 +126,13 @@ function View:clamp_scroll_position()
end end
function View:update_scrollbar()
local x, y, w, h = self:get_scrollbar_rect()
self.scrollbar.w.to.thumb = w
self:move_towards(self.scrollbar.w, "thumb", self.scrollbar.w.to.thumb, 0.3, "scroll")
self.scrollbar.x.thumb = x + w - self.scrollbar.w.thumb
self.scrollbar.y.thumb = y
self.scrollbar.h.thumb = h
local x, y, w, h = self:get_scrollbar_track_rect()
self.scrollbar.w.to.track = w
self:move_towards(self.scrollbar.w, "track", self.scrollbar.w.to.track, 0.3, "scroll")
self.scrollbar.x.track = x + w - self.scrollbar.w.track
self.scrollbar.y.track = y
self.scrollbar.h.track = h
-- we use 100 for a smoother transition
self.scrollbar_alpha.to = (self.hovered_scrollbar_track or self.dragging_scrollbar) and 100 or 0
self:move_towards(self.scrollbar_alpha, "value", self.scrollbar_alpha.to, 0.3, "scroll")
end
function View:update() function View:update()
self:clamp_scroll_position() self:clamp_scroll_position()
self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3, "scroll") self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3)
self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3, "scroll") self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3)
self:update_scrollbar()
end end
---@param color renderer.color
function View:draw_background(color) function View:draw_background(color)
local x, y = self.position.x, self.position.y local x, y = self.position.x, self.position.y
local w, h = self.size.x, self.size.y local w, h = self.size.x, self.size.y
@ -302,29 +140,11 @@ function View:draw_background(color)
end end
function View:draw_scrollbar_track() function View:draw_scrollbar()
if not (self.hovered_scrollbar_track or self.dragging_scrollbar) local x, y, w, h = self:get_scrollbar_rect()
and self.scrollbar_alpha.value == 0 then
return
end
local color = { table.unpack(style.scrollbar_track) }
color[4] = color[4] * self.scrollbar_alpha.value / 100
renderer.draw_rect(self.scrollbar.x.track, self.scrollbar.y.track,
self.scrollbar.w.track, self.scrollbar.h.track, color)
end
function View:draw_scrollbar_thumb()
local highlight = self.hovered_scrollbar or self.dragging_scrollbar local highlight = self.hovered_scrollbar or self.dragging_scrollbar
local color = highlight and style.scrollbar2 or style.scrollbar local color = highlight and style.scrollbar2 or style.scrollbar
renderer.draw_rect(self.scrollbar.x.thumb, self.scrollbar.y.thumb, renderer.draw_rect(x, y, w, h, color)
self.scrollbar.w.thumb, self.scrollbar.h.thumb, color)
end
function View:draw_scrollbar()
self:draw_scrollbar_track()
self:draw_scrollbar_thumb()
end end

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local common = require "core.common" local common = require "core.common"
local config = require "core.config" local config = require "core.config"
@ -10,66 +10,14 @@ local RootView = require "core.rootview"
local DocView = require "core.docview" local DocView = require "core.docview"
local Doc = require "core.doc" local Doc = require "core.doc"
config.plugins.autocomplete = common.merge({ config.plugins.autocomplete = {
-- Amount of characters that need to be written for autocomplete -- Amount of characters that need to be written for autocomplete
min_len = 3, min_len = 3,
-- The max amount of visible items -- The max amount of visible items
max_height = 6, max_height = 6,
-- The max amount of scrollable items -- The max amount of scrollable items
max_suggestions = 100, 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)
local autocomplete = {} local autocomplete = {}
@ -85,7 +33,7 @@ local triggered_manually = false
local mt = { __tostring = function(t) return t.text end } local mt = { __tostring = function(t) return t.text end }
function autocomplete.add(t, manually_triggered) function autocomplete.add(t, triggered_manually)
local items = {} local items = {}
for text, info in pairs(t.items) do for text, info in pairs(t.items) do
if type(info) == "table" then if type(info) == "table" then
@ -95,10 +43,9 @@ function autocomplete.add(t, manually_triggered)
{ {
text = text, text = text,
info = info.info, info = info.info,
desc = info.desc, -- Description shown on item selected desc = info.desc, -- Description shown on item selected
onhover = info.onhover, -- A callback called once when item is hovered cb = info.cb, -- A callback called once when item is selected
onselect = info.onselect, -- A callback called when item is selected data = info.data -- Optional data that can be used on cb
data = info.data -- Optional data that can be used on cb
}, },
mt mt
) )
@ -109,7 +56,7 @@ function autocomplete.add(t, manually_triggered)
end end
end end
if not manually_triggered then if not triggered_manually then
autocomplete.map[t.name] = { files = t.files or ".*", items = items } autocomplete.map[t.name] = { files = t.files or ".*", items = items }
else else
autocomplete.map_manually[t.name] = { files = t.files or ".*", items = items } 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 -- 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() core.add_thread(function()
local cache = setmetatable({}, { __mode = "k" }) 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 function get_symbols(doc)
local s = {} if doc.disable_symbols then return {} end
get_syntax_symbols(s, doc)
if doc.disable_symbols then return s end
local i = 1 local i = 1
local s = {}
local symbols_count = 0 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 for sym in doc.lines[i]:gmatch(config.symbol_pattern) do
if not s[sym] then if not s[sym] then
symbols_count = symbols_count + 1 symbols_count = symbols_count + 1
if symbols_count > max_symbols then if symbols_count > max_symbols then
s = nil s = nil
doc.disable_symbols = true 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, core.status_view:show_message("!", style.accent,
"Too many symbols in "..filename_message.. "Too many symbols in document "..doc.filename..
": stopping auto-complete for this document according to ".. ": stopping auto-complete for this document according to config.max_symbols.")
"config.plugins.autocomplete.max_symbols."
)
collectgarbage('collect') collectgarbage('collect')
return {} return {}
end end
@ -202,7 +132,6 @@ core.add_thread(function()
for _, doc in ipairs(core.docs) do for _, doc in ipairs(core.docs) do
if not cache_is_valid(doc) then if not cache_is_valid(doc) then
valid = false valid = false
break
end end
end end
end end
@ -230,6 +159,16 @@ local function reset_suggestions()
end end
end end
local function in_table(value, table_array)
for i, element in pairs(table_array) do
if element == value then
return true
end
end
return false
end
local function update_suggestions() local function update_suggestions()
local doc = core.active_view.doc local doc = core.active_view.doc
local filename = doc and doc.filename or "" local filename = doc and doc.filename or ""
@ -260,7 +199,6 @@ local function update_suggestions()
j = j + 1 j = j + 1
end end
end end
suggestions_idx = 1
end end
local function get_partial_symbol() local function get_partial_symbol()
@ -271,7 +209,7 @@ local function get_partial_symbol()
end end
local function get_active_view() local function get_active_view()
if core.active_view:is(DocView) then if getmetatable(core.active_view) == DocView then
return core.active_view return core.active_view
end end
end end
@ -282,7 +220,8 @@ local function get_suggestions_rect(av)
end end
local line, col = av.doc:get_selection() 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 y = y + av:get_line_height() + style.padding.y
local font = av:get_font() local font = av:get_font()
local th = font:get_height() local th = font:get_height()
@ -310,11 +249,6 @@ local function get_suggestions_rect(av)
max_width = 150 max_width = 150
end end
-- if portion not visiable to right, reposition to DocView right margin
if (x - av.position.x) + max_width > av.size.x then
x = (av.size.x + av.position.x) - max_width - (style.padding.x * 2)
end
return return
x - style.padding.x, x - style.padding.x,
y - style.padding.y, y - style.padding.y,
@ -322,99 +256,20 @@ local function get_suggestions_rect(av)
max_items * (th + style.padding.y) + style.padding.y max_items * (th + style.padding.y) + style.padding.y
end 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) 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 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 = {} local lines = {}
for line in string.gmatch(text.."\n", "(.-)\n") do for line in string.gmatch(text.."\n", "(.-)\n") do
local wrapper_lines = wrap_line(line, max_chars) width = math.max(width, style.font:get_width(line))
if type(wrapper_lines) == "table" then table.insert(lines, line)
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
end end
if draw_left then local height = #lines * style.font:get_height()
x = sx - (style.padding.x / 4) - width - (style.padding.x * 2)
end
local height = #lines * font:get_height()
-- draw background rect -- draw background rect
renderer.draw_rect( renderer.draw_rect(
x, sx + sw + style.padding.x / 4,
sy, sy,
width + style.padding.x * 2, width + style.padding.x * 2,
height + style.padding.y * 2, height + style.padding.y * 2,
@ -422,10 +277,13 @@ local function draw_description_box(text, av, sx, sy, sw, sh)
) )
-- draw text -- 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 for _, line in pairs(lines) do
common.draw_text( common.draw_text(
font, style.text, line, "left", style.font, style.text, line, "left", x + style.padding.x, y, width, lh
x + style.padding.x, y, width, lh
) )
y = y + lh y = y + lh
end end
@ -462,9 +320,10 @@ local function draw_suggestions_box(av)
end end
y = y + lh y = y + lh
if suggestions_idx == i then if suggestions_idx == i then
if s.onhover then if s.cb then
s.onhover(suggestions_idx, s) s.cb(suggestions_idx, s)
s.onhover = nil s.cb = nil
s.data = nil
end end
if s.desc and #s.desc > 0 then if s.desc and #s.desc > 0 then
draw_description_box(s.desc, av, rx, ry, rw, rh) draw_description_box(s.desc, av, rx, ry, rw, rh)
@ -621,26 +480,17 @@ end
-- Commands -- Commands
-- --
local function predicate() local function predicate()
local active_docview = get_active_view() return get_active_view() and #suggestions > 0
return active_docview and #suggestions > 0, active_docview
end end
command.add(predicate, { command.add(predicate, {
["autocomplete:complete"] = function(dv) ["autocomplete:complete"] = function()
local doc = dv.doc local doc = core.active_view.doc
local line, col = doc:get_selection() local line, col = doc:get_selection()
local item = suggestions[suggestions_idx] local text = suggestions[suggestions_idx].text
local text = item.text doc:insert(line, col, text)
local inserted = false doc:remove(line, col, line, col - #partial)
if item.onselect then doc:set_selection(line, col + #text - #partial)
inserted = item.onselect(suggestions_idx, item)
end
if not inserted then
local current_partial = get_partial_symbol()
doc:insert(line, col, text)
doc:remove(line, col, line, col - #current_partial)
doc:set_selection(line, col + #text - #current_partial)
end
reset_suggestions() reset_suggestions()
end, end,

View File

@ -1,109 +1,43 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local config = require "core.config" local config = require "core.config"
local style = require "core.style"
local Doc = require "core.doc" 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 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) 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 end
local function reload_doc(doc) 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) update_time(doc)
core.redraw = true doc:clean()
core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename) core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename)
end end
local function check_prompt_reload(doc) local on_modify = core.on_dirmonitor_modify
if doc and doc.deferred_reload then
core.nag_view:show("File Changed", doc.filename .. " has changed. Reload this file?", {
{ font = style.font, text = "Yes", default_yes = true },
{ font = style.font, text = "No" , default_no = true }
}, function(item)
if item.text == "Yes" then reload_doc(doc) end
doc.deferred_reload = false
end)
end
end
local function doc_changes_visiblity(doc, visibility) core.on_dirmonitor_modify = function(dir, filepath)
if doc and visible[doc] ~= visibility and doc.abs_filename then local abs_filename = dir.name .. PATHSEP .. filepath
visible[doc] = visibility for _, doc in ipairs(core.docs) do
if visibility then check_prompt_reload(doc) end local info = system.get_file_info(doc.filename or "")
get_project_doc_watch(doc):watch(doc.abs_filename, visibility) if doc.abs_filename == abs_filename and info and times[doc] ~= info.modified then
end reload_doc(doc)
end break
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 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
end 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
end) on_modify(dir, filepath)
end
-- patch `Doc.save|load` to store modified time -- patch `Doc.save|load` to store modified time
local load = Doc.load local load = Doc.load
@ -117,8 +51,6 @@ end
Doc.save = function(self, ...) Doc.save = function(self, ...)
local res = save(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) update_time(self)
return res return res
end end

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local command = require "core.command" local command = require "core.command"
local keymap = require "core.keymap" local keymap = require "core.keymap"
@ -32,9 +32,9 @@ function RootView:draw(...)
menu:draw() menu:draw()
end end
command.add("core.docview!", { command.add(nil, {
["context:show"] = function(dv) ["context:show"] = function()
menu:show(dv.position.x, dv.position.y) menu:show(core.active_view.position.x, core.active_view.position.y)
end end
}) })
@ -42,24 +42,23 @@ keymap.add {
["menu"] = "context:show" ["menu"] = "context:show"
} }
command.add(function() return menu.show_context_menu == true end, { local function copy_log()
["context:focus-previous"] = function() local item = core.active_view.hovered_item
menu:focus_previous() if item then
end, system.set_clipboard(core.get_log(item))
["context:focus-next"] = function() end
menu:focus_next() end
end,
["context:hide"] = function() local function open_as_doc()
menu:hide() local doc = core.open_doc("logs.txt")
end, core.root_view:open_doc(doc)
["context:on-selected"] = function() doc:insert(1, 1, core.get_log())
menu:call_selected_item() end
end,
menu:register("core.logview", {
{ text = "Copy entry", command = copy_log },
{ text = "Open as file", command = open_as_doc }
}) })
keymap.add { ["return"] = "context:on-selected" }
keymap.add { ["up"] = "context:focus-previous" }
keymap.add { ["down"] = "context:focus-next" }
keymap.add { ["escape"] = "context:hide" }
if require("plugins.scale") then if require("plugins.scale") then
menu:register("core.docview", { menu:register("core.docview", {

View File

@ -1,256 +1,95 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local command = require "core.command" local command = require "core.command"
local common = require "core.common" local common = require "core.common"
local config = require "core.config" local config = require "core.config"
local core_syntax = require "core.syntax"
local DocView = require "core.docview" local DocView = require "core.docview"
local Doc = require "core.doc" local Doc = require "core.doc"
local tokenizer = require "core.tokenizer"
local cache = setmetatable({}, { __mode = "k" }) local cache = setmetatable({}, { __mode = "k" })
local comments_cache = {}
local auto_detect_max_lines = 150
local function indent_occurrences_more_than_once(stat, idx) local function add_to_stat(stat, val)
if stat[idx-1] and stat[idx-1] == stat[idx] then for i = 1, #stat do
return true if val == stat[i][1] then
elseif stat[idx+1] and stat[idx+1] == stat[idx] then stat[i][2] = stat[i][2] + 1
return true return
end
end end
return false stat[#stat + 1] = {val, 1}
end end
local function optimal_indent_from_stat(stat) local function optimal_indent_from_stat(stat)
if #stat == 0 then return nil, 0 end if #stat == 0 then return nil, 0 end
table.sort(stat, function(a, b) return a > b end) local bins = {}
local best_indent = 0 for k = 1, #stat do
local best_score = 0 local indent = stat[k][1]
local count = #stat
for x=1, count do
local indent = stat[x]
local score = 0 local score = 0
for y=1, count do local mult_prev, lines_prev
if y ~= x and stat[y] % indent == 0 then for i = k, #stat do
score = score + 1 if stat[i][1] % indent == 0 then
elseif local mult = stat[i][1] / indent
indent > stat[y] if not mult_prev or (mult_prev + 1 == mult and lines_prev / stat[i][2] > 0.1) then
and -- we add the number of lines to the score only if the previous
indent_occurrences_more_than_once(stat, y) -- multiple of "indent" was populated with enough lines.
then score = score + stat[i][2]
score = 0 end
break mult_prev, lines_prev = mult, stat[i][2]
end end
end end
if score > best_score then bins[#bins + 1] = {indent, score}
best_indent = indent
best_score = score
end
if score > 0 then
break
end
end 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 end
local function escape_comment_tokens(token) -- return nil if it is a comment or blank line or the initial part of the
local special_chars = "*-%[].()+?^$" -- line otherwise.
local escaped = "" -- we don't need to have the whole line to detect indentation.
for x=1, token:len() do local function get_first_line_part(tokens)
local found = false local i, n = 1, #tokens
for y=1, special_chars:len() do while i + 1 <= n do
if token:sub(x, x) == special_chars:sub(y, y) then local ttype, ttext = tokens[i], tokens[i + 1]
escaped = escaped .. "%" .. token:sub(x, x) if ttype ~= "comment" and ttext:gsub("%s+", "") ~= "" then
found = true return ttext
break
end
end
if not found then
escaped = escaped .. token:sub(x, x)
end end
i = i + 2
end end
return escaped
end end
local function get_comment_patterns(syntax)
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)
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) local function get_non_empty_lines(syntax, lines)
return coroutine.wrap(function() return coroutine.wrap(function()
local comments = get_comment_patterns(syntax) local tokens, state
local i = 0 local i = 0
local end_regex = nil
local end_pattern = nil
local inside_comment = false
for _, line in ipairs(lines) do for _, line in ipairs(lines) do
if line:gsub("^%s+", "") ~= "" then tokens, state = tokenizer.tokenize(syntax, line, state)
local is_comment = false local line_start = get_first_line_part(tokens)
if comments then if line_start then
if not inside_comment then i = i + 1
for c=1, #comments do coroutine.yield(i, line_start)
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.match(
comment[2], line, 1, regex.ANCHORED
)
if start then
if not regex.match(
comment[3], line, ending+1, regex.ANCHORED
)
then
is_comment = true
inside_comment = true
end_regex = comment[3]
end
break
end
elseif regex.match(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.match(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
end end
end end
end) end)
end end
local auto_detect_max_lines = 100
local function detect_indent_stat(doc) local function detect_indent_stat(doc)
local stat = {} local stat = {}
local tab_count = 0 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 for i, text in get_non_empty_lines(doc.syntax, doc.lines) do
local spaces = text:match("^ +") local str = text:match("^ %s+%S")
if spaces then table.insert(stat, spaces:len()) end if str then add_to_stat(stat, #str - 1) end
local tabs = text:match("^\t+") local str = text:match("^\t+")
if tabs then tab_count = tab_count + 1 end if str 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
-- Stop parsing when files is very long. Not needed for euristic determination. -- 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 end
table.sort(stat, function(a, b) return a[1] < b[1] end)
local indent, score = optimal_indent_from_stat(stat) local indent, score = optimal_indent_from_stat(stat)
if tab_count > score then if tab_count > score then
return "hard", config.indent_size, tab_count return "hard", config.indent_size, tab_count
@ -262,7 +101,7 @@ end
local function update_cache(doc) local function update_cache(doc)
local type, size, score = detect_indent_stat(doc) local type, size, score = detect_indent_stat(doc)
local score_threshold = 2 local score_threshold = 4
if score < score_threshold then if score < score_threshold then
-- use default values -- use default values
type = config.tab_type type = config.tab_type
@ -291,54 +130,55 @@ end
local function set_indent_type(doc, type) local function set_indent_type(doc, type)
local _, indent_size = doc:get_indent_info() local _, indent_size = doc:get_indent_info()
cache[doc] = { cache[doc] = {type = type,
type = type, size = indent_size,
size = indent_size, confirmed = true}
confirmed = true
}
doc.indent_info = cache[doc] doc.indent_info = cache[doc]
end end
local function set_indent_type_command(dv) local function set_indent_type_command()
core.command_view:enter("Specify indent style for this file", { core.command_view:enter(
submit = function(value) "Specify indent style for this file",
local doc = dv.doc function(value) -- submit
local doc = core.active_view.doc
value = value:lower() value = value:lower()
set_indent_type(doc, value == "tabs" and "hard" or "soft") set_indent_type(doc, value == "tabs" and "hard" or "soft")
end, end,
suggest = function(text) function(text) -- suggest
return common.fuzzy_match({"tabs", "spaces"}, text) return common.fuzzy_match({"tabs", "spaces"}, text)
end, end,
validate = function(text) nil, -- cancel
function(text) -- validate
local t = text:lower() local t = text:lower()
return t == "tabs" or t == "spaces" return t == "tabs" or t == "spaces"
end end
}) )
end end
local function set_indent_size(doc, size) local function set_indent_size(doc, size)
local indent_type = doc:get_indent_info() local indent_type = doc:get_indent_info()
cache[doc] = { cache[doc] = {type = indent_type,
type = indent_type, size = size,
size = size, confirmed = true}
confirmed = true
}
doc.indent_info = cache[doc] doc.indent_info = cache[doc]
end end
local function set_indent_size_command(dv) local function set_indent_size_command()
core.command_view:enter("Specify indent size for current file", { core.command_view:enter(
submit = function(value) "Specify indent size for current file",
value = math.floor(tonumber(value)) function(value) -- submit
local doc = dv.doc local value = math.floor(tonumber(value))
local doc = core.active_view.doc
set_indent_size(doc, value) set_indent_size(doc, value)
end, end,
validate = function(value) nil, -- suggest
value = tonumber(value) nil, -- cancel
function(value) -- validate
local value = tonumber(value)
return value ~= nil and value >= 1 return value ~= nil and value >= 1
end end
}) )
end end
@ -347,24 +187,20 @@ command.add("core.docview", {
["indent:set-file-indent-size"] = set_indent_size_command ["indent:set-file-indent-size"] = set_indent_size_command
}) })
command.add(
function() command.add(function()
return core.active_view:is(DocView) return core.active_view:is(DocView)
and cache[core.active_view.doc] and cache[core.active_view.doc]
and cache[core.active_view.doc].type == "soft" and cache[core.active_view.doc].type == "soft"
end, { end, {
["indent:switch-file-to-tabs-indentation"] = function() ["indent:switch-file-to-tabs-indentation"] = function() set_indent_type(core.active_view.doc, "hard") end
set_indent_type(core.active_view.doc, "hard")
end
}) })
command.add(
function() command.add(function()
return core.active_view:is(DocView) return core.active_view:is(DocView)
and cache[core.active_view.doc] and cache[core.active_view.doc]
and cache[core.active_view.doc].type == "hard" and cache[core.active_view.doc].type == "hard"
end, { end, {
["indent:switch-file-to-spaces-indentation"] = function() ["indent:switch-file-to-spaces-indentation"] = function() set_indent_type(core.active_view.doc, "soft") end
set_indent_type(core.active_view.doc, "soft")
end
}) })

View File

@ -1,304 +1,36 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local style = require "core.style" local style = require "core.style"
local DocView = require "core.docview" local DocView = require "core.docview"
local common = require "core.common" local common = require "core.common"
local config = require "core.config"
local Highlighter = require "core.doc.highlighter"
config.plugins.drawwhitespace = common.merge({
enabled = true,
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 = true
},
{
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 local draw_line_text = DocView.draw_line_text
function DocView:draw_line_text(idx, x, y) 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 = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"])
local font_size = font:get_size() local color = style.syntax.whitespace or style.syntax.comment
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 ty = y + self:get_line_text_y_offset() local ty = y + self:get_line_text_y_offset()
local cache = ws_cache[self.doc.highlighter][idx] local tx
for i=1,#cache,4 do local text, offset, s, e = self.doc.lines[idx], 1
local sub = cache[i] local x1, _, x2, _ = self:get_content_bounds()
local tx = cache[i + 1] + x local _offset = self:get_x_offset_col(idx, x1)
local tw = cache[i + 2] offset = _offset
local color = cache[i + 3] while true do
if tx + tw >= x1 then s, e = text:find(" +", offset)
tx = renderer.draw_text(font, sub, tx, ty, color) if not s then break end
end tx = self:get_col_x_offset(idx, s) + x
if tx > x2 then break end renderer.draw_text(font, string.rep("·", e - s + 1), tx, ty, color)
if tx > x + x2 then break end
offset = e + 1
end end
offset = _offset
return draw_line_text(self, idx, x, y) 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 end

View File

@ -1,13 +1,12 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax" local syntax = require "core.syntax"
syntax.add { syntax.add {
name = "C", name = "C",
files = { "%.c$" }, files = { "%.c$", "%.h$", "%.inl$" },
comment = "//", comment = "//",
block_comment = { "/*", "*/" },
patterns = { patterns = {
{ pattern = "//.*", type = "comment" }, { pattern = "//.-\n", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" },
{ pattern = { '"', '"', '\\' }, type = "string" }, { pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" }, { pattern = { "'", "'", '\\' }, type = "string" },
@ -15,64 +14,12 @@ syntax.add {
{ pattern = "%d+[%d%.eE]*f?", type = "number" }, { pattern = "%d+[%d%.eE]*f?", type = "number" },
{ pattern = "%.?%d+f?", type = "number" }, { pattern = "%.?%d+f?", type = "number" },
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" }, { pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{ pattern = "##", type = "operator" },
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{ pattern = "union%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" }, { 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 = "#include%s()<.->", type = {"keyword", "string"} },
{ pattern = "#[%a_][%w_]*", type = "keyword" },
}, },
symbols = { symbols = {
["if"] = "keyword", ["if"] = "keyword",
@ -97,8 +44,6 @@ syntax.add {
["case"] = "keyword", ["case"] = "keyword",
["default"] = "keyword", ["default"] = "keyword",
["auto"] = "keyword", ["auto"] = "keyword",
["struct"] = "keyword",
["union"] = "keyword",
["void"] = "keyword2", ["void"] = "keyword2",
["int"] = "keyword2", ["int"] = "keyword2",
["short"] = "keyword2", ["short"] = "keyword2",
@ -115,7 +60,6 @@ syntax.add {
["#if"] = "keyword", ["#if"] = "keyword",
["#ifdef"] = "keyword", ["#ifdef"] = "keyword",
["#ifndef"] = "keyword", ["#ifndef"] = "keyword",
["#elif"] = "keyword",
["#else"] = "keyword", ["#else"] = "keyword",
["#elseif"] = "keyword", ["#elseif"] = "keyword",
["#endif"] = "keyword", ["#endif"] = "keyword",

View File

@ -1,4 +1,6 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
pcall(require, "plugins.language_c")
local syntax = require "core.syntax" local syntax = require "core.syntax"
syntax.add { syntax.add {
@ -8,101 +10,28 @@ syntax.add {
"%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$" "%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$"
}, },
comment = "//", comment = "//",
block_comment = { "/*", "*/" },
patterns = { patterns = {
{ pattern = "//.*", type = "comment" }, { pattern = "//.-\n", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" },
{ pattern = { '"', '"', '\\' }, type = "string" }, { pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" }, { pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = "0x%x+", type = "number" }, { pattern = "0x%x+", type = "number" },
{ pattern = "%d+[%d%.'eE]*f?", type = "number" }, { pattern = "%d+[%d%.eE]*f?", type = "number" },
{ pattern = "%.?%d+f?", type = "number" }, { pattern = "%.?%d+f?", type = "number" },
{ pattern = "[%+%-=/%*%^%%<>!~|:&]", type = "operator" }, { pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{ pattern = "##", type = "operator" },
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{ pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{ pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
-- static declarations { pattern = "[%a_][%w_]*::", type = "symbol" },
{ pattern = "static()%s+()inline", { pattern = "::", type = "symbol" },
type = { "keyword", "normal", "keyword" } { pattern = "[%a_][%w_]*", type = "symbol" },
}, { pattern = "#include%s()<.->", type = {"keyword", "string"} },
{ pattern = "static()%s+()const", { pattern = "#[%a_][%w_]*", type = "keyword" },
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" },
}, },
symbols = { symbols = {
["alignof"] = "keyword", ["alignof"] = "keyword",
["alignas"] = "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", ["private"] = "keyword",
["protected"] = "keyword", ["protected"] = "keyword",
["public"] = "keyword", ["public"] = "keyword",
@ -110,12 +39,9 @@ syntax.add {
["nullptr"] = "keyword", ["nullptr"] = "keyword",
["operator"] = "keyword", ["operator"] = "keyword",
["asm"] = "keyword", ["asm"] = "keyword",
["bitand"] = "keyword",
["bitor"] = "keyword",
["catch"] = "keyword", ["catch"] = "keyword",
["throw"] = "keyword", ["throw"] = "keyword",
["try"] = "keyword", ["try"] = "keyword",
["class"] = "keyword",
["compl"] = "keyword", ["compl"] = "keyword",
["explicit"] = "keyword", ["explicit"] = "keyword",
["export"] = "keyword", ["export"] = "keyword",
@ -125,8 +51,8 @@ syntax.add {
["constinit"] = "keyword", ["constinit"] = "keyword",
["const_cast"] = "keyword", ["const_cast"] = "keyword",
["dynamic_cast"] = "keyword", ["dynamic_cast"] = "keyword",
["reinterpret_cast"] = "keyword", ["reinterpret_cast"] = "keyword",
["static_cast"] = "keyword", ["static_cast"] = "keyword",
["static_assert"] = "keyword", ["static_assert"] = "keyword",
["template"] = "keyword", ["template"] = "keyword",
["this"] = "keyword", ["this"] = "keyword",
@ -137,6 +63,7 @@ syntax.add {
["co_yield"] = "keyword", ["co_yield"] = "keyword",
["decltype"] = "keyword", ["decltype"] = "keyword",
["delete"] = "keyword", ["delete"] = "keyword",
["export"] = "keyword",
["friend"] = "keyword", ["friend"] = "keyword",
["typeid"] = "keyword", ["typeid"] = "keyword",
["typename"] = "keyword", ["typename"] = "keyword",
@ -144,7 +71,6 @@ syntax.add {
["override"] = "keyword", ["override"] = "keyword",
["virtual"] = "keyword", ["virtual"] = "keyword",
["using"] = "keyword", ["using"] = "keyword",
["namespace"] = "keyword",
["new"] = "keyword", ["new"] = "keyword",
["noexcept"] = "keyword", ["noexcept"] = "keyword",
["if"] = "keyword", ["if"] = "keyword",
@ -158,8 +84,6 @@ syntax.add {
["continue"] = "keyword", ["continue"] = "keyword",
["return"] = "keyword", ["return"] = "keyword",
["goto"] = "keyword", ["goto"] = "keyword",
["struct"] = "keyword",
["union"] = "keyword",
["typedef"] = "keyword", ["typedef"] = "keyword",
["enum"] = "keyword", ["enum"] = "keyword",
["extern"] = "keyword", ["extern"] = "keyword",
@ -171,6 +95,7 @@ syntax.add {
["case"] = "keyword", ["case"] = "keyword",
["default"] = "keyword", ["default"] = "keyword",
["auto"] = "keyword", ["auto"] = "keyword",
["const"] = "keyword",
["void"] = "keyword2", ["void"] = "keyword2",
["int"] = "keyword2", ["int"] = "keyword2",
["short"] = "keyword2", ["short"] = "keyword2",
@ -180,18 +105,12 @@ syntax.add {
["char"] = "keyword2", ["char"] = "keyword2",
["unsigned"] = "keyword2", ["unsigned"] = "keyword2",
["bool"] = "keyword2", ["bool"] = "keyword2",
["true"] = "literal", ["true"] = "keyword2",
["false"] = "literal", ["false"] = "keyword2",
["NULL"] = "literal",
["wchar_t"] = "keyword2",
["char8_t"] = "keyword2",
["char16_t"] = "keyword2",
["char32_t"] = "keyword2",
["#include"] = "keyword", ["#include"] = "keyword",
["#if"] = "keyword", ["#if"] = "keyword",
["#ifdef"] = "keyword", ["#ifdef"] = "keyword",
["#ifndef"] = "keyword", ["#ifndef"] = "keyword",
["#elif"] = "keyword",
["#else"] = "keyword", ["#else"] = "keyword",
["#elseif"] = "keyword", ["#elseif"] = "keyword",
["#endif"] = "keyword", ["#endif"] = "keyword",
@ -199,5 +118,6 @@ syntax.add {
["#warning"] = "keyword", ["#warning"] = "keyword",
["#error"] = "keyword", ["#error"] = "keyword",
["#pragma"] = "keyword", ["#pragma"] = "keyword",
}, },
} }

View File

@ -1,13 +1,12 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax" local syntax = require "core.syntax"
syntax.add { syntax.add {
name = "CSS", name = "CSS",
files = { "%.css$" }, files = { "%.css$" },
block_comment = { "/*", "*/" },
patterns = { patterns = {
{ pattern = "\\.", type = "normal" }, { pattern = "\\.", type = "normal" },
{ pattern = "//.*", type = "comment" }, { pattern = "//.-\n", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" },
{ pattern = { '"', '"', '\\' }, type = "string" }, { pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" }, { pattern = { "'", "'", '\\' }, type = "string" },

View File

@ -1,23 +1,31 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax" local syntax = require "core.syntax"
syntax.add { syntax.add {
name = "HTML", name = "HTML",
files = { "%.html?$" }, files = { "%.html?$" },
block_comment = { "<!--", "-->" },
patterns = { patterns = {
{ {
pattern = { pattern = {
"<%s*[sS][cC][rR][iI][pP][tT]%f[%s>].->", "<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*" ..
"<%s*/%s*[sS][cC][rR][iI][pP][tT]%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", syntax = ".js",
type = "function" type = "function"
}, },
{ {
pattern = { pattern = {
"<%s*[sS][tT][yY][lL][eE]%f[%s>].->", "<%s*[sS][tT][yY][lL][eE][^>]*>",
"<%s*/%s*[sS][tT][yY][lL][eE]%s*>" "<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
}, },
syntax = ".css", syntax = ".css",
type = "function" type = "function"

View File

@ -1,13 +1,12 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax" local syntax = require "core.syntax"
syntax.add { syntax.add {
name = "JavaScript", name = "JavaScript",
files = { "%.js$", "%.json$", "%.cson$", "%.mjs$", "%.cjs$" }, files = { "%.js$", "%.json$", "%.cson$" },
comment = "//", comment = "//",
block_comment = { "/*", "*/" },
patterns = { patterns = {
{ pattern = "//.*", type = "comment" }, { pattern = "//.-\n", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" },
{ pattern = { '/[^= ]', '/', '\\' },type = "string" }, { pattern = { '/[^= ]', '/', '\\' },type = "string" },
{ pattern = { '"', '"', '\\' }, type = "string" }, { pattern = { '"', '"', '\\' }, type = "string" },

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax" local syntax = require "core.syntax"
syntax.add { syntax.add {
@ -6,13 +6,12 @@ syntax.add {
files = "%.lua$", files = "%.lua$",
headers = "^#!.*[ /]lua", headers = "^#!.*[ /]lua",
comment = "--", comment = "--",
block_comment = { "--[[", "]]" },
patterns = { patterns = {
{ pattern = { '"', '"', '\\' }, type = "string" }, { pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" }, { pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = { "%[%[", "%]%]" }, type = "string" }, { pattern = { "%[%[", "%]%]" }, type = "string" },
{ pattern = { "%-%-%[%[", "%]%]"}, type = "comment" }, { pattern = { "%-%-%[%[", "%]%]"}, type = "comment" },
{ pattern = "%-%-.*", type = "comment" }, { pattern = "%-%-.-\n", type = "comment" },
{ pattern = "0x%x+%.%x*[pP][-+]?%d+", type = "number" }, { pattern = "0x%x+%.%x*[pP][-+]?%d+", type = "number" },
{ pattern = "0x%x+%.%x*", type = "number" }, { pattern = "0x%x+%.%x*", type = "number" },
{ pattern = "0x%.%x+[pP][-+]?%d+", type = "number" }, { pattern = "0x%.%x+[pP][-+]?%d+", type = "number" },

View File

@ -1,235 +1,56 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax" local syntax = require "core.syntax"
local style = require "core.style"
local core = require "core"
local initial_color = style.syntax["keyword2"]
-- Add 3 type of font styles for use on markdown files
for _, attr in pairs({"bold", "italic", "bold_italic"}) do
local attributes = {}
if attr ~= "bold_italic" then
attributes[attr] = true
else
attributes["bold"] = true
attributes["italic"] = true
end
style.syntax_fonts["markdown_"..attr] = style.code_font:copy(
style.code_font:get_size(),
attributes
)
-- also add a color for it
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
end
local in_squares_match = "^%[%]"
local in_parenthesis_match = "^%(%)"
syntax.add { syntax.add {
name = "Markdown", name = "Markdown",
files = { "%.md$", "%.markdown$" }, files = { "%.md$", "%.markdown$" },
block_comment = { "<!--", "-->" },
space_handling = false, -- turn off this feature to handle it our selfs
patterns = { patterns = {
---- Place patterns that require spaces at start to optimize matching speed { pattern = "\\.", type = "normal" },
---- and apply the %s+ optimization immediately afterwards { pattern = { "<!%-%-", "%-%->" }, type = "comment" },
-- 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___", "___%f[%s]" }, type = "markdown_bold_italic" },
{ pattern = { "%s__", "__%f[%s]" }, type = "markdown_bold" },
{ pattern = { "%s_[%S]", "_%f[%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 = { "```c++", "```" }, type = "string", syntax = ".cpp" }, { pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
{ pattern = { "```cpp", "```" }, type = "string", syntax = ".cpp" },
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" }, { pattern = { "```python", "```" }, type = "string", syntax = ".py" },
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" }, { pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
{ pattern = { "```perl", "```" }, type = "string", syntax = ".pl" }, { pattern = { "```perl", "```" }, type = "string", syntax = ".pl" },
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" }, { pattern = { "```php", "```" }, type = "string", syntax = ".php" },
{ pattern = { "```javascript", "```" }, type = "string", syntax = ".js" }, { pattern = { "```javascript", "```" }, type = "string", syntax = ".js" },
{ pattern = { "```json", "```" }, type = "string", syntax = ".js" },
{ pattern = { "```html", "```" }, type = "string", syntax = ".html" }, { pattern = { "```html", "```" }, type = "string", syntax = ".html" },
{ pattern = { "```ini", "```" }, type = "string", syntax = ".ini" },
{ pattern = { "```xml", "```" }, type = "string", syntax = ".xml" }, { pattern = { "```xml", "```" }, type = "string", syntax = ".xml" },
{ pattern = { "```css", "```" }, type = "string", syntax = ".css" }, { pattern = { "```css", "```" }, type = "string", syntax = ".css" },
{ pattern = { "```lua", "```" }, type = "string", syntax = ".lua" }, { pattern = { "```lua", "```" }, type = "string", syntax = ".lua" },
{ pattern = { "```bash", "```" }, type = "string", syntax = ".sh" }, { pattern = { "```bash", "```" }, type = "string", syntax = ".sh" },
{ pattern = { "```sh", "```" }, type = "string", syntax = ".sh" },
{ pattern = { "```java", "```" }, type = "string", syntax = ".java" }, { pattern = { "```java", "```" }, type = "string", syntax = ".java" },
{ pattern = { "```c#", "```" }, type = "string", syntax = ".cs" }, { pattern = { "```c#", "```" }, type = "string", syntax = ".cs" },
{ pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" }, { pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" },
{ pattern = { "```d", "```" }, type = "string", syntax = ".d" }, { pattern = { "```d", "```" }, type = "string", syntax = ".d" },
{ pattern = { "```glsl", "```" }, type = "string", syntax = ".glsl" }, { pattern = { "```glsl", "```" }, type = "string", syntax = ".glsl" },
{ pattern = { "```c", "```" }, type = "string", syntax = ".c" }, { pattern = { "```c", "```" }, type = "string", syntax = ".c" },
{ pattern = { "```julia", "```" }, type = "string", syntax = ".jl" }, { pattern = { "```julia", "```" }, type = "string", syntax = ".jl" },
{ pattern = { "```rust", "```" }, type = "string", syntax = ".rs" }, { pattern = { "```rust", "```" }, type = "string", syntax = ".rs" },
{ pattern = { "```dart", "```" }, type = "string", syntax = ".dart" }, { pattern = { "```dart", "```" }, type = "string", syntax = ".dart" },
{ pattern = { "```v", "```" }, type = "string", syntax = ".v" }, { pattern = { "```v", "```" }, type = "string", syntax = ".v" },
{ pattern = { "```toml", "```" }, type = "string", syntax = ".toml" }, { pattern = { "```toml", "```" }, type = "string", syntax = ".toml" },
{ pattern = { "```yaml", "```" }, type = "string", syntax = ".yaml" }, { pattern = { "```yaml", "```" }, type = "string", syntax = ".yaml" },
{ pattern = { "```nim", "```" }, type = "string", syntax = ".nim" }, { pattern = { "```php", "```" }, type = "string", syntax = ".php" },
{ pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" }, { pattern = { "```nim", "```" }, type = "string", syntax = ".nim" },
{ pattern = { "```rescript", "```" }, type = "string", syntax = ".res" }, { pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" },
{ pattern = { "```moon", "```" }, type = "string", syntax = ".moon" }, { pattern = { "```rescript", "```" }, type = "string", syntax = ".res" },
{ pattern = { "```go", "```" }, type = "string", syntax = ".go" }, { pattern = { "```moon", "```" }, type = "string", syntax = ".moon" },
{ pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" }, { pattern = { "```go", "```" }, type = "string", syntax = ".go" },
{ pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" }, { pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" },
{ pattern = { "```", "```" }, type = "string" }, { pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" },
{ pattern = { "``", "``" }, type = "string" }, { pattern = { "```", "```" }, type = "string" },
{ pattern = { "%f[\\`]%`[%S]", "`" }, type = "string" }, { pattern = { "``", "``", "\\" }, type = "string" },
-- strike { pattern = { "`", "`", "\\" }, type = "string" },
{ pattern = { "~~", "~~" }, type = "keyword2" }, { pattern = { "~~", "~~", "\\" }, type = "keyword2" },
-- highlight { pattern = "%-%-%-+", type = "comment" },
{ pattern = { "==", "==" }, type = "literal" }, { pattern = "%*%s+", type = "operator" },
-- lines { pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
{ pattern = "^%-%-%-+$" , type = "comment" }, { pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
{ pattern = "^%*%*%*+$", type = "comment" }, { pattern = "#.-\n", type = "keyword" },
{ pattern = "^___+$", type = "comment" }, { pattern = "!?%[.-%]%(.-%)", type = "function" },
-- 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]+___%s" , type = "markdown_bold_italic" },
{ pattern = "^__[%s%p%w]+__%s" , type = "markdown_bold" },
{ pattern = "^_[%s%p%w]+_%s" , type = "markdown_italic" },
-- heading with custom id
{
pattern = "^#+%s[%w%s%p]+(){()#[%w%-]+()}",
type = { "keyword", "function", "string", "function" }
},
-- headings
{ pattern = "^#+%s.+$", 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 = "https?://%S+", type = "function" }, { pattern = "https?://%S+", type = "function" },
-- optimize consecutive dashes used in tables
{ pattern = "%-+", type = "normal" },
}, },
symbols = { }, symbols = { },
} }
-- Adjust the color on theme changes
core.add_thread(function()
while true do
if initial_color ~= style.syntax["keyword2"] then
for _, attr in pairs({"bold", "italic", "bold_italic"}) do
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
end
initial_color = style.syntax["keyword2"]
end
coroutine.yield(1)
end
end)

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax" local syntax = require "core.syntax"
syntax.add { syntax.add {
@ -6,14 +6,9 @@ syntax.add {
files = { "%.py$", "%.pyw$", "%.rpy$" }, files = { "%.py$", "%.pyw$", "%.rpy$" },
headers = "^#!.*[ /]python", headers = "^#!.*[ /]python",
comment = "#", comment = "#",
block_comment = { '"""', '"""' },
patterns = { patterns = {
{ pattern = "#.*", type = "comment" }, { pattern = { "#", "\n" }, type = "comment" },
{ pattern = { '^%s*"""', '"""' }, type = "comment" },
{ pattern = '[uUrR]%f["]', type = "keyword" },
{ pattern = "class%s+()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{ pattern = { '[ruU]?"""', '"""'; '\\' }, type = "string" }, { pattern = { '[ruU]?"""', '"""'; '\\' }, type = "string" },
{ pattern = { "[ruU]?'''", "'''", '\\' }, type = "string" },
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" }, { pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" }, { pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
{ pattern = "0x[%da-fA-F]+", type = "number" }, { pattern = "0x[%da-fA-F]+", type = "number" },
@ -33,8 +28,6 @@ syntax.add {
["lambda"] = "keyword", ["lambda"] = "keyword",
["try"] = "keyword", ["try"] = "keyword",
["def"] = "keyword", ["def"] = "keyword",
["async"] = "keyword",
["await"] = "keyword",
["from"] = "keyword", ["from"] = "keyword",
["nonlocal"] = "keyword", ["nonlocal"] = "keyword",
["while"] = "keyword", ["while"] = "keyword",
@ -47,8 +40,6 @@ syntax.add {
["if"] = "keyword", ["if"] = "keyword",
["or"] = "keyword", ["or"] = "keyword",
["else"] = "keyword", ["else"] = "keyword",
["match"] = "keyword",
["case"] = "keyword",
["import"] = "keyword", ["import"] = "keyword",
["pass"] = "keyword", ["pass"] = "keyword",
["break"] = "keyword", ["break"] = "keyword",

View File

@ -1,11 +1,10 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax" local syntax = require "core.syntax"
syntax.add { syntax.add {
name = "XML", name = "XML",
files = { "%.xml$" }, files = { "%.xml$" },
headers = "<%?xml", headers = "<%?xml",
block_comment = { "<!--", "-->" },
patterns = { patterns = {
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" }, { pattern = { "<!%-%-", "%-%->" }, type = "comment" },
{ pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" }, { pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },

View File

@ -1,115 +1,21 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local common = require "core.common"
local command = require "core.command"
local config = require "core.config" local config = require "core.config"
local style = require "core.style" local style = require "core.style"
local DocView = require "core.docview" local DocView = require "core.docview"
local CommandView = require "core.commandview" 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 local draw_overlay = DocView.draw_overlay
function DocView:draw_overlay(...) function DocView:draw_overlay(...)
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
draw_overlay(self, ...) draw_overlay(self, ...)
if
type(config.plugins.lineguide) == "table"
and
config.plugins.lineguide.enabled
and
not self:is(CommandView)
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
end
end end
command.add(nil, {
["lineguide:toggle"] = function()
config.plugins.lineguide.enabled = not config.plugins.lineguide.enabled
end
})

View File

@ -1,581 +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 x,y,w,h = docview:get_scrollbar_rect()
local width = (type(config.plugins.linewrapping.width_override) == "function" and config.plugins.linewrapping.width_override(docview))
or config.plugins.linewrapping.width_override or (docview.size.x - docview:get_gutter_width() - w)
if (not docview.wrapped_settings or docview.wrapped_settings.width == nil or width ~= docview.wrapped_settings.width) then
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 = {}
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_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
LineWrapping.update_docview_breaks(self)
end
end
local old_scroll_to_make_visible = DocView.scroll_to_make_visible
function DocView:scroll_to_make_visible(line, col)
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 = 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)
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
x2 = x + self:get_col_x_offset(line, get_idx_line_length(self, i, line) + 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
local _, _, count = get_line_idx_col_count(self, line)
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
LineWrapping.update_docview_breaks(core.active_view)
end
end,
["line-wrapping:disable"] = function()
if core.active_view and core.active_view.doc then
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

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local command = require "core.command" local command = require "core.command"
local keymap = require "core.keymap" local keymap = require "core.keymap"

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local common = require "core.common" local common = require "core.common"
local keymap = require "core.keymap" local keymap = require "core.keymap"
@ -11,11 +11,11 @@ local ResultsView = View:extend()
ResultsView.context = "session" ResultsView.context = "session"
function ResultsView:new(path, text, fn) function ResultsView:new(text, fn)
ResultsView.super.new(self) ResultsView.super.new(self)
self.scrollable = true self.scrollable = true
self.brightness = 0 self.brightness = 0
self:begin_search(path, text, fn) self:begin_search(text, fn)
end end
@ -45,8 +45,8 @@ local function find_all_matches_in_file(t, filename, fn)
end end
function ResultsView:begin_search(path, text, fn) function ResultsView:begin_search(text, fn)
self.search_args = { path, text, fn } self.search_args = { text, fn }
self.results = {} self.results = {}
self.last_file_idx = 1 self.last_file_idx = 1
self.query = text self.query = text
@ -56,9 +56,9 @@ function ResultsView:begin_search(path, text, fn)
core.add_thread(function() core.add_thread(function()
local i = 1 local i = 1
for dir_name, file in core.get_project_files() do 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 if file.type == "file" then
local truncated_path = (dir_name == core.project_dir and "" or (dir_name .. PATHSEP)) local path = (dir_name == core.project_dir and "" or (dir_name .. PATHSEP))
find_all_matches_in_file(self.results, truncated_path .. file.filename, fn) find_all_matches_in_file(self.results, path .. file.filename, fn)
end end
self.last_file_idx = i self.last_file_idx = i
i = i + 1 i = i + 1
@ -176,7 +176,7 @@ function ResultsView:draw()
local text local text
if self.searching then if self.searching then
if files_number 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, per * 100, self.last_file_idx, files_number,
#self.results, self.query) #self.results, self.query)
else else
@ -219,72 +219,41 @@ function ResultsView:draw()
end end
local function begin_search(path, text, fn) local function begin_search(text, fn)
if text == "" then if text == "" then
core.error("Expected non-empty string") core.error("Expected non-empty string")
return return
end end
local rv = ResultsView(path, text, fn) local rv = ResultsView(text, fn)
core.root_view:get_active_node_default():add_view(rv) core.root_view:get_active_node_default():add_view(rv)
end 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
command.add(nil, { command.add(nil, {
["project-search:find"] = function(path) ["project-search:find"] = function()
core.command_view:enter("Find Text In " .. (normalize_path(path) or "Project"), { core.command_view:enter("Find Text In Project", function(text)
text = get_selected_text(), text = text:lower()
select_text = true, begin_search(text, function(line_text)
submit = function(text) return line_text:lower():find(text, nil, true)
text = text:lower() end)
begin_search(path, text, function(line_text) end)
return line_text:lower():find(text, nil, true)
end)
end
})
end, end,
["project-search:find-regex"] = function(path) ["project-search:find-regex"] = function()
core.command_view:enter("Find Regex In " .. (normalize_path(path) or "Project"), { core.command_view:enter("Find Regex In Project", function(text)
submit = function(text) local re = regex.compile(text, "i")
local re = regex.compile(text, "i") begin_search(text, function(line_text)
begin_search(path, text, function(line_text) return regex.cmatch(re, line_text)
return regex.cmatch(re, line_text) end)
end) end)
end
})
end, end,
["project-search:fuzzy-find"] = function(path) ["project-search:fuzzy-find"] = function()
core.command_view:enter("Fuzzy Find Text In " .. (normalize_path(path) or "Project"), { core.command_view:enter("Fuzzy Find Text In Project", function(text)
text = get_selected_text(), begin_search(text, function(line_text)
select_text = true, return common.fuzzy_match(line_text, text) and 1
submit = function(text) end)
begin_search(path, text, function(line_text) end)
return common.fuzzy_match(line_text, text) and 1
end)
end
})
end, end,
}) })
@ -309,22 +278,22 @@ command.add(ResultsView, {
["project-search:refresh"] = function() ["project-search:refresh"] = function()
core.active_view:refresh() core.active_view:refresh()
end, end,
["project-search:move-to-previous-page"] = function() ["project-search:move-to-previous-page"] = function()
local view = core.active_view local view = core.active_view
view.scroll.to.y = view.scroll.to.y - view.size.y view.scroll.to.y = view.scroll.to.y - view.size.y
end, end,
["project-search:move-to-next-page"] = function() ["project-search:move-to-next-page"] = function()
local view = core.active_view local view = core.active_view
view.scroll.to.y = view.scroll.to.y + view.size.y view.scroll.to.y = view.scroll.to.y + view.size.y
end, end,
["project-search:move-to-start-of-doc"] = function() ["project-search:move-to-start-of-doc"] = function()
local view = core.active_view local view = core.active_view
view.scroll.to.y = 0 view.scroll.to.y = 0
end, end,
["project-search:move-to-end-of-doc"] = function() ["project-search:move-to-end-of-doc"] = function()
local view = core.active_view local view = core.active_view
view.scroll.to.y = view:get_scrollable_size() view.scroll.to.y = view:get_scrollable_size()

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local command = require "core.command" local command = require "core.command"
local keymap = require "core.keymap" local keymap = require "core.keymap"
@ -19,8 +19,8 @@ end
command.add("core.docview", { command.add("core.docview", {
["quote:quote"] = function(dv) ["quote:quote"] = function()
dv.doc:replace(function(text) core.active_view.doc:replace(function(text)
return '"' .. text:gsub("[\0-\31\\\"]", replace) .. '"' return '"' .. text:gsub("[\0-\31\\\"]", replace) .. '"'
end) end)
end, end,

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local config = require "core.config" local config = require "core.config"
local command = require "core.command" local command = require "core.command"
@ -25,8 +25,8 @@ end
command.add("core.docview", { command.add("core.docview", {
["reflow:reflow"] = function(dv) ["reflow:reflow"] = function()
local doc = dv.doc local doc = core.active_view.doc
doc:replace(function(text) doc:replace(function(text)
local prefix_set = "[^%w\n%[%](){}`'\"]*" local prefix_set = "[^%w\n%[%](){}`'\"]*"

View File

@ -1,20 +1,17 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local common = require "core.common" local common = require "core.common"
local command = require "core.command" local command = require "core.command"
local config = require "core.config" local config = require "core.config"
local keymap = require "core.keymap" local keymap = require "core.keymap"
local style = require "core.style" local style = require "core.style"
local RootView = require "core.rootview"
local CommandView = require "core.commandview" local CommandView = require "core.commandview"
config.plugins.scale = common.merge({ config.plugins.scale = {
-- The method used to apply the scaling: "code", "ui"
mode = "code", mode = "code",
-- Default scale applied at startup.
default_scale = "autodetect",
-- Allow using CTRL + MouseWheel for changing the scale.
use_mousewheel = true use_mousewheel = true
}, config.plugins.scale) }
local scale_steps = 0.05 local scale_steps = 0.05
@ -47,14 +44,18 @@ local function set_scale(scale)
style.tab_width = style.tab_width * s style.tab_width = style.tab_width * s
for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do
style[name]:set_size(s * style[name]:get_size()) style[name] = renderer.font.copy(style[name], s * style[name]:get_size())
end end
else 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 end
for name, font in pairs(style.syntax_fonts) do for _, font in pairs(style.syntax_fonts) do
style.syntax_fonts[name]:set_size(s * font:get_size()) 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 end
-- restore scroll positions -- restore scroll positions
@ -82,75 +83,6 @@ local function dec_scale()
set_scale(current_scale - scale_steps) set_scale(current_scale - scale_steps)
end 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, { command.add(nil, {
["scale:reset" ] = function() res_scale() end, ["scale:reset" ] = function() res_scale() end,
@ -161,16 +93,11 @@ command.add(nil, {
keymap.add { keymap.add {
["ctrl+0"] = "scale:reset", ["ctrl+0"] = "scale:reset",
["ctrl+-"] = "scale:decrease", ["ctrl+-"] = "scale:decrease",
["ctrl+="] = "scale:increase" ["ctrl+="] = "scale:increase",
["ctrl+wheelup"] = "scale:increase",
["ctrl+wheeldown"] = "scale:decrease"
} }
if config.plugins.scale.use_mousewheel then
keymap.add {
["ctrl+wheelup"] = "scale:increase",
["ctrl+wheeldown"] = "scale:decrease"
}
end
return { return {
["set"] = set_scale, ["set"] = set_scale,
["get"] = get_scale, ["get"] = get_scale,

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local command = require "core.command" local command = require "core.command"
local translate = require "core.doc.translate" local translate = require "core.doc.translate"
@ -41,23 +41,21 @@ end
command.add("core.docview", { command.add("core.docview", {
["tabularize:tabularize"] = function(dv) ["tabularize:tabularize"] = function()
core.command_view:enter("Tabularize On Delimiter", { core.command_view:enter("Tabularize On Delimiter", function(delim)
submit = function(delim) if delim == "" then delim = " " end
if delim == "" then delim = " " end
local doc = dv.doc local doc = core.active_view.doc
local line1, col1, line2, col2, swap = doc:get_selection(true) local line1, col1, line2, col2, swap = doc:get_selection(true)
line1, col1 = doc:position_offset(line1, col1, translate.start_of_line) line1, col1 = doc:position_offset(line1, col1, translate.start_of_line)
line2, col2 = doc:position_offset(line2, col2, translate.end_of_line) line2, col2 = doc:position_offset(line2, col2, translate.end_of_line)
doc:set_selection(line1, col1, line2, col2, swap) doc:set_selection(line1, col1, line2, col2, swap)
doc:replace(function(text) doc:replace(function(text)
local lines = gmatch_to_array(text, "[^\n]*\n?") local lines = gmatch_to_array(text, "[^\n]*\n?")
tabularize_lines(lines, delim) tabularize_lines(lines, delim)
return table.concat(lines) return table.concat(lines)
end) end)
end end)
})
end, end,
}) })

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local common = require "core.common" local common = require "core.common"
local command = require "core.command" local command = require "core.command"
@ -7,26 +7,31 @@ local View = require "core.view"
local ToolbarView = View:extend() 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() function ToolbarView:new()
ToolbarView.super.new(self) ToolbarView.super.new(self)
self.visible = true self.visible = true
self.init_size = true self.init_size = true
self.tooltip = false 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 end
function ToolbarView:update() 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 if self.init_size then
self.size.y = dest_size self.size.y = dest_size
self.init_size = nil self.init_size = nil
@ -41,24 +46,19 @@ function ToolbarView:toggle_visible()
self.visible = not self.visible self.visible = not self.visible
end 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() 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 toolbar_spacing = icon_w / 2
local ox, oy = self:get_content_offset() local ox, oy = self:get_content_offset()
local index = 0 local index = 0
local iter = function() local iter = function()
index = index + 1 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 dx = style.padding.x + (icon_w + toolbar_spacing) * (index - 1)
local dy = style.padding.y local dy = style.padding.y
if dx + icon_w > self.size.x then return end 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
end end
return iter return iter
@ -66,9 +66,9 @@ end
function ToolbarView:get_min_width() 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 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 end
@ -76,20 +76,19 @@ function ToolbarView:draw()
self:draw_background(style.background2) self:draw_background(style.background2)
for item, x, y, w, h in self:each_item() do 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 local color = item == self.hovered_item and style.text or style.dim
common.draw_text(self.toolbar_font, color, item.symbol, nil, x, y, 0, h) common.draw_text(style.icon_big_font, color, item.symbol, nil, x, y, 0, h)
end end
end end
function ToolbarView:on_mouse_pressed(button, x, y, clicks) function ToolbarView:on_mouse_pressed(button, x, y, clicks)
local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks) 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) 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) command.perform(self.hovered_item.command)
end end
return true
end end

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local common = require "core.common" local common = require "core.common"
local command = require "core.command" local command = require "core.command"
@ -8,13 +8,9 @@ local style = require "core.style"
local View = require "core.view" local View = require "core.view"
local ContextMenu = require "core.contextmenu" local ContextMenu = require "core.contextmenu"
local RootView = require "core.rootview" 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_offset = style.font:get_height()
local tooltip_border = 1 local tooltip_border = 1
local tooltip_delay = 0.5 local tooltip_delay = 0.5
@ -43,26 +39,19 @@ function TreeView:new()
self.scrollable = true self.scrollable = true
self.visible = true self.visible = true
self.init_size = true self.init_size = true
self.target_size = config.plugins.treeview.size self.target_size = default_treeview_size
self.cache = {} self.cache = {}
self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 } self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 }
self.cursor_pos = { x = 0, y = 0 }
self.item_icon_width = 0 self.item_icon_width = 0
self.item_text_spacing = 0 self.item_text_spacing = 0
self:add_core_hooks() local on_dirmonitor_modify = core.on_dirmonitor_modify
end function core.on_dirmonitor_modify(dir, filepath)
if self.cache[dir.name] then
self.cache[dir.name][filepath] = nil
function TreeView:add_core_hooks() end
-- When a file or directory is deleted we delete the corresponding cache entry on_dirmonitor_modify(dir, filepath)
-- because if the entry is recreated we may use wrong information from cache.
local on_delete = core.on_dirmonitor_delete
core.on_dirmonitor_delete = function(dir, filepath)
local cache = self.cache[dir.name]
if cache then cache[filepath] = nil end
on_delete(dir, filepath)
end end
end end
@ -101,7 +90,7 @@ function TreeView:get_cached(dir, item, dirname)
end end
t.name = basename t.name = basename
t.type = item.type 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 dir_cache[cache_name] = t
end end
return t return t
@ -109,7 +98,7 @@ end
function TreeView:get_name() function TreeView:get_name()
return nil return "Project"
end end
@ -153,51 +142,34 @@ function TreeView:each_item()
count_lines = count_lines + 1 count_lines = count_lines + 1
y = y + h y = y + h
local i = 1 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
while i <= #dir.files and dir_cached.expanded do local item = dir.files[i]
local item = dir.files[i] local cached = self:get_cached(dir, item, dir.name)
local cached = self:get_cached(dir, item, dir.name)
coroutine.yield(cached, ox, y, w, h) coroutine.yield(cached, ox, y, w, h)
count_lines = count_lines + 1 count_lines = count_lines + 1
y = y + h y = y + h
i = i + 1 i = i + 1
if not cached.expanded then if not cached.expanded then
if cached.skip then if cached.skip then
i = cached.skip i = cached.skip
else else
local depth = cached.depth local depth = cached.depth
while i <= #dir.files do while i <= #dir.files do
if get_depth(dir.files[i].filename) <= depth then break end if get_depth(dir.files[i].filename) <= depth then break end
i = i + 1 i = i + 1
end
cached.skip = i
end end
cached.skip = i
end end
end -- while files end
end end -- while files
end -- for directories end -- for directories
self.count_lines = count_lines self.count_lines = count_lines
end) end)
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) function TreeView:get_text_bounding_box(item, x, y, w, h)
local icon_width = style.icon_font:get_width("D") local icon_width = style.icon_font:get_width("D")
local xoffset = item.depth * style.padding.x + style.padding.x + icon_width local xoffset = item.depth * style.padding.x + style.padding.x + icon_width
@ -208,14 +180,8 @@ end
function TreeView:on_mouse_moved(px, py, ...) function TreeView:on_mouse_moved(px, py, ...)
if not self.visible then return end
TreeView.super.on_mouse_moved(self, px, py, ...) TreeView.super.on_mouse_moved(self, px, py, ...)
self.cursor_pos.x = px if self.dragging_scrollbar then return end
self.cursor_pos.y = py
if self.dragging_scrollbar then
self.hovered_item = nil
return
end
local item_changed, tooltip_changed local item_changed, tooltip_changed
for item, x,y,w,h in self:each_item() do for item, x,y,w,h in self:each_item() do
@ -237,6 +203,50 @@ function TreeView:on_mouse_moved(px, py, ...)
end 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() function TreeView:update()
-- update width -- update width
local dest = self.visible and self.target_size or 0 local dest = self.visible and self.target_size or 0
@ -244,14 +254,12 @@ function TreeView:update()
self.size.x = dest self.size.x = dest
self.init_size = false self.init_size = false
else else
self:move_towards(self.size, "x", dest, nil, "treeview") self:move_towards(self.size, "x", dest)
end end
if not self.visible then return end
local duration = system.get_time() - self.tooltip.begin local duration = system.get_time() - self.tooltip.begin
if self.hovered_item and self.tooltip.x and duration > tooltip_delay then 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 else
self.tooltip.alpha = 0 self.tooltip.alpha = 0
end end
@ -259,13 +267,6 @@ function TreeView:update()
self.item_icon_width = style.icon_font:get_width("D") self.item_icon_width = style.icon_font:get_width("D")
self.item_text_spacing = style.icon_font:get_width("f") / 2 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) TreeView.super.update(self)
end end
@ -349,10 +350,6 @@ end
function TreeView:draw_item_background(item, active, hovered, x, y, w, h) function TreeView:draw_item_background(item, active, hovered, x, y, w, h)
if hovered then 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) renderer.draw_rect(x, y, w, h, style.line_highlight)
end end
end end
@ -369,7 +366,6 @@ end
function TreeView:draw() function TreeView:draw()
if not self.visible then return end
self:draw_background(style.background2) self:draw_background(style.background2)
local _y, _h = self.position.y, self.size.y local _y, _h = self.position.y, self.size.y
@ -379,86 +375,23 @@ function TreeView:draw()
for item, x,y,w,h in self:each_item() do for item, x,y,w,h in self:each_item() do
if y + h >= _y and y < _y + _h then if y + h >= _y and y < _y + _h then
self:draw_item(item, self:draw_item(item,
item == self.selected_item, item.abs_filename == active_filename,
item == self.hovered_item, item == self.hovered_item,
x, y, w, h) x, y, w, h)
end end
end end
self:draw_scrollbar() 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) core.root_view:defer_draw(self.draw_tooltip, self)
end end
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 -- init
local view = TreeView() local view = TreeView()
local node = core.root_view:get_active_node() 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 -- The toolbarview plugin is special because it is plugged inside
-- a treeview pane which is itelf provided in a plugin. -- a treeview pane which is itelf provided in a plugin.
@ -467,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 -- plugin module that plug itself in the active node but it is plugged here
-- in the treeview node. -- in the treeview node.
local toolbar_view = nil 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 if config.plugins.toolbarview ~= false and toolbar_plugin then
toolbar_view = ToolbarView() 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() 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, { command.add(nil, {
["toolbar:toggle"] = function() ["toolbar:toggle"] = function()
toolbar_view:toggle_visible() toolbar_view:toggle_visible()
@ -513,22 +446,7 @@ function RootView:draw(...)
menu:draw() menu:draw()
end 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) 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 return core.project_dir == path
end end
@ -558,143 +476,65 @@ menu:register(
} }
) )
menu:register(
function()
return view.hovered_item
and not is_primary_project_folder(view.hovered_item.abs_filename)
and is_project_folder(view.hovered_item.abs_filename)
end,
{
{ text = "Remove directory", command = "treeview:remove-project-directory" },
}
)
local previous_view = nil
-- Register the TreeView commands and keymap -- Register the TreeView commands and keymap
command.add(nil, { command.add(nil, {
["treeview:toggle"] = function() ["treeview:toggle"] = function()
view.visible = not view.visible view.visible = not view.visible
end, end})
["treeview:toggle-focus"] = function()
if not core.active_view:is(TreeView) then command.add(function() return view.hovered_item ~= nil end, {
if core.active_view:is(CommandView) then ["treeview:rename"] = function()
previous_view = core.last_active_view 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 else
previous_view = core.active_view core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
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
end end
end, common.path_suggest)
end,
else ["treeview:new-file"] = function()
core.set_active_view( if not is_project_folder(view.hovered_item.abs_filename) then
previous_view or core.root_view:get_primary_node().active_view core.command_view:set_text(view.hovered_item.filename .. "/")
)
end end
end core.command_view:enter("Filename", function(filename)
}) local doc_filename = core.project_dir .. PATHSEP .. filename
local file = io.open(doc_filename, "a+")
command.add(TreeView, { file:write("")
["treeview:next"] = function() file:close()
local item, _, item_y = view:get_next(view.selected_item) core.root_view:open_doc(core.open_doc(doc_filename))
view:set_selection(item, item_y) core.log("Created %s", doc_filename)
end, common.path_suggest)
end, end,
["treeview:previous"] = function() ["treeview:new-folder"] = function()
local item, _, item_y = view:get_previous(view.selected_item) if not is_project_folder(view.hovered_item.abs_filename) then
view:set_selection(item, item_y) core.command_view:set_text(view.hovered_item.filename .. "/")
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)
end 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, end,
["treeview:deselect"] = function() ["treeview:delete"] = function()
view.selected_item = nil local filename = view.hovered_item.abs_filename
end, local relfilename = view.hovered_item.filename
["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,
})
local function treeitem() return view.hovered_item or view.selected_item end
command.add(
function()
local item = treeitem()
return item ~= nil
and (
core.active_view == view or core.active_view == menu
or (view.toolbar and core.active_view == view.toolbar)
-- sometimes the context menu is shown on top of statusbar
or core.active_view == core.status_view
), 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.basename(item.dir_name) .. PATHSEP .. relfilename
end
local file_info = system.get_file_info(filename) local file_info = system.get_file_info(filename)
local file_type = file_info.type == "dir" and "Directory" or "File" local file_type = file_info.type == "dir" and "Directory" or "File"
-- Ask before deleting -- Ask before deleting
@ -728,177 +568,22 @@ command.add(
end end
end end
) )
end
})
command.add(function()
if not (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) then return end
if core.root_view.overlapping_node.active_view ~= view then return end
local item = treeitem()
return item ~= nil, item
end, {
["treeview:rename"] = function(item)
local old_filename = item.filename
local old_abs_filename = item.abs_filename
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 = item.dir_name .. PATHSEP .. 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, end,
["treeview:new-file"] = function(item) ["treeview:open-in-system"] = function()
local text local hovered_item = view.hovered_item
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 = item.dir_name .. PATHSEP .. 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 = item.dir_name .. PATHSEP .. 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 if PLATFORM == "Windows" then
system.exec(string.format("start \"\" %q", item.abs_filename)) system.exec(string.format("start \"\" %q", hovered_item.abs_filename))
elseif string.find(PLATFORM, "Mac") or PLATFORM == "MorphOS" then elseif string.find(PLATFORM, "Mac") then
system.exec(string.format("open %q", item.abs_filename)) system.exec(string.format("open %q", hovered_item.abs_filename))
elseif PLATFORM == "Linux" or string.find(PLATFORM, "BSD") then elseif PLATFORM == "Linux" then
system.exec(string.format("xdg-open %q", item.abs_filename)) system.exec(string.format("xdg-open %q", hovered_item.abs_filename))
elseif PLATFORM == "AmigaOS 4" then
system.exec(string.format("WBRUN %q SHOW=all VIEWBY=name", item.abs_filename))
end end
end
})
local projectsearch = pcall(require, "plugins.projectsearch")
if projectsearch then
menu:register(function()
return view.hovered_item and view.hovered_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, end,
}) })
keymap.add { ["ctrl+\\"] = "treeview:toggle" }
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
}
}
-- Return the treeview with toolbar and contextmenu to allow -- Return the treeview with toolbar and contextmenu to allow
-- user or plugin modifications -- user or plugin modifications

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local command = require "core.command" local command = require "core.command"
local Doc = require "core.doc" local Doc = require "core.doc"
@ -24,8 +24,8 @@ end
command.add("core.docview", { command.add("core.docview", {
["trim-whitespace:trim-trailing-whitespace"] = function(dv) ["trim-whitespace:trim-trailing-whitespace"] = function()
trim_trailing_whitespace(dv.doc) trim_trailing_whitespace(core.active_view.doc)
end, end,
}) })

View File

@ -1,4 +1,4 @@
-- mod-version:3 -- mod-version:2 -- lite-xl 2.0
local core = require "core" local core = require "core"
local common = require "core.common" local common = require "core.common"
local DocView = require "core.docview" local DocView = require "core.docview"
@ -117,7 +117,7 @@ local function load_view(t)
-- cannot be read. -- cannot be read.
if dv and dv.doc then if dv and dv.doc then
dv.doc:set_selection(table.unpack(t.selection)) 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.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
end end

View File

@ -4,11 +4,6 @@
---@type table<integer, string> ---@type table<integer, string>
ARGS = {} 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. ---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" PLATFORM = "Operating System"

View File

@ -124,7 +124,7 @@ process.options = {}
---@return process | nil ---@return process | nil
---@return string errmsg ---@return string errmsg
---@return process.errortype | integer errcode ---@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 ---Translates an error code into a useful text message

View File

@ -6,9 +6,7 @@
renderer = {} renderer = {}
--- ---
---Array of bytes that represents a color used by the rendering functions. ---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.
---@class renderer.color ---@class renderer.color
---@field public r number Red ---@field public r number Red
---@field public g number Green ---@field public g number Green
@ -19,13 +17,11 @@ renderer.color = {}
--- ---
---Represent options that affect a font's rendering. ---Represent options that affect a font's rendering.
---@class renderer.fontoptions ---@class renderer.fontoptions
---@field public antialiasing "'none'" | "'grayscale'" | "'subpixel'" ---@field public antialiasing "'grayscale'" | "'subpixel'"
---@field public hinting "'slight'" | "'none'" | '"full"' ---@field public hinting "'slight'" | "'none'" | '"full"'
-- @field public bold boolean -- @field public bold boolean
-- @field public italic boolean -- @field public italic boolean
-- @field public underline boolean -- @field public underline boolean
-- @field public smoothing boolean
-- @field public strikethrough boolean
renderer.fontoptions = {} renderer.fontoptions = {}
--- ---
@ -37,29 +33,18 @@ renderer.font = {}
--- ---
---@param path string ---@param path string
---@param size number ---@param size number
---@param options? renderer.fontoptions ---@param options renderer.fontoptions
--- ---
---@return renderer.font ---@return renderer.font
function renderer.font.load(path, size, options) end 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. ---Clones a font object into a new one.
--- ---
---@param size? number Optional new size for cloned font. ---@param size? number Optional new size for cloned font.
---@param options? renderer.fontoptions
--- ---
---@return renderer.font ---@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. ---Set the amount of characters that represent a tab.
@ -96,11 +81,23 @@ function renderer.font:get_size() end
function renderer.font:set_size(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 ---Assistive functionality to replace characters in a
---array of strings if a group font. ---rendered text with other characters.
---@class renderer.replacements
renderer.replacements = {}
--- ---
---@return string | table<integer, string> ---Create a new character replacements object.
function renderer.font:get_path() end ---
---@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 ---Toggles drawing debugging rectangles on the currently rendered sections
@ -144,13 +141,29 @@ function renderer.set_clip_rect(x, y, width, height) end
function renderer.draw_rect(x, y, width, height, color) 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 font renderer.font
---@param text string ---@param text string
---@param x number ---@param x number
---@param y number ---@param y number
---@param color renderer.color ---@param color renderer.color
---@param replace renderer.replacements
---@param color_replace renderer.color
--- ---
---@return number x ---@return number x_subpixel
function renderer.draw_text(font, text, x, y, color) end function renderer.draw_text(font, text, x, y, color, replace, color_replace) end
---
---Draw text at subpixel level.
---
---@param font renderer.font
---@param text string
---@param x number
---@param y number
---@param color renderer.color
---@param replace renderer.replacements
---@param color_replace renderer.color
---
---@return number x_subpixel
function renderer.draw_text_subpixel(font, text, x, y, color, replace, color_replace) end

View File

@ -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

View File

@ -192,12 +192,6 @@ function system.get_clipboard() end
---@param text string ---@param text string
function system.set_clipboard(text) end function system.set_clipboard(text) end
---
---Get the process id of lite-xl it self.
---
---@return integer
function system.get_process_id() end
--- ---
---Get amount of iterations since the application was launched ---Get amount of iterations since the application was launched
---also known as SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency() ---also known as SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency()

View File

@ -1,191 +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

1595
lib/dmon/dmon.h Normal file

File diff suppressed because it is too large Load Diff

162
lib/dmon/dmon_extra.h Normal file
View File

@ -0,0 +1,162 @@
#ifndef __DMON_EXTRA_H__
#define __DMON_EXTRA_H__
//
// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved.
// License: https://github.com/septag/dmon#license-bsd-2-clause
//
// Extra header functionality for dmon.h for the backend based on inotify
//
// Add/Remove directory functions:
// dmon_watch_add: Adds a sub-directory to already valid watch_id. sub-directories are assumed to be relative to watch root_dir
// dmon_watch_add: Removes a sub-directory from already valid watch_id. sub-directories are assumed to be relative to watch root_dir
// Reason: The inotify backend does not work well when watching in recursive mode a root directory of a large tree, it may take
// quite a while until all inotify watches are established, and events will not be received in this time. Also, since one
// inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user
// will be reached. The default maximum is 8192.
// When using inotify backend users may turn off the DMON_WATCHFLAGS_RECURSIVE flag and add/remove selectively the
// sub-directories to be watched based on application-specific logic about which sub-directory actually needs to be watched.
// The function dmon_watch_add and dmon_watch_rm are used to this purpose.
//
#ifndef __DMON_H__
#error "Include 'dmon.h' before including this file"
#endif
#ifdef __cplusplus
extern "C" {
#endif
DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir);
DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir);
#ifdef __cplusplus
}
#endif
#ifdef DMON_IMPL
#if DMON_OS_LINUX
DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
{
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
bool skip_lock = pthread_self() == _dmon.thread_handle;
if (!skip_lock)
pthread_mutex_lock(&_dmon.mutex);
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
// check if the directory exists
// if watchdir contains absolute/root-included path, try to strip the rootdir from it
// else, we assume that watchdir is correct, so save it as it is
struct stat st;
dmon__watch_subdir subdir;
if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) {
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) {
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir));
}
} else {
char fullpath[DMON_MAX_PATH];
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
dmon__strcat(fullpath, sizeof(fullpath), watchdir);
if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) {
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
}
int dirlen = (int)strlen(subdir.rootdir);
if (subdir.rootdir[dirlen - 1] != '/') {
subdir.rootdir[dirlen] = '/';
subdir.rootdir[dirlen + 1] = '\0';
}
// check that the directory is not already added
for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) {
if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) {
_DMON_LOG_ERRORF("Error watching directory '%s', because it is already added.", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
}
const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY;
char fullpath[DMON_MAX_PATH];
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir);
int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask);
if (wd == -1) {
_DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watchdir, errno);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
stb_sb_push(watch->subdirs, subdir);
stb_sb_push(watch->wds, wd);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return true;
}
DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir)
{
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
bool skip_lock = pthread_self() == _dmon.thread_handle;
if (!skip_lock)
pthread_mutex_lock(&_dmon.mutex);
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
char subdir[DMON_MAX_PATH];
dmon__strcpy(subdir, sizeof(subdir), watchdir);
if (strstr(subdir, watch->rootdir) == subdir) {
dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir));
}
int dirlen = (int)strlen(subdir);
if (subdir[dirlen - 1] != '/') {
subdir[dirlen] = '/';
subdir[dirlen + 1] = '\0';
}
int i, c = stb_sb_count(watch->subdirs);
for (i = 0; i < c; i++) {
if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) {
break;
}
}
if (i >= c) {
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return false;
}
inotify_rm_watch(watch->fd, watch->wds[i]);
/* Remove entry from subdirs and wds by swapping position with the last entry */
watch->subdirs[i] = stb_sb_last(watch->subdirs);
stb_sb_pop(watch->subdirs);
watch->wds[i] = stb_sb_last(watch->wds);
stb_sb_pop(watch->wds);
if (!skip_lock)
pthread_mutex_unlock(&_dmon.mutex);
return true;
}
#endif // DMON_OS_LINUX
#endif // DMON_IMPL
#endif // __DMON_EXTRA_H__

1
lib/dmon/meson.build Normal file
View File

@ -0,0 +1 @@
lite_includes += include_directories('.')

View File

@ -22,6 +22,33 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
## septag/dmon
Copyright 2019 Sepehr Taghdisian. All rights reserved.
https://github.com/septag/dmon
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## Fira Sans ## Fira Sans
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.

View File

@ -1,41 +1,18 @@
project('lite-xl', project('lite-xl',
['c'], ['c'],
version : '2.1.0', version : '2.0.3',
license : 'MIT', license : 'MIT',
meson_version : '>= 0.47', meson_version : '>= 0.54',
default_options : [ default_options : ['c_std=gnu11']
'c_std=gnu11',
'wrap_mode=nofallback'
]
) )
#===============================================================================
# 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 # Configuration
#=============================================================================== #===============================================================================
conf_data = configuration_data() conf_data = configuration_data()
conf_data.set('PROJECT_BUILD_DIR', meson.current_build_dir()) conf_data.set('PROJECT_BUILD_DIR', meson.current_build_dir())
conf_data.set('PROJECT_SOURCE_DIR', meson.current_source_dir()) conf_data.set('PROJECT_SOURCE_DIR', meson.current_source_dir())
conf_data.set('PROJECT_VERSION', version) conf_data.set('PROJECT_VERSION', meson.project_version())
#=============================================================================== #===============================================================================
# Compiler Settings # Compiler Settings
@ -47,7 +24,7 @@ endif
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
lite_includes = [] lite_includes = []
lite_cargs = ['-DSDL_MAIN_HANDLED', '-DPCRE2_STATIC'] lite_cargs = []
# On macos we need to use the SDL renderer to support retina displays # On macos we need to use the SDL renderer to support retina displays
if get_option('renderer') or host_machine.system() == 'darwin' if get_option('renderer') or host_machine.system() == 'darwin'
lite_cargs += '-DLITE_USE_SDL_RENDERER' lite_cargs += '-DLITE_USE_SDL_RENDERER'
@ -69,84 +46,14 @@ endif
if not get_option('source-only') if not get_option('source-only')
libm = cc.find_library('m', required : false) libm = cc.find_library('m', required : false)
libdl = cc.find_library('dl', required : false) libdl = cc.find_library('dl', required : false)
threads_dep = dependency('threads')
default_fallback_options = ['warning_level=0', 'werror=false'] lua_dep = dependency('lua5.2', fallback: ['lua', 'lua_dep'],
default_options: ['shared=false', 'use_readline=false', 'app=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
]
foreach lua : lua_names
last_lua = (lua == lua_names[-1] or get_option('wrap_mode') == 'forcefallback')
lua_dep = dependency(lua, fallback: last_lua ? ['lua', 'lua_dep'] : [], required : last_lua,
version: '>= 5.4',
default_options: default_fallback_options + ['default_library=static', 'line_editing=false', 'interpreter=false']
)
if lua_dep.found()
break
endif
endforeach
pcre2_dep = dependency('libpcre2-8', fallback: ['pcre2', 'libpcre2_8'],
default_options: default_fallback_options + ['default_library=static', 'grep=false', 'test=false']
) )
pcre2_dep = dependency('libpcre2-8')
freetype_dep = dependency('freetype2', fallback: ['freetype2', 'freetype_dep'], freetype_dep = dependency('freetype2')
default_options: default_fallback_options + ['default_library=static', 'zlib=disabled', 'bzip2=disabled', 'png=disabled', 'harfbuzz=disabled', 'brotli=disabled'] sdl_dep = dependency('sdl2', method: 'config-tool')
) lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl, threads_dep]
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'
# 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_dep = dependency('sdl2', fallback: ['sdl2', 'sdl2_dep'],
default_options: default_fallback_options + sdl_options
)
lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl]
endif endif
#=============================================================================== #===============================================================================
# Install Configuration # Install Configuration
@ -187,20 +94,21 @@ endif
install_data('licenses/licenses.md', install_dir : lite_docdir) 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'] 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 endforeach
configure_file( configure_file(
input : 'data/core/start.lua', input : 'data/core/start.lua',
output : 'start.lua', output : 'start.lua',
configuration : conf_data, configuration : conf_data,
install_dir : join_paths(lite_datadir, 'core'), install : true,
install_dir : lite_datadir / 'core',
) )
if not get_option('source-only') if not get_option('source-only')
subdir('lib/dmon')
subdir('src') subdir('src')
subdir('scripts') subdir('scripts')
endif endif

View File

@ -2,4 +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('source-only', type : 'boolean', value : false, description: 'Configure source files only, doesn\'t checks for dependencies')
option('portable', type : 'boolean', value : false, description: 'Portable install') option('portable', type : 'boolean', value : false, description: 'Portable install')
option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer') option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer')
option('dirmonitor_backend', type : 'combo', value : '', choices : ['', 'inotify', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use')

View File

@ -6,7 +6,6 @@
<name>Lite XL</name> <name>Lite XL</name>
<summary>A lightweight text editor written in Lua</summary> <summary>A lightweight text editor written in Lua</summary>
<content_rating type="oars-1.0" /> <content_rating type="oars-1.0" />
<launchable type="desktop-id">org.lite_xl.lite_xl.desktop</launchable>
<description> <description>
<p> <p>
@ -18,11 +17,11 @@
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<caption>The editor window</caption> <caption>The editor window</caption>
<image>https://lite-xl.com/assets/img/editor.png</image> <image>https://lite-xl.github.io/assets/img/screenshots/editor.png</image>
</screenshot> </screenshot>
</screenshots> </screenshots>
<url type="homepage">https://lite-xl.com</url> <url type="homepage">https://lite-xl.github.io</url>
<provides> <provides>
<binary>lite-xl</binary> <binary>lite-xl</binary>

View File

@ -8,8 +8,6 @@
<string>lite-xl</string> <string>lite-xl</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>icon.icns</string> <string>icon.icns</string>
<key>CFBundleIdentifier</key>
<string>com.lite-xl</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>Lite XL</string> <string>Lite XL</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>

View File

@ -0,0 +1,54 @@
`core.set_project_dir`:
Reset project directories and set its directory.
It chdir into the directory, empty the `core.project_directories` and add
the given directory.
`core.add_project_directory`:
Add a new top-level directory to the project.
Also called from modules and commands outside core.init.
local function `scan_project_folder`:
Scan all files for a given top-level project directory.
Can emit a warning about file limit.
Called only from within core.init module.
`core.scan_project_subdir`: (before was named `core.scan_project_folder`)
scan a single folder, without recursion. Used when too many files.
Local function `scan_project_folder`:
Populate the project folder top directory. Done only once when the directory
is added to the project.
`core.add_project_directory`:
Add a new top-level folder to the project.
`core.set_project_dir`:
Set the initial project directory.
`core.dir_rescan_add_job`:
Add a job to rescan after an elapsed time a project's subdirectory to fix for any
changes.
Local function `rescan_project_subdir`:
Rescan a project's subdirectory, compare to the current version and patch the list if
a difference is found.
`core.project_scan_thread`:
Should disappear now that we use dmon.
`core.project_scan_topdir`:
New function to scan a top level project folder.
`config.project_scan_rate`:
`core.project_scan_thread_id`:
`core.reschedule_project_scan`:
`core.project_files_limit`:
A eliminer.
`core.get_project_files`:
To be fixed. Use `find_project_files_co` for a single directory
In TreeView remove usage of self.last to detect new scan that changed the files list.

View File

@ -1,195 +0,0 @@
diff -ruN lua-5.4.3/meson.build newlua/meson.build
--- lua-5.4.3/meson.build 2022-05-29 21:04:17.850449500 +0800
+++ newlua/meson.build 2022-06-10 19:23:55.685139800 +0800
@@ -82,6 +82,7 @@
'src/lutf8lib.c',
'src/lvm.c',
'src/lzio.c',
+ 'src/utf8_wrappers.c',
dependencies: lua_lib_deps,
override_options: project_options,
implicit_include_directories: false,
Binary files lua-5.4.3/src/lua54.dll and newlua/src/lua54.dll differ
diff -ruN lua-5.4.3/src/luaconf.h newlua/src/luaconf.h
--- lua-5.4.3/src/luaconf.h 2021-03-15 21:32:52.000000000 +0800
+++ newlua/src/luaconf.h 2022-06-10 19:15:03.014745300 +0800
@@ -786,5 +786,15 @@
+#if defined(lua_c) || defined(luac_c) || (defined(LUA_LIB) && \
+ (defined(lauxlib_c) || defined(liolib_c) || \
+ defined(loadlib_c) || defined(loslib_c)))
+#include "utf8_wrappers.h"
+#endif
+
+
+
+
+
#endif
diff -ruN lua-5.4.3/src/Makefile newlua/src/Makefile
--- lua-5.4.3/src/Makefile 2021-02-10 02:47:17.000000000 +0800
+++ newlua/src/Makefile 2022-06-10 19:22:45.267931400 +0800
@@ -33,7 +33,7 @@
PLATS= guess aix bsd c89 freebsd generic linux linux-readline macosx mingw posix solaris
LUA_A= liblua.a
-CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o lundump.o lvm.o lzio.o
+CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o lundump.o lvm.o lzio.o utf8_wrappers.o
LIB_O= lauxlib.o lbaselib.o lcorolib.o ldblib.o liolib.o lmathlib.o loadlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o linit.o
BASE_O= $(CORE_O) $(LIB_O) $(MYOBJS)
diff -ruN lua-5.4.3/src/utf8_wrappers.c newlua/src/utf8_wrappers.c
--- lua-5.4.3/src/utf8_wrappers.c 1970-01-01 07:30:00.000000000 +0730
+++ newlua/src/utf8_wrappers.c 2022-06-10 19:13:11.904613300 +0800
@@ -0,0 +1,101 @@
+/**
+ * Wrappers to provide Unicode (UTF-8) support on Windows.
+ *
+ * Copyright (c) 2018 Peter Wu <peter@lekensteyn.nl>
+ * SPDX-License-Identifier: (GPL-2.0-or-later OR MIT)
+ */
+
+#ifdef _WIN32
+#include <windows.h> /* for MultiByteToWideChar */
+#include <wchar.h> /* for _wrename */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+// Set a high limit in case long paths are enabled.
+#define MAX_PATH_SIZE 4096
+#define MAX_MODE_SIZE 128
+// cmd.exe argument length is reportedly limited to 8192.
+#define MAX_CMD_SIZE 8192
+
+FILE *fopen_utf8(const char *pathname, const char *mode) {
+ wchar_t pathname_w[MAX_PATH_SIZE];
+ wchar_t mode_w[MAX_MODE_SIZE];
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pathname, -1, pathname_w, MAX_PATH_SIZE) ||
+ !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, mode_w, MAX_MODE_SIZE)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ return _wfopen(pathname_w, mode_w);
+}
+
+FILE *freopen_utf8(const char *pathname, const char *mode, FILE *stream) {
+ wchar_t pathname_w[MAX_PATH_SIZE];
+ wchar_t mode_w[MAX_MODE_SIZE];
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pathname, -1, pathname_w, MAX_PATH_SIZE) ||
+ !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, mode_w, MAX_MODE_SIZE)) {
+ // Close stream as documented for the error case.
+ fclose(stream);
+ errno = EINVAL;
+ return NULL;
+ }
+ return _wfreopen(pathname_w, mode_w, stream);
+}
+
+int remove_utf8(const char *pathname) {
+ wchar_t pathname_w[MAX_PATH_SIZE];
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pathname, -1, pathname_w, MAX_PATH_SIZE)) {
+ errno = EINVAL;
+ return -1;
+ }
+ return _wremove(pathname_w);
+}
+
+int rename_utf8(const char *oldpath, const char *newpath) {
+ wchar_t oldpath_w[MAX_PATH_SIZE];
+ wchar_t newpath_w[MAX_PATH_SIZE];
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, oldpath, -1, oldpath_w, MAX_PATH_SIZE) ||
+ !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, newpath, -1, newpath_w, MAX_PATH_SIZE)) {
+ errno = EINVAL;
+ return -1;
+ }
+ return _wrename(oldpath_w, newpath_w);
+}
+
+FILE *popen_utf8(const char *command, const char *mode) {
+ wchar_t command_w[MAX_CMD_SIZE];
+ wchar_t mode_w[MAX_MODE_SIZE];
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, command, -1, command_w, MAX_CMD_SIZE) ||
+ !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, mode_w, MAX_MODE_SIZE)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ return _wpopen(command_w, mode_w);
+}
+
+int system_utf8(const char *command) {
+ wchar_t command_w[MAX_CMD_SIZE];
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, command, -1, command_w, MAX_CMD_SIZE)) {
+ errno = EINVAL;
+ return -1;
+ }
+ return _wsystem(command_w);
+}
+
+DWORD GetModuleFileNameA_utf8(HMODULE hModule, LPSTR lpFilename, DWORD nSize) {
+ wchar_t filename_w[MAX_PATH + 1];
+ if (!GetModuleFileNameW(hModule, filename_w, MAX_PATH + 1)) {
+ return 0;
+ }
+ return WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, filename_w, -1, lpFilename, nSize, NULL, NULL);
+}
+
+HMODULE LoadLibraryExA_utf8(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) {
+ wchar_t pathname_w[MAX_PATH_SIZE];
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, lpLibFileName, -1, pathname_w, MAX_PATH_SIZE)) {
+ SetLastError(ERROR_INVALID_NAME);
+ return NULL;
+ }
+ return LoadLibraryExW(pathname_w, hFile, dwFlags);
+}
+#endif
diff -ruN lua-5.4.3/src/utf8_wrappers.h newlua/src/utf8_wrappers.h
--- lua-5.4.3/src/utf8_wrappers.h 1970-01-01 07:30:00.000000000 +0730
+++ newlua/src/utf8_wrappers.h 2022-06-10 19:22:53.554879400 +0800
@@ -0,0 +1,42 @@
+/**
+ * Wrappers to provide Unicode (UTF-8) support on Windows.
+ *
+ * Copyright (c) 2018 Peter Wu <peter@lekensteyn.nl>
+ * SPDX-License-Identifier: (GPL-2.0-or-later OR MIT)
+ */
+
+#ifdef _WIN32
+
+#if defined(loadlib_c) || defined(lauxlib_c) || defined(liolib_c) || defined(luac_c)
+#include <stdio.h> /* for loadlib_c */
+FILE *fopen_utf8(const char *pathname, const char *mode);
+#define fopen fopen_utf8
+#endif
+
+#ifdef lauxlib_c
+FILE *freopen_utf8(const char *pathname, const char *mode, FILE *stream);
+#define freopen freopen_utf8
+#endif
+
+#ifdef liolib_c
+FILE *popen_utf8(const char *command, const char *mode);
+#define _popen popen_utf8
+#endif
+
+#ifdef loslib_c
+int remove_utf8(const char *pathname);
+int rename_utf8(const char *oldpath, const char *newpath);
+int system_utf8(const char *command);
+#define remove remove_utf8
+#define rename rename_utf8
+#define system system_utf8
+#endif
+
+#ifdef loadlib_c
+#include <windows.h>
+DWORD GetModuleFileNameA_utf8(HMODULE hModule, LPSTR lpFilename, DWORD nSize);
+HMODULE LoadLibraryExA_utf8(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
+#define GetModuleFileNameA GetModuleFileNameA_utf8
+#define LoadLibraryExA LoadLibraryExA_utf8
+#endif
+#endif

View File

@ -1,5 +1,5 @@
#!/bin/env bash #!/bin/env bash
set -e set -ex
if [ ! -e "src/api/api.h" ]; then if [ ! -e "src/api/api.h" ]; then
echo "Please run this script from the root directory of Lite XL." echo "Please run this script from the root directory of Lite XL."
@ -8,13 +8,6 @@ fi
source scripts/common.sh source scripts/common.sh
ARCH="$(uname -m)"
BUILD_DIR="$(get_default_build_dir)"
RUN_BUILD=true
STATIC_BUILD=false
ADDONS=false
BUILD_TYPE="debug"
show_help(){ show_help(){
echo echo
echo "Usage: $0 <OPTIONS>" echo "Usage: $0 <OPTIONS>"
@ -23,21 +16,22 @@ show_help(){
echo echo
echo "-h --help Show this help and exits." echo "-h --help Show this help and exits."
echo "-b --builddir DIRNAME Sets the name of the build dir (no path)." echo "-b --builddir DIRNAME Sets the name of the build dir (no path)."
echo " Default: '${BUILD_DIR}'." echo " Default: 'build'."
echo " --debug Debug this script."
echo "-n --nobuild Skips the build step, use existing files." echo "-n --nobuild Skips the build step, use existing files."
echo "-s --static Specify if building using static libraries." echo "-s --static Specify if building using static libraries"
echo " by using lhelper tool."
echo "-v --version VERSION Specify a version, non whitespace separated string." echo "-v --version VERSION Specify a version, non whitespace separated string."
echo "-a --addons Install 3rd party addons."
echo "-r --release Compile in release mode."
echo echo
} }
initial_arg_count=$# ARCH="$(uname -m)"
BUILD_DIR="$(get_default_build_dir)"
RUN_BUILD=true
STATIC_BUILD=false
for i in "$@"; do for i in "$@"; do
case $i in case $i in
-h|--help) -h|--belp)
show_help show_help
exit 0 exit 0
;; ;;
@ -46,22 +40,10 @@ for i in "$@"; do
shift shift
shift shift
;; ;;
-a|--addons)
ADDONS=true
shift
;;
--debug)
set -x
shift
;;
-n|--nobuild) -n|--nobuild)
RUN_BUILD=false RUN_BUILD=false
shift shift
;; ;;
-r|--release)
BUILD_TYPE="release"
shift
;;
-s|--static) -s|--static)
STATIC_BUILD=true STATIC_BUILD=true
shift shift
@ -77,19 +59,25 @@ for i in "$@"; do
esac esac
done done
# show help if no valid argument was found # TODO: Versioning using git
if [ $initial_arg_count -eq $# ]; then #if [[ -z $VERSION && -d .git ]]; then
# VERSION=$(git describe --tags --long | sed 's/^v//; s/\([^-]*-g\)/r\1/; s/-/./g')
#fi
if [[ -n $1 ]]; then
show_help show_help
exit 1 exit 1
fi fi
setup_appimagetool() { setup_appimagetool() {
if [ ! -e appimagetool ]; then if ! which appimagetool > /dev/null ; then
if ! wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${ARCH}.AppImage" ; then if [ ! -e appimagetool ]; then
echo "Could not download the appimagetool for the arch '${ARCH}'." if ! wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${ARCH}.AppImage" ; then
exit 1 echo "Could not download the appimagetool for the arch '${ARCH}'."
else exit 1
chmod 0755 appimagetool else
chmod 0755 appimagetool
fi
fi fi
fi fi
} }
@ -116,14 +104,7 @@ build_litexl() {
echo "Build lite-xl..." echo "Build lite-xl..."
sleep 1 sleep 1
if [[ $STATIC_BUILD == false ]]; then meson setup --buildtype=release --prefix /usr ${BUILD_DIR}
meson setup --buildtype=$BUILD_TYPE --prefix=/usr ${BUILD_DIR}
else
meson setup --wrap-mode=forcefallback \
--buildtype=$BUILD_TYPE \
--prefix=/usr \
${BUILD_DIR}
fi
meson compile -C ${BUILD_DIR} meson compile -C ${BUILD_DIR}
} }
@ -140,11 +121,6 @@ generate_appimage() {
cp resources/icons/lite-xl.svg LiteXL.AppDir/ cp resources/icons/lite-xl.svg LiteXL.AppDir/
cp resources/linux/org.lite_xl.lite_xl.desktop LiteXL.AppDir/ cp resources/linux/org.lite_xl.lite_xl.desktop LiteXL.AppDir/
if [[ $ADDONS == true ]]; then
addons_download "${BUILD_DIR}"
addons_install "${BUILD_DIR}" "LiteXL.AppDir/usr/share/lite-xl"
fi
if [[ $STATIC_BUILD == false ]]; then if [[ $STATIC_BUILD == false ]]; then
echo "Copying libraries..." echo "Copying libraries..."
@ -177,10 +153,6 @@ generate_appimage() {
version="-$VERSION" version="-$VERSION"
fi fi
if [[ $ADDONS == true ]]; then
version="${version}-addons"
fi
./appimagetool LiteXL.AppDir LiteXL${version}-${ARCH}.AppImage ./appimagetool LiteXL.AppDir LiteXL${version}-${ARCH}.AppImage
} }

View File

@ -22,25 +22,19 @@ show_help() {
echo "-B --bundle Create an App bundle (macOS only)" echo "-B --bundle Create an App bundle (macOS only)"
echo "-P --portable Create a portable binary package." echo "-P --portable Create a portable binary package."
echo "-O --pgo Use profile guided optimizations (pgo)." echo "-O --pgo Use profile guided optimizations (pgo)."
echo "-U --windows-lua-utf Use the UTF8 patch for Lua."
echo " macOS: disabled when used with --bundle," echo " macOS: disabled when used with --bundle,"
echo " Windows: Implicit being the only option." echo " Windows: Implicit being the only option."
echo "-r --release Compile in release mode."
echo echo
} }
main() { main() {
local platform="$(get_platform_name)" local platform="$(get_platform_name)"
local build_dir="$(get_default_build_dir)" local build_dir="$(get_default_build_dir)"
local build_type="debug"
local prefix=/ local prefix=/
local force_fallback local force_fallback
local bundle local bundle
local portable local portable
local pgo local pgo
local patch_lua
local lua_subproject_path
for i in "$@"; do for i in "$@"; do
case $i in case $i in
@ -82,14 +76,6 @@ main() {
pgo="-Db_pgo=generate" pgo="-Db_pgo=generate"
shift shift
;; ;;
-U|--windows-lua-utf)
patch_lua="true"
shift
;;
-r|--release)
build_type="release"
shift
;;
*) *)
# unknown option # unknown option
;; ;;
@ -109,7 +95,7 @@ main() {
rm -rf "${build_dir}" rm -rf "${build_dir}"
CFLAGS=$CFLAGS LDFLAGS=$LDFLAGS meson setup \ CFLAGS=$CFLAGS LDFLAGS=$LDFLAGS meson setup \
--buildtype=$build_type \ --buildtype=release \
--prefix "$prefix" \ --prefix "$prefix" \
$force_fallback \ $force_fallback \
$bundle \ $bundle \
@ -117,11 +103,6 @@ main() {
$pgo \ $pgo \
"${build_dir}" "${build_dir}"
lua_subproject_path=$(echo subprojects/lua-*/)
if [[ $patch_lua == "true" ]] && [[ ! -z $force_fallback ]] && [[ -d $lua_subproject_path ]]; then
patch -d $lua_subproject_path -p1 --forward < resources/windows/001-lua-unicode.diff
fi
meson compile -C "${build_dir}" meson compile -C "${build_dir}"
if [ ! -z ${pgo+x} ]; then if [ ! -z ${pgo+x} ]; then

View File

@ -2,64 +2,6 @@
set -e set -e
addons_download() {
local build_dir="$1"
if [[ -d "${build_dir}/third/data/colors" ]]; then
echo "Warning: found previous addons installation, skipping."
echo " addons path: ${build_dir}/third/data/colors"
return 0
fi
# Download third party color themes
curl --insecure \
-L "https://github.com/lite-xl/lite-xl-colors/archive/master.zip" \
-o "${build_dir}/lite-xl-colors.zip"
mkdir -p "${build_dir}/third/data/colors"
unzip "${build_dir}/lite-xl-colors.zip" -d "${build_dir}"
mv "${build_dir}/lite-xl-colors-master/colors" "${build_dir}/third/data"
rm -rf "${build_dir}/lite-xl-colors-master"
# Download widgets library
curl --insecure \
-L "https://github.com/lite-xl/lite-xl-widgets/archive/master.zip" \
-o "${build_dir}/lite-xl-widgets.zip"
unzip "${build_dir}/lite-xl-widgets.zip" -d "${build_dir}"
mv "${build_dir}/lite-xl-widgets-master" "${build_dir}/third/data/widget"
# Downlaod thirdparty plugins
curl --insecure \
-L "https://github.com/lite-xl/lite-xl-plugins/archive/2.1.zip" \
-o "${build_dir}/lite-xl-plugins.zip"
unzip "${build_dir}/lite-xl-plugins.zip" -d "${build_dir}"
mv "${build_dir}/lite-xl-plugins-2.1/plugins" "${build_dir}/third/data"
rm -rf "${build_dir}/lite-xl-plugins-2.1"
}
# Addons installation: some distributions forbid external downloads
# so make it as optional module.
addons_install() {
local build_dir="$1"
local data_dir="$2"
for module_name in colors widget; do
cp -r "${build_dir}/third/data/$module_name" "${data_dir}"
done
mkdir -p "${data_dir}/plugins"
for plugin_name in settings open_ext; do
cp -r "${build_dir}/third/data/plugins/${plugin_name}.lua" \
"${data_dir}/plugins/"
done
cp "${build_dir}/third/data/plugins/"language_* \
"${data_dir}/plugins/"
}
get_platform_name() { get_platform_name() {
if [[ "$OSTYPE" == "msys" ]]; then if [[ "$OSTYPE" == "msys" ]]; then
echo "windows" echo "windows"
@ -72,23 +14,9 @@ get_platform_name() {
fi fi
} }
get_platform_arch() {
platform=$(get_platform_name)
arch=$(uname -m)
if [[ $MSYSTEM != "" ]]; then
if [[ $MSYSTEM == "MINGW64" ]]; then
arch=x86_64
else
arch=i686
fi
fi
echo "$arch"
}
get_default_build_dir() { get_default_build_dir() {
platform=$(get_platform_name) platform=$(get_platform_name)
arch=$(get_platform_arch) echo "build-$platform-$(uname -m)"
echo "build-$platform-$arch"
} }
if [[ $(get_platform_name) == "UNSUPPORTED-OS" ]]; then if [[ $(get_platform_name) == "UNSUPPORTED-OS" ]]; then

View File

@ -15,29 +15,15 @@ show_help() {
echo echo
echo "-b --builddir DIRNAME Sets the name of the build directory (not path)." echo "-b --builddir DIRNAME Sets the name of the build directory (not path)."
echo " Default: '$(get_default_build_dir)'." echo " Default: '$(get_default_build_dir)'."
echo "-v --version VERSION Sets the version on the package name."
echo "-a --addons Tell the script we are packaging an install with addons."
echo " --debug Debug this script." echo " --debug Debug this script."
echo echo
} }
main() { main() {
local build_dir=$(get_default_build_dir) local build_dir=$(get_default_build_dir)
local addons=false
local arch local arch
local arch_file
local version
local output
if [[ $MSYSTEM == "MINGW64" ]]; then if [[ $MSYSTEM == "MINGW64" ]]; then arch=x64; else arch=Win32; fi
arch=x64
arch_file=x86_64
else
arch=i686;
arch_file=i686
fi
initial_arg_count=$#
for i in "$@"; do for i in "$@"; do
case $i in case $i in
@ -45,20 +31,11 @@ main() {
show_help show_help
exit 0 exit 0
;; ;;
-a|--addons)
addons=true
shift
;;
-b|--builddir) -b|--builddir)
build_dir="$2" build_dir="$2"
shift shift
shift shift
;; ;;
-v|--version)
if [[ -n $2 ]]; then version="-$2"; fi
shift
shift
;;
--debug) --debug)
set -x set -x
shift shift
@ -69,19 +46,19 @@ main() {
esac esac
done done
# show help if no valid argument was found if [[ -n $1 ]]; then
if [ $initial_arg_count -eq $# ]; then
show_help show_help
exit 1 exit 1
fi fi
if [[ $addons == true ]]; then # Copy MinGW libraries dependencies.
version="${version}-addons" # MSYS2 ldd command seems to be only 64bit, so use ntldd
fi # see https://github.com/msys2/MINGW-packages/issues/4164
local mingwLibsDir="${build_dir}/mingwLibs$arch"
mkdir -p "$mingwLibsDir"
ntldd -R "${build_dir}/src/lite-xl.exe" | grep mingw | awk '{print $3}' | sed 's#\\#/#g' | xargs -I '{}' cp -v '{}' $mingwLibsDir
output="LiteXL${version}-${arch_file}-setup" "/c/Program Files (x86)/Inno Setup 6/ISCC.exe" -dARCH=$arch "${build_dir}/scripts/innosetup.iss"
"/c/Program Files (x86)/Inno Setup 6/ISCC.exe" -dARCH=$arch //F"${output}" "${build_dir}/scripts/innosetup.iss"
pushd "${build_dir}/scripts"; mv LiteXL*.exe "./../../"; popd pushd "${build_dir}/scripts"; mv LiteXL*.exe "./../../"; popd
} }

View File

@ -48,7 +48,7 @@ main() {
if [[ $lhelper == true ]]; then if [[ $lhelper == true ]]; then
sudo apt-get install -qq ninja-build sudo apt-get install -qq ninja-build
else else
sudo apt-get install -qq libfuse2 ninja-build wayland-protocols libsdl2-dev libfreetype6 sudo apt-get install -qq ninja-build libsdl2-dev libfreetype6
fi fi
pip3 install meson pip3 install meson
elif [[ "$OSTYPE" == "darwin"* ]]; then elif [[ "$OSTYPE" == "darwin"* ]]; then
@ -63,10 +63,10 @@ main() {
elif [[ "$OSTYPE" == "msys" ]]; then elif [[ "$OSTYPE" == "msys" ]]; then
if [[ $lhelper == true ]]; then if [[ $lhelper == true ]]; then
pacman --noconfirm -S \ pacman --noconfirm -S \
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,mesa} unzip ${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config} unzip
else else
pacman --noconfirm -S \ pacman --noconfirm -S \
${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,mesa,freetype,pcre2,SDL2} unzip ${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,freetype,pcre2,SDL2} unzip
fi fi
fi fi
} }

View File

@ -51,23 +51,25 @@ main() {
pushd lhelper; bash install "${lhelper_prefix}"; popd pushd lhelper; bash install "${lhelper_prefix}"; popd
if [[ "$OSTYPE" == "darwin"* ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then
CC=clang CXX=clang++ lhelper create build CC=clang CXX=clang++ lhelper create lite-xl -n
else else
lhelper create lite-xl build lhelper create lite-xl -n
fi fi
fi fi
# Not using $(lhelper activate lite-xl) to support CI # Not using $(lhelper activate lite-xl) to support CI
source "$(lhelper env-source build)" source "$(lhelper env-source lite-xl)"
lhelper install freetype2
lhelper install sdl2 2.0.14-wait-event-timeout-1
lhelper install pcre2
# Help MSYS2 to find the SDL2 include and lib directories to avoid errors # Help MSYS2 to find the SDL2 include and lib directories to avoid errors
# during build and linking when using lhelper. # during build and linking when using lhelper.
# Francesco: not sure why this is needed. I have never observed the problem when if [[ "$OSTYPE" == "msys" ]]; then
# building on window. CFLAGS=-I${LHELPER_ENV_PREFIX}/include/SDL2
# if [[ "$OSTYPE" == "msys" ]]; then LDFLAGS=-L${LHELPER_ENV_PREFIX}/lib
# CFLAGS=-I${LHELPER_ENV_PREFIX}/include/SDL2 fi
# LDFLAGS=-L${LHELPER_ENV_PREFIX}/lib
# fi
} }
main "$@" main

View File

@ -20,19 +20,44 @@ show_help() {
echo "-h --help Show this help and exit." echo "-h --help Show this help and exit."
echo "-p --prefix PREFIX Install directory prefix. Default: '/'." echo "-p --prefix PREFIX Install directory prefix. Default: '/'."
echo "-v --version VERSION Sets the version on the package name." echo "-v --version VERSION Sets the version on the package name."
echo "-a --addons Install 3rd party addons." echo " --addons Install 3rd party addons (currently Lite XL colors)."
echo " --debug Debug this script." echo " --debug Debug this script."
echo "-A --appimage Create an AppImage (Linux only)." echo "-A --appimage Create an AppImage (Linux only)."
echo "-B --binary Create a normal / portable package or macOS bundle," echo "-B --binary Create a normal / portable package or macOS bundle,"
echo " depending on how the build was configured. (Default.)" echo " depending on how the build was configured. (Default.)"
echo "-D --dmg Create a DMG disk image with AppDMG (macOS only)." echo "-D --dmg Create a DMG disk image with AppDMG (macOS only)."
echo "-I --innosetup Create a InnoSetup package (Windows only)." echo "-I --innosetup Create a InnoSetup package (Windows only)."
echo "-r --release Strip debugging symbols."
echo "-S --source Create a source code package," echo "-S --source Create a source code package,"
echo " including subprojects dependencies." echo " including subprojects dependencies."
echo echo
} }
# Addons installation: some distributions forbid external downloads
# so make it as optional module.
install_addons() {
local build_dir="$1"
local data_dir="$2"
if [[ -d "${build_dir}/third/data/colors" ]]; then
echo "Warning: found previous colors addons installation, skipping."
return 0
fi
# Copy third party color themes
curl --insecure \
-L "https://github.com/lite-xl/lite-xl-colors/archive/master.zip" \
-o "${build_dir}/lite-xl-colors.zip"
mkdir -p "${build_dir}/third/data/colors"
unzip "${build_dir}/lite-xl-colors.zip" -d "${build_dir}"
mv "${build_dir}/lite-xl-colors-master/colors" "${build_dir}/third/data"
rm -rf "${build_dir}/lite-xl-colors-master"
for module_name in colors; do
cp -r "${build_dir}/third/data/$module_name" "${data_dir}"
done
}
source_package() { source_package() {
local build_dir=build-src local build_dir=build-src
local package_name=$1 local package_name=$1
@ -60,7 +85,7 @@ source_package() {
} }
main() { main() {
local arch="$(get_platform_arch)" local arch="$(uname -m)"
local platform="$(get_platform_name)" local platform="$(get_platform_name)"
local build_dir="$(get_default_build_dir)" local build_dir="$(get_default_build_dir)"
local dest_dir=lite-xl local dest_dir=lite-xl
@ -71,12 +96,8 @@ main() {
local binary=false local binary=false
local dmg=false local dmg=false
local innosetup=false local innosetup=false
local release=false
local source=false local source=false
# store the current flags to easily pass them to appimage script
local flags="$@"
for i in "$@"; do for i in "$@"; do
case $i in case $i in
-b|--builddir) -b|--builddir)
@ -131,15 +152,11 @@ main() {
fi fi
shift shift
;; ;;
-r|--release)
release=true
shift
;;
-S|--source) -S|--source)
source=true source=true
shift shift
;; ;;
-a|--addons) --addons)
addons=true addons=true
shift shift
;; ;;
@ -153,10 +170,6 @@ main() {
esac esac
done done
if [[ $addons == true ]]; then
version="$version-addons"
fi
if [[ -n $1 ]]; then show_help; exit 1; fi if [[ -n $1 ]]; then show_help; exit 1; fi
# The source package doesn't require a previous build, # The source package doesn't require a previous build,
@ -177,7 +190,6 @@ main() {
local data_dir="$(pwd)/${dest_dir}/data" local data_dir="$(pwd)/${dest_dir}/data"
local exe_file="$(pwd)/${dest_dir}/lite-xl" local exe_file="$(pwd)/${dest_dir}/lite-xl"
local package_name=lite-xl$version-$platform-$arch local package_name=lite-xl$version-$platform-$arch
local bundle=false local bundle=false
local portable=false local portable=false
@ -190,14 +202,6 @@ main() {
if [[ $platform == "windows" ]]; then if [[ $platform == "windows" ]]; then
exe_file="${exe_file}.exe" exe_file="${exe_file}.exe"
stripcmd="strip --strip-all" stripcmd="strip --strip-all"
# Copy MinGW libraries dependencies.
# MSYS2 ldd command seems to be only 64bit, so use ntldd
# see https://github.com/msys2/MINGW-packages/issues/4164
ntldd -R "${exe_file}" \
| grep mingw \
| awk '{print $3}' \
| sed 's#\\#/#g' \
| xargs -I '{}' cp -v '{}' "$(pwd)/${dest_dir}/"
else else
# Windows archive is always portable # Windows archive is always portable
package_name+="-portable" package_name+="-portable"
@ -212,21 +216,18 @@ main() {
rm -rf "Lite XL.app"; mv "${dest_dir}" "Lite XL.app" rm -rf "Lite XL.app"; mv "${dest_dir}" "Lite XL.app"
dest_dir="Lite XL.app" dest_dir="Lite XL.app"
exe_file="$(pwd)/${dest_dir}/Contents/MacOS/lite-xl" exe_file="$(pwd)/${dest_dir}/Contents/MacOS/lite-xl"
data_dir="$(pwd)/${dest_dir}/Contents/Resources"
fi fi
fi fi
if [[ $bundle == false && $portable == false ]]; then if [[ $bundle == false && $portable == false ]]; then
echo "Creating a compressed archive..."
data_dir="$(pwd)/${dest_dir}/$prefix/share/lite-xl" data_dir="$(pwd)/${dest_dir}/$prefix/share/lite-xl"
exe_file="$(pwd)/${dest_dir}/$prefix/bin/lite-xl" exe_file="$(pwd)/${dest_dir}/$prefix/bin/lite-xl"
fi fi
mkdir -p "${data_dir}" mkdir -p "${data_dir}"
if [[ $addons == true ]]; then if [[ $addons == true ]]; then install_addons "${build_dir}" "${data_dir}"; fi
addons_download "${build_dir}"
addons_install "${build_dir}" "${data_dir}"
fi
# TODO: use --skip-subprojects when 0.58.0 will be available on supported # TODO: use --skip-subprojects when 0.58.0 will be available on supported
# distributions to avoid subprojects' include and lib directories to be copied. # distributions to avoid subprojects' include and lib directories to be copied.
@ -237,11 +238,8 @@ main() {
find . -type d -empty -delete find . -type d -empty -delete
popd popd
if [[ $release == true ]]; then $stripcmd "${exe_file}"
$stripcmd "${exe_file}"
fi
echo "Creating a compressed archive ${package_name}"
if [[ $binary == true ]]; then if [[ $binary == true ]]; then
rm -f "${package_name}".tar.gz rm -f "${package_name}".tar.gz
rm -f "${package_name}".zip rm -f "${package_name}".zip
@ -253,15 +251,9 @@ main() {
fi fi
fi fi
if [[ $appimage == true ]]; then if [[ $appimage == true ]]; then source scripts/appimage.sh; fi
source scripts/appimage.sh $flags --static if [[ $bundle == true && $dmg == true ]]; then source scripts/appdmg.sh "${package_name}"; fi
fi if [[ $innosetup == true ]]; then source scripts/innosetup/innosetup.sh -b "${build_dir}"; fi
if [[ $bundle == true && $dmg == true ]]; then
source scripts/appdmg.sh "${package_name}"
fi
if [[ $innosetup == true ]]; then
source scripts/innosetup/innosetup.sh $flags
fi
} }
main "$@" main "$@"

Some files were not shown because too many files have changed in this diff Show More