Compare commits

..

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

227 changed files with 2743 additions and 17549 deletions

View File

@ -30,9 +30,9 @@ jobs:
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)-portable" >> "$GITHUB_ENV"
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Python Setup - name: Python Setup
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.9 python-version: 3.9
- name: Update Packages - name: Update Packages
@ -47,21 +47,18 @@ jobs:
if: ${{ matrix.config.cc == 'gcc' }} if: ${{ matrix.config.cc == 'gcc' }}
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --binary run: bash scripts/package.sh --version ${INSTALL_REF} --debug --binary
- name: Upload Artifacts - name: Upload Artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
if: ${{ matrix.config.cc == 'gcc' }} if: ${{ matrix.config.cc == 'gcc' }}
with: with:
name: Linux Artifacts name: Linux Artifacts
path: ${{ env.INSTALL_NAME }}.tar.gz path: ${{ env.INSTALL_NAME }}.tar.gz
build_macos: build_macos:
name: macOS name: macOS (x86_64)
runs-on: macos-11 runs-on: macos-11
env: env:
CC: clang CC: clang
CXX: clang++ CXX: clang++
strategy:
matrix:
arch: ['x86_64', 'arm64']
steps: steps:
- name: System Information - name: System Information
run: | run: |
@ -73,64 +70,24 @@ jobs:
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-${{ matrix.arch }}" >> "$GITHUB_ENV" echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-macos-$(uname -m)" >> "$GITHUB_ENV"
if [[ $(uname -m) != ${{ matrix.arch }} ]]; then echo "ARCH=--cross-arch ${{ matrix.arch }}" >> "$GITHUB_ENV"; fi - uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Python Setup - name: Python Setup
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.9 python-version: 3.9
- name: Install Dependencies - name: Install Dependencies
# --lhelper will eliminate a warning with arm64 and libusb run: bash scripts/install-dependencies.sh --debug
run: bash scripts/install-dependencies.sh --debug --lhelper
- name: Build - name: Build
run: | run: |
bash --version bash --version
bash scripts/build.sh --bundle --debug --forcefallback $ARCH bash scripts/build.sh --bundle --debug --forcefallback
- name: Create DMG Image - name: Create DMG Image
run: bash scripts/package.sh --version ${INSTALL_REF} $ARCH --debug --dmg run: bash scripts/package.sh --version ${INSTALL_REF} --debug --dmg
- name: Upload DMG Image - name: Upload DMG Image
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: macOS DMG Images name: macOS DMG Image
path: ${{ env.INSTALL_NAME }}.dmg
build_macos_universal:
name: macOS (Universal)
runs-on: macos-11
needs: build_macos
steps:
- name: System Information
run: |
system_profiler SPSoftwareDataType
bash --version
gcc -v
xcodebuild -version
- name: Set Environment Variables
run: |
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-macos-universal" >> "$GITHUB_ENV"
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dmgbuild
run: pip install dmgbuild
- uses: actions/checkout@v3
- name: Download artifacts
uses: actions/download-artifact@v3
id: download
with:
name: macOS DMG Images
path: dmgs-original
- name: Make universal bundles
run: |
bash --version
bash scripts/make-universal-binaries.sh ${{ steps.download.outputs.download-path }} "${INSTALL_NAME}"
- name: Upload DMG Image
uses: actions/upload-artifact@v3
with:
name: macOS Universal DMG Images
path: ${{ env.INSTALL_NAME }}.dmg path: ${{ env.INSTALL_NAME }}.dmg
build_windows_msys2: build_windows_msys2:
@ -138,26 +95,20 @@ jobs:
runs-on: windows-2019 runs-on: windows-2019
strategy: strategy:
matrix: matrix:
config: msystem: [MINGW32, MINGW64]
- {msystem: MINGW32, arch: i686}
- {msystem: MINGW64, arch: x86_64}
defaults: defaults:
run: run:
shell: msys2 {0} shell: msys2 {0}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: msys2/setup-msys2@v2 - uses: msys2/setup-msys2@v2
with: with:
msystem: ${{ matrix.config.msystem }} msystem: ${{ matrix.msystem }}
update: true
install: >- install: >-
base-devel base-devel
git git
zip zip
mingw-w64-${{ matrix.config.arch }}-gcc
mingw-w64-${{ matrix.config.arch }}-meson
mingw-w64-${{ matrix.config.arch }}-ninja
mingw-w64-${{ matrix.config.arch }}-ca-certificates
mingw-w64-${{ matrix.config.arch }}-ntldd
- name: Set Environment Variables - name: Set Environment Variables
run: | run: |
echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
@ -168,7 +119,6 @@ jobs:
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-windows-i686" >> "$GITHUB_ENV" echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-windows-i686" >> "$GITHUB_ENV"
fi fi
- name: Install Dependencies - name: Install Dependencies
if: false
run: bash scripts/install-dependencies.sh --debug run: bash scripts/install-dependencies.sh --debug
- name: Build - name: Build
run: | run: |
@ -177,51 +127,7 @@ jobs:
- name: Package - name: Package
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --binary run: bash scripts/package.sh --version ${INSTALL_REF} --debug --binary
- name: Upload Artifacts - name: Upload Artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: Windows Artifacts name: Windows Artifacts
path: ${{ env.INSTALL_NAME }}.zip path: ${{ env.INSTALL_NAME }}.zip
build_windows_msvc:
name: Windows (MSVC)
runs-on: windows-2019
strategy:
matrix:
arch:
- { target: x86, name: i686 }
- { target: x64, name: x86_64 }
steps:
- uses: actions/checkout@v3
- uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.arch.target }}
- uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install meson and ninja
run: pip install meson ninja
- name: Set up environment variables
run: |
"INSTALL_NAME=lite-xl-$($env:GITHUB_REF -replace ".*/")-windows-msvc-${{ matrix.arch.name }}" >> $env:GITHUB_ENV
"INSTALL_REF=$($env:GITHUB_REF -replace ".*/")" >> $env:GITHUB_ENV
"LUA_SUBPROJECT_PATH=subprojects/$(awk -F ' *= *' '/directory/ { printf $2 }' subprojects/lua.wrap)" >> $env:GITHUB_ENV
- name: Download and patch subprojects
shell: bash
run: |
meson subprojects download
cat resources/windows/001-lua-unicode.diff | patch -Np1 -d "$LUA_SUBPROJECT_PATH"
- name: Configure
run: |
meson setup --wrap-mode=forcefallback build
- name: Build
run: |
meson install -C build --destdir="../lite-xl"
- name: Package
run: |
Remove-Item -Recurse -Force -Path "lite-xl/lib","lite-xl/include"
Compress-Archive -Path lite-xl -DestinationPath "$env:INSTALL_NAME.zip"
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: Windows Artifacts (MSVC)
path: ${{ env.INSTALL_NAME }}.zip

View File

@ -1,29 +1,23 @@
name: Release name: Release
on: on:
push:
tags:
- v[0-9]+.*
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version: version:
description: Release Version description: Release Version
default: v2.1.4 default: v2.1.0
required: true required: true
jobs: jobs:
release: release:
name: Create Release name: Create Release
runs-on: ubuntu-latest runs-on: ubuntu-20.04
permissions:
contents: write
outputs: outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
version: ${{ steps.tag.outputs.version }} version: ${{ steps.tag.outputs.version }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Fetch Version - name: Fetch Version
id: tag id: tag
run: | run: |
@ -32,12 +26,6 @@ jobs:
else else
echo ::set-output name=version::${GITHUB_REF/refs\/tags\//} echo ::set-output name=version::${GITHUB_REF/refs\/tags\//}
fi fi
- name: Update Tag
uses: richardsimko/update-tag@v1
with:
tag_name: ${{ steps.tag.outputs.version }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release - name: Create Release
id: create_release id: create_release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
@ -45,14 +33,14 @@ jobs:
tag_name: ${{ steps.tag.outputs.version }} tag_name: ${{ steps.tag.outputs.version }}
name: Lite XL ${{ steps.tag.outputs.version }} name: Lite XL ${{ steps.tag.outputs.version }}
draft: true draft: true
prerelease: false
body_path: changelog.md body_path: changelog.md
generate_release_notes: true generate_release_notes: true
build_linux: build_linux:
name: Linux name: Linux
needs: release needs: release
runs-on: ubuntu-latest runs-on: ubuntu-20.04
container: ghcr.io/lite-xl/lite-xl-build-box:latest
env: env:
CC: gcc CC: gcc
CXX: g++ CXX: g++
@ -61,27 +49,17 @@ jobs:
run: | run: |
echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV" echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
- uses: actions/checkout@v3 - uses: actions/checkout@v2
# disabled because this will break our own Python install
- name: Python Setup - name: Python Setup
if: false uses: actions/setup-python@v2
uses: actions/setup-python@v4
with: with:
python-version: 3.9 python-version: 3.9
# disabled because the container has up-to-date packages
- name: Update Packages - name: Update Packages
if: false
run: sudo apt-get update run: sudo apt-get update
# disabled as the dependencies are already installed
- name: Install Dependencies - name: Install Dependencies
if: false
run: | run: |
bash scripts/install-dependencies.sh --debug bash scripts/install-dependencies.sh --debug
sudo apt-get install -y ccache sudo apt-get install -y ccache
- name: Build Portable - name: Build Portable
run: | run: |
bash --version bash --version
@ -98,7 +76,6 @@ jobs:
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
tag_name: ${{ needs.release.outputs.version }} tag_name: ${{ needs.release.outputs.version }}
draft: true
files: | files: |
lite-xl-${{ env.INSTALL_REF }}-linux-x86_64-portable.tar.gz lite-xl-${{ env.INSTALL_REF }}-linux-x86_64-portable.tar.gz
lite-xl-${{ env.INSTALL_REF }}-addons-linux-x86_64-portable.tar.gz lite-xl-${{ env.INSTALL_REF }}-addons-linux-x86_64-portable.tar.gz
@ -106,12 +83,9 @@ jobs:
LiteXL-${{ env.INSTALL_REF }}-addons-x86_64.AppImage LiteXL-${{ env.INSTALL_REF }}-addons-x86_64.AppImage
build_macos: build_macos:
name: macOS name: macOS (x86_64)
needs: release needs: release
runs-on: macos-11 runs-on: macos-11
strategy:
matrix:
arch: [x86_64, arm64]
env: env:
CC: clang CC: clang
CXX: clang++ CXX: clang++
@ -126,12 +100,11 @@ jobs:
run: | run: |
echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV" echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
echo "INSTALL_NAME=lite-xl-${{ needs.release.outputs.version }}-macos-${{ matrix.arch }}" >> "$GITHUB_ENV" echo "INSTALL_NAME=lite-xl-${{ needs.release.outputs.version }}-macos-$(uname -m)" >> "$GITHUB_ENV"
echo "INSTALL_NAME_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-macos-${{ matrix.arch }}" >> "$GITHUB_ENV" echo "INSTALL_NAME_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-macos-$(uname -m)" >> "$GITHUB_ENV"
if [[ $(uname -m) != ${{ matrix.arch }} ]]; then echo "ARCH=--cross-arch ${{ matrix.arch }}" >> "$GITHUB_ENV"; fi - uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Python Setup - name: Python Setup
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: 3.9 python-version: 3.9
- name: Install Dependencies - name: Install Dependencies
@ -139,75 +112,19 @@ jobs:
- name: Build - name: Build
run: | run: |
bash --version bash --version
bash scripts/build.sh --bundle --debug --forcefallback --release $ARCH bash scripts/build.sh --bundle --debug --forcefallback --release
- name: Create DMG Image - name: Create DMG Image
run: | run: |
bash scripts/package.sh --version ${INSTALL_REF} $ARCH --debug --dmg --release bash scripts/package.sh --version ${INSTALL_REF} --debug --dmg --release
bash scripts/package.sh --version ${INSTALL_REF} $ARCH --debug --addons --dmg --release bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --dmg --release
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: macOS DMG Images
path: |
${{ env.INSTALL_NAME }}.dmg
${{ env.INSTALL_NAME_ADDONS }}.dmg
- name: Upload Files - name: Upload Files
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
tag_name: ${{ needs.release.outputs.version }} tag_name: ${{ needs.release.outputs.version }}
draft: true
files: | files: |
${{ env.INSTALL_NAME }}.dmg ${{ env.INSTALL_NAME }}.dmg
${{ env.INSTALL_NAME_ADDONS }}.dmg ${{ env.INSTALL_NAME_ADDONS }}.dmg
build_macos_universal:
name: macOS (Universal)
needs: [release, build_macos]
runs-on: macos-11
steps:
- name: System Information
run: |
system_profiler SPSoftwareDataType
bash --version
gcc -v
xcodebuild -version
- name: Set Environment Variables
run: |
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
echo "INSTALL_BASE=lite-xl-${{ needs.release.outputs.version }}-macos" >> "$GITHUB_ENV"
echo "INSTALL_BASE_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-macos" >> "$GITHUB_ENV"
- uses: actions/checkout@v2
- name: Download Artifacts
uses: actions/download-artifact@v3
id: download
with:
name: macOS DMG Images
path: dmgs-original
- name: Python Setup
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dmgbuild
run: pip install dmgbuild
- name: Prepare DMG Images
run: |
mkdir -p dmgs-addons dmgs-normal
mv -v "${{ steps.download.outputs.download-path }}/$INSTALL_BASE-"{x86_64,arm64}.dmg dmgs-normal
mv -v "${{ steps.download.outputs.download-path }}/$INSTALL_BASE_ADDONS-"{x86_64,arm64}.dmg dmgs-addons
- name: Create Universal DMGs
run: |
bash --version
bash scripts/make-universal-binaries.sh dmgs-normal "$INSTALL_BASE-universal"
bash scripts/make-universal-binaries.sh dmgs-addons "$INSTALL_BASE_ADDONS-universal"
- name: Upload Files
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.release.outputs.version }}
draft: true
files: |
${{ env.INSTALL_BASE }}-universal.dmg
${{ env.INSTALL_BASE_ADDONS }}-universal.dmg
build_windows_msys2: build_windows_msys2:
name: Windows name: Windows
needs: release needs: release
@ -219,7 +136,7 @@ jobs:
run: run:
shell: msys2 {0} shell: msys2 {0}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- uses: msys2/setup-msys2@v2 - uses: msys2/setup-msys2@v2
with: with:
msystem: ${{ matrix.msystem }} msystem: ${{ matrix.msystem }}
@ -259,7 +176,6 @@ jobs:
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
tag_name: ${{ needs.release.outputs.version }} tag_name: ${{ needs.release.outputs.version }}
draft: true
files: | files: |
${{ env.INSTALL_NAME }}.zip ${{ env.INSTALL_NAME }}.zip
${{ env.INSTALL_NAME_ADDONS }}.zip ${{ env.INSTALL_NAME_ADDONS }}.zip

3
.gitignore vendored
View File

@ -22,10 +22,9 @@ LiteXL*
lite lite
.config/ .config/
*.lha *.lha
release_files
*.o *.o
*.snalyzerinfo *.snalyzerinfo
!resources/windows/*.diff !resources/windows/*.diff
!resources/windows/*.exe.manifest.in
!resources/macos/*.py

View File

@ -1,6 +1,4 @@
Copyright (c) 2020 rxi Copyright (c) 2020-2021 Lite XL Team
Copyright (c) 2020-2022 Francesco Abbate
Copyright (c) 2022-present Lite XL Team
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,7 +1,7 @@
# #
# Project: Lite XL # Project: Lite XL
# #
# Created on: 26-12-2021 # Created on: 26-12-2021
# #
LiteXL_OBJ := \ LiteXL_OBJ := \
@ -9,22 +9,18 @@ LiteXL_OBJ := \
src/api/api.o src/api/dirmonitor.o \ src/api/api.o src/api/dirmonitor.o \
src/api/regex.o src/api/renderer.o src/api/system.o \ src/api/regex.o src/api/renderer.o src/api/system.o \
src/api/utf8.o src/platform/morphos.o \ src/api/utf8.o src/platform/morphos.o \
src/api/dirmonitor/mos.o src/platform/codesets.o src/api/dirmonitor/mos.o
outfile := lite-xl
compiler := ppc-morphos-gcc-11
cxxcompiler := ppc-morphos-g++-11
INCPATH := -Isrc -Ilib/dmon -I/sdk/gg/usr/local/include/SDL2 \ outfile := lite
-I/sdk/gg/usr/include/freetype -I/sdk/gg/usr/include/lua5.4 compiler := gcc
DFLAGS ?= -D__USE_INLINE__ cxxcompiler := g++
CFLAGS ?= -Wall -Wwrite-strings -O2 -noixemul -g -std=gnu11 -fno-strict-aliasing
LFLAGS ?= -noixemul -lpcre2-8 -lSDL2 -llua54 -lagg -lfreetype -lm -lc -L/usr/local/lib 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
ifeq ($(DEBUG),1)
CFLAGS += -g -gstabs
LFLAGS += -gstabs
endif
.PHONY: LiteXL clean release .PHONY: LiteXL clean release
@ -36,11 +32,13 @@ 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/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/morphos.h src/renderer.h src/platform/morphos.h
@ -67,23 +65,22 @@ src/api/utf8.o: src/api/utf8.c
src/api/dirmonitor/mos.o: src/api/dirmonitor/mos.c src/api/dirmonitor/mos.o: src/api/dirmonitor/mos.c
src/platform/codesets.o: src/platform/codesets.c
release: clean LiteXL release: clean LiteXL
@echo "Creating release files..." @echo "Creating release files..."
@mkdir -p release/LiteXL2 @mkdir -p release/LiteXL2
@cp -r resources/amiga/* release/LiteXL2/ @cp release_files/* release/LiteXL2/ -r
@mv release/LiteXL2/LiteXL2.info release/ @mv release/LiteXL2/LiteXL2.info release/
@rm release/LiteXL2/AutoInstall @cp data release/LiteXL2/ -r
@cp -r data release/LiteXL2/
@cp changelog.md release/LiteXL2/ @cp changelog.md release/LiteXL2/
@cp $(outfile) release/LiteXL2/ @cp lite release/LiteXL2/
@strip release/LiteXL2/$(outfile) @strip release/LiteXL2/lite
@cp README.md release/LiteXL2/ @cp README.md release/LiteXL2/
@cp README_Amiga.md release/LiteXL2/ @cp README_Amiga.md release/LiteXL2/
@cp LICENSE release/LiteXL2/ @cp LICENSE release/LiteXL2/
@cp -r licenses release/LiteXL2/
@echo "Creating release archive..." @echo "Creating release archive..."
@lha -aeqr3 a LiteXL2_MOS.lha release/ @lha -aeqr3 a LiteXL2_MOS.lha release/
@echo "Clean release files..." @echo "Clean release files..."
@delete release ALL QUIET FORCE @delete release ALL QUIET FORCE

View File

@ -1,7 +1,7 @@
# #
# Project: Lite XL # Project: Lite XL
# #
# Created on: 26-12-2021 # Created on: 26-12-2021
# #
LiteXL_OBJ := \ LiteXL_OBJ := \
@ -9,22 +9,25 @@ LiteXL_OBJ := \
src/api/api.o src/api/dirmonitor.o \ src/api/api.o src/api/dirmonitor.o \
src/api/regex.o src/api/renderer.o src/api/system.o \ src/api/regex.o src/api/renderer.o src/api/system.o \
src/api/utf8.o src/platform/amigaos4.o \ src/api/utf8.o src/platform/amigaos4.o \
src/api/dirmonitor/os4.o src/platform/codesets.o src/api/dirmonitor/os4.o
outfile := lite-xl
outfile := lite
compiler := gcc compiler := gcc
cxxcompiler := g++ cxxcompiler := g++
INCPATH := -Isrc -I/sdk/local/newlib/include/SDL2 \ INCPATH := -Isrc -Ilib/dmon -I/sdk/local/newlib/include/SDL2 \
-I/sdk/local/common/include/lua54 -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
DFLAGS += -D__USE_INLINE__ -DLITE_XL_DATA_USE_EXEDIR CFLAGS += -Werror -Wwrite-strings -O2 -g -std=gnu11 -fno-strict-aliasing
CFLAGS ?= -Werror -Wwrite-strings -O3 -std=gnu11 -fno-strict-aliasing
LFLAGS ?= -mcrt=newlib -lpcre2-8 -lSDL2 -llua54 -lfreetype -lpng -lz \ LFLAGS += -mcrt=newlib -lauto \
-lpthread -athread=native -lpcre2 -lSDL2 -llua54 -lfreetype -lz -lm -lpthread -athread=native
ifeq ($(DEBUG),1) ifeq ($(DEBUG),1)
CFLAGS += -g -gstabs CFLAGS += -gstabs
LFLAGS += -gstabs LFLAGS += -gstabs
ifeq ($(PROFYLER),1) ifeq ($(PROFYLER),1)
@ -45,7 +48,7 @@ clean:
LiteXL: $(LiteXL_OBJ) LiteXL: $(LiteXL_OBJ)
@echo "Linking LiteXL" @echo "Linking LiteXL"
@$(compiler) -o $(outfile) $(LiteXL_OBJ) $(LFLAGS) @$(compiler) -o $(outfile) $(LiteXL_OBJ) $(LFLAGS)
.c.o: .c.o:
@ -54,7 +57,7 @@ LiteXL: $(LiteXL_OBJ)
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/platform/codesets.h src/renderer.h src/platform/amigaos4.h
src/rencache.o: src/rencache.c src/rencache.o: src/rencache.c
@ -68,35 +71,30 @@ src/api/regex.o: src/api/regex.c
src/api/renderer.o: src/api/renderer.c src/api/renderer.o: src/api/renderer.c
src/api/system.o: src/api/system.c src/platform/amigaos4.h src/api/system.o: src/api/system.c
src/platform/amigaos4.o: src/platform/amigaos4.c src/platform/amigaos4.o: src/platform/amigaos4.c
src/platform/codesets.o: src/platform/codesets.c
src/api/dirmonitor.o: src/api/dirmonitor.c src/api/dirmonitor/os4.c src/api/dirmonitor.o: src/api/dirmonitor.c src/api/dirmonitor/os4.c
src/api/utf8.o: src/api/utf8.c src/platform/amigaos4.h src/api/utf8.o: src/api/utf8.c
src/api/dirmonitor/os4.o: src/api/dirmonitor/os4.c src/api/dirmonitor/os4.o: src/api/dirmonitor/os4.c
src/api/process.o: src/api/process.c release:
release: clean LiteXL
@echo "Creating release files..." @echo "Creating release files..."
@mkdir -p release/LiteXL2 @mkdir -p release/LiteXL2
@cp -r resources/amiga/* release/LiteXL2/ @cp -r release_files/* release/LiteXL2/
@mv release/LiteXL2/LiteXL2.info release/ @mv release/LiteXL2/LiteXL2.info release/
@mv release/LiteXL2/AutoInstall release/
@cp -r data release/LiteXL2/ @cp -r data release/LiteXL2/
@cp changelog.md release/LiteXL2/ @cp changelog.md release/LiteXL2/
@cp $(outfile) release/LiteXL2/ @cp lite release/LiteXL2/
@strip release/LiteXL2/$(outfile) @strip release/LiteXL2/lite
@cp README.md release/LiteXL2/ @cp README.md release/LiteXL2/
@cp README_Amiga.md release/LiteXL2/ @cp README_Amiga.md release/LiteXL2/
@cp LICENSE release/LiteXL2/ @cp LICENSE release/LiteXL2/
@cp -r licenses release/LiteXL2/
@echo "Creating release archive..." @echo "Creating release archive..."
@lha -aeqr3 a LiteXL2_OS4.lha release/ @lha -aeqr3 a LiteXL2_OS4.lha release/
@echo "Clean release files..." @echo "Clean release files..."
@delete release ALL QUIET FORCE @delete release ALL QUIET FORCE

View File

@ -81,39 +81,6 @@ affects only the place where the application is actually installed.
Head over to [releases](https://github.com/lite-xl/lite-xl/releases) and download the version for your operating system. Head over to [releases](https://github.com/lite-xl/lite-xl/releases) and download the version for your operating system.
### Windows
Lite XL comes with installers on Windows for typical installations.
Alternatively, we provide ZIP archives that you can download and extract anywhere and run directly.
To make Lite XL portable (e.g. running Lite XL from a thumb drive),
simply create a `user` folder where `lite-xl.exe` is located.
Lite XL will load and store all your configurations and plugins in the folder.
### macOS
We provide DMG files for macOS. Simply drag the program into your Applications folder.
> **Important**
> Newer versions of Lite XL are signed with a self-signed certificate,
> so you'll have to follow these steps when running Lite XL for the first time.
>
> 1. Find Lite XL in Finder (do not open it in Launchpad).
> 2. Control-click Lite XL, then choose `Open` from the shortcut menu.
> 3. Click `Open` in the popup menu.
>
> The correct steps may vary between macOS versions, so you should refer to
> the [macOS User Guide](https://support.apple.com/en-my/guide/mac-help/mh40616/mac).
>
> On an older version of Lite XL, you will need to run these commands instead:
>
> ```sh
> # clears attributes from the directory
> xattr -cr /Applications/Lite\ XL.app
> ```
>
> Otherwise, macOS will display a **very misleading error** saying that the application is damaged.
### Linux ### Linux
Unzip the file and `cd` into the `lite-xl` directory: Unzip the file and `cd` into the `lite-xl` directory:
@ -125,15 +92,15 @@ cd lite-xl
To run lite-xl without installing: To run lite-xl without installing:
```sh ```sh
cd bin
./lite-xl ./lite-xl
``` ```
To install lite-xl copy files over into appropriate directories: To install lite-xl copy files over into appropriate directories:
```sh ```sh
rm -rf $HOME/.local/share/lite-xl $HOME/.local/bin/lite-xl mkdir -p $HOME/.local/bin && cp bin/lite-xl $HOME/.local/bin
mkdir -p $HOME/.local/bin && cp lite-xl $HOME/.local/bin/ cp -r share $HOME/.local
mkdir -p $HOME/.local/share/lite-xl && cp -r data/* $HOME/.local/share/lite-xl/
``` ```
If `$HOME/.local/bin` is not in PATH: If `$HOME/.local/bin` is not in PATH:

View File

@ -1,21 +1,24 @@
# Lite XL v2 for AmigaOS 4.1 FE & MorphOS 3 # Lite XL v2 for AmigaOS 4.1 FE & MorphOS 3
Lite XL is a lightweight text editor written in Lua and SDL2. Lite XL is a lightweight text editor written in Lua.
The port is not perfect and it might have issues here and there. It might The port is not perfect and it might have issues here and there. For example
crash from time to time, if there is a path problem, but overall it works the filesystem notifications are not working yet. So when you make changes
pretty well. This is my daily editor for any kind of development. 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 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. If it crashes on your system, try to delete to `.config` folder.
## Installation ## 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 installation well as plugins, themes etc.. By default this version uses the
folder, but if you want to override it, you can create an ENV variable executable folder, but if you want to override it, you can create an ENV
named `HOME` and set there your prefferable path. variable named `HOME` and set there your prefferable 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
@ -24,7 +27,7 @@ GetEnv HOME
``` ```
If there is one set, then you will see the path at the output. If there is one set, then you will see the path at the output.
Otherwise, you can set your home path be executing the following command. Otherwise, you can set your home path be executing the following command.
Change the path to the one of your preference. Change the path to the one of your preference.
``` ```
SetEnv SAVE HOME "Sys:home/" SetEnv SAVE HOME "Sys:home/"
@ -33,7 +36,7 @@ SetEnv SAVE HOME "Sys:home/"
## Addons ## Addons
### Colors ### Colors
Colors are lua files that set the color scheme of the editor. There are Colors are lua files that set the color scheme of the editor. There are
light and dark themes for you to choose. light and dark themes for you to choose.
To install and use them you have to copy the ones you would like from To install and use them you have to copy the ones you would like from
`addons/colors/light` or `addons/colors/dark` into the folder `addons/colors/light` or `addons/colors/dark` into the folder
@ -45,19 +48,19 @@ at the cog icon at the toolbar (bottom left sixth icon). Go at the line
that looks like below that looks like below
``` ```
-- core.reload_module("colors.summer") -- core.reload_module("colors.summer")
``` ```
and change the `summer` with the name of your color theme. Also, remove and change the `summer` with the name of your color theme. Also, remove
the two dashes `--` at the start of the line and save the file. If you the two dashes `--` at the start of the line and save the file. If you
did everything right, the color schema should change instantly. did everything right, the color schema should change instantly.
The themes can also be found at 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
LiteXL is able to use plugins to extend its features. Those can be found This Lite XL release is based on version 2.0.3 of the application as
at https://github.com/lite-xl/lite-xl-plugins and other websites. Not all released on other systems, by the original development team.
of them will work fine on AmigaOS 4 or MorphOS, because of missing This not the latest version. This means that some of the latest
dependencies or filesystem issues. plugins might not working at all or need 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
@ -71,10 +74,10 @@ need.
The included plugins are the following: 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** **autosaveonfocuslost**
Automatically saves files that were changed when the main window loses Automatically saves files that were changed when the main window loses
focus by switching to another application focus by switching to another application
@ -87,28 +90,10 @@ Shows the current time and date in a view with large text
**bracketmatch** **bracketmatch**
Underlines matching pair for bracket under the caret Underlines matching pair for bracket under the caret
**codesets**
This plugin uses the codesets.library on AmigaOS 4 and the
charsets.library on MorphOS to translate ISO encoded files to unicode
and vice-versa. When this is enabled new menu items are added to
load/save the code with a different encoding. Also there is a new
section at the status bar that show the file encoding.
This plugin is **EXPERIMENTAL** and heavily inspired from the encoding
plugin at https://github.com/jgmdev/lite-xl-encoding
**colorpreview** **colorpreview**
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.
**custom_caret**
Customize the caret in the editor setting it to *underline*, *block* or
*line* at the init.lua file in your config folder.
For example add:
`config.plugins.custom_caret.shape = "block"`
**EditorConfig**
EditorConfig (https://editorconfig.org/) implementation for Lite XL
**ephemeral_tabs** **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.
@ -116,10 +101,7 @@ 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 On AmigaOS 4 it uses *urlopen* and on MorphOS it uses *openurl* to load
the generated html in the browser. It requires a GitHub application token the generated html in the browser.
because it uses its Rest API. Add it at the init.lua file in your config
folder like below:
`config.plugins.ghmarkdown.github_token = "<token here>"`
**indentguide** **indentguide**
Adds indent guides Adds indent guides
@ -143,19 +125,19 @@ Automatically inserts indentation and closing bracket/text after newline
Add markers to docs and jump between them quickly Add markers to docs and jump between them quickly
**minimap** **minimap**
Shows a minimap on the right-hand side of the docview. Please note that Shows a minimap on the right-hand side of the docview. Please note that
this plugin will make the editor slower on file loading and scrolling. this plugin will make the editor slower on file loading and scrolling.
**navigate** **navigate**
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** **nonicons**
File icons set for TreeView. Download TTF font to your config/fonts File icons set for TreeView. Download TTF font to your config/fonts
folder from https://github.com/yamatsum/nonicons/tree/master/dist folder from https://github.com/yamatsum/nonicons/tree/master/dist
**opacity** **opacity**
Change the opaqueness/transparency of lite-xl using LAmiga+mousewheel Change the opaqueness/transparency of lite-xl using shift+mousewheel
or a command. or a command.
**openfilelocation** **openfilelocation**
@ -165,7 +147,7 @@ Opens the parent directory of the current file in the file manager
Show nesting of parentheses with rainbow colours Show nesting of parentheses with rainbow colours
**restoretabs** **restoretabs**
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_colorscheme**
@ -178,16 +160,14 @@ Highlights regions of code that match the current selection
**smallclock** **smallclock**
It adds a small clock at the bottom right corner. It adds a small clock at the bottom right corner.
**tetris**
Play Tetris inside Lite XL.
## Tips and tricks ## Tips and tricks
### Transitions ### Transitions
If you want to disable the transitions and make the editor faster, If you want to disable the transitions and make the editor faster,
open your configuration file by clicking at the cog icon at the toolbar open your configuration file by clicking at the cog icon at the toolbar
(bottom left, 6th icon) and add the following line at the end of the file, (bottom left, 6th icon) and add the following line at the end of the file,
and then save it. You might need to restart your editor. and then save it. You might need to restart your editor (CTRL+SHIFT+R)
``` ```
config.transitions = false config.transitions = false
@ -196,16 +176,16 @@ config.transitions = false
### Hide files from the file list ### Hide files from the file list
If you would like to hide files or whole folder from the left side bar list, If you would like to hide files or whole folder from the left side bar list,
open your configuration by clicking at the cog icon at the toolbar open your configuration by clicking at the cog icon at the toolbar
(bottom left sixth icon) and add the followline at the end of the file and (bottom left sixth icon) and add the followline at the end of the file and
save it. This hides all the files that start with a dot, and all the `.info` save it. This hides all the files that start with a dot, and all the `.info`
files. You might need to restart your editor. files. You might need to restart your editor (CTRL+SHIFT+R)
``` ```
config.ignore_files = {"^%.", "%.info$"} config.ignore_files = {"^%.", "%.info$"}
``` ```
You can add as many rules as you want in there, to hide files or You can add as many rules as you want in there, to hide files or
folders, as you like. folders, as you like.
## I would like to thank ## I would like to thank
@ -225,81 +205,11 @@ please consider to buy me a coffee at:
https://ko-fi.com/walkero https://ko-fi.com/walkero
## Known issues ## Known issues
You can find the known issues at You can find the known issues at
https://git.walkero.gr/walkero/lite-xl/issues https://git.walkero.gr/walkero/lite-xl/issues
# Changelog # Changelog
## [2.1.4r1] - future
### Added
- Added the ability to open files and folders by drag 'n drop them on the
LiteXL icon when this is on the AmiDock
### Updated
- Updated the code to the upstream 2.1.4 release
### Fixed
- Fix opening files from the root of a device
## [2.1.3r1] - 2024-03-09
### Added
- Added AmiUpdate support
- Added the Tetris plugin
### Updated
- Updated the code to the upstream 2.1.3 release
- Compiled with SDL 2.30.0 that supports editing with ISO encodings, other
than English. Now the editor should be able to support any language
and in conjuction with the codesets plugin be able to make text
encodings conversions
- Now the codesets plugin supports MorphOS 3.18 and above
### Changed
- Changed the way the "Open in system" option executes the WBRun command
in AmigaOS 4, with a more secure way
- Did a lot of code cleanup in sync with the upstream, and parts of code
that were left over
- Compiled with pcre2 10.42 (MorphOS version only)
### Fixed
- I did a lot of changes on path manipulation and usage, fixing scanning
the root of a partition or an assign path
- Fixed an error with the codesets plugin, where an empty file could
not be opened
- Improved the folder suggestions when opening projects or changing paths.
Now even the root folders of a partition are presented
- Fixed ghmarkdown plugin, but now requires a GitHub token to be provided
## [2.1.2r1] - 2023-12-19
### Added
- Added the new experimental codesets plugin (AmigaOS4 version only).
MorphOS version is in WIP
### Changed
- Synced with the latest upstream v2.1.2 code
- Compiled with gcc 11.3.0
- Compiled with SDL 2.28.4
- Compiled with libfreetype 2.13.x
- Compiled with lua 5.4.6
- Compiled with linpng 1.6.40 (AmigaOS4 version only)
- Compiled with libz 1.2.13 (AmigaOS4 version only)
## [2.1.1r2] - 2022-05-14
### Changed
- Compiled with latest SDL v2.26.5-rc2
## [2.1.1r1] - 2022-01-29
### Changed
- Binary name changed to lite-xl
- Updated the colour themes and the plugins that are included in the release
- Compiled with latest SDL 2.26
- Compiled with gcc 11
- Synced the code with the upstream master branch at 8th January 2023
### Fixed
- Set the default locale on AmigaOS 4, so as to fix some issues with decimal
numbers
## [2.1.0r1] - 2022-10-10 ## [2.1.0r1] - 2022-10-10
### Added ### Added
- This version of LiteXL recognises changes that are done outside the editor - This version of LiteXL recognises changes that are done outside the editor
@ -315,9 +225,9 @@ https://git.walkero.gr/walkero/lite-xl/issues
### Fixed ### Fixed
- Fixed regex issues with some plugins - Fixed regex issues with some plugins
- Fixed "Open in System" on AmigaOS 4 and MorphOS. When you right click - 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 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 the type of the item opens on Workbench. A folder opens in a list view
and a file opens with its default tool and a file opens with its default tool
- Fixed markdown preview on MorphOS. Now, it calls openurl with the html - Fixed markdown preview on MorphOS. Now, it calls openurl with the html
file (#20) file (#20)
@ -331,7 +241,7 @@ https://git.walkero.gr/walkero/lite-xl/issues
### Fixed ### Fixed
- Fixed non existing path crashes on OS4 and MorphOS - Fixed non existing path crashes on OS4 and MorphOS
- Fixed editor refresh whenever init.lua is changed, no matter the working - Fixed editor refresh whenever init.lua is changed, no matter the working
folder folder
- Fixed an issue when the user added a directory in the project that - Fixed an issue when the user added a directory in the project that
already existed already existed
@ -340,7 +250,7 @@ https://git.walkero.gr/walkero/lite-xl/issues
- Fixed "Find" on MorphOS that was not working (shortcut CTRL+F) - 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 - If the user selects to change the project folder and inserts Sys: or any
partition name, the included folders will be listed as suggestions partition name, the included folders will be listed as suggestions
### Changed ### Changed
- Removed linking with unix on OS4 build - Removed linking with unix on OS4 build
- Makefiles updated - Makefiles updated
@ -357,6 +267,7 @@ https://git.walkero.gr/walkero/lite-xl/issues
### Fixed ### Fixed
- Fixed the usage of NumPad (reported by root) - 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
@ -366,3 +277,4 @@ https://git.walkero.gr/walkero/lite-xl/issues
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 problems you might get
by using this software. by using this software.

View File

@ -13,36 +13,33 @@ show_help() {
echo echo
echo "Common options:" echo "Common options:"
echo echo
echo "-h --help Show this help and exit." echo "-h --help Show this help and exit."
echo "-b --builddir DIRNAME Set the name of the build directory (not path)." echo "-b --builddir DIRNAME Set the name of the build directory (not path)."
echo " Default: '$(get_default_build_dir)'." echo " Default: '$(get_default_build_dir)'."
echo "-p --prefix PREFIX Install directory prefix." echo "-p --prefix PREFIX Install directory prefix."
echo " Default: '/'." echo " Default: '/'."
echo " --cross-platform PLATFORM The platform to cross compile for." echo " --debug Debug this script."
echo " --cross-arch ARCH The architecture to cross compile for."
echo " --debug Debug this script."
echo echo
echo "Build options:" echo "Build options:"
echo echo
echo "-f --forcefallback Force to build subprojects dependencies statically." echo "-f --forcefallback Force to build subprojects dependencies statically."
echo "-B --bundle Create an App bundle (macOS only)" echo "-B --bundle Create an App bundle (macOS only)"
echo "-P --portable Create a portable package." echo "-P --portable Create a portable package."
echo "-O --pgo Use profile guided optimizations (pgo)." echo "-O --pgo Use profile guided optimizations (pgo)."
echo " Requires running the application iteractively." echo " Requires running the application iteractively."
echo " --cross-file CROSS_FILE The cross file used for compiling."
echo echo
echo "Package options:" echo "Package options:"
echo echo
echo "-d --destdir DIRNAME Set the name of the package directory (not path)." echo "-d --destdir DIRNAME Set the name of the package directory (not path)."
echo " Default: 'lite-xl'." echo " Default: 'lite-xl'."
echo "-v --version VERSION Sets the version on the package name." echo "-v --version VERSION Sets the version on the package name."
echo "-A --appimage Create an AppImage (Linux only)." echo "-A --appimage Create an AppImage (Linux only)."
echo "-D --dmg Create a DMG disk image (macOS only)." echo "-D --dmg Create a DMG disk image (macOS only)."
echo " Requires dmgbuild." 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 "-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
} }
@ -63,12 +60,6 @@ main() {
local portable local portable
local pgo local pgo
local release local release
local cross_platform
local cross_platform_option=()
local cross_arch
local cross_arch_option=()
local cross_file
local cross_file_option=()
for i in "$@"; do for i in "$@"; do
case $i in case $i in
@ -132,21 +123,6 @@ main() {
pgo="--pgo" pgo="--pgo"
shift shift
;; ;;
--cross-platform)
cross_platform="$2"
shift
shift
;;
--cross-arch)
cross_arch="$2"
shift
shift
;;
--cross-file)
cross_file="$2"
shift
shift
;;
--debug) --debug)
debug="--debug" debug="--debug"
set -x set -x
@ -167,18 +143,10 @@ main() {
if [[ -n $dest_dir ]]; then dest_dir_option=("--destdir" "${dest_dir}"); fi if [[ -n $dest_dir ]]; then dest_dir_option=("--destdir" "${dest_dir}"); fi
if [[ -n $prefix ]]; then prefix_option=("--prefix" "${prefix}"); fi if [[ -n $prefix ]]; then prefix_option=("--prefix" "${prefix}"); fi
if [[ -n $version ]]; then version_option=("--version" "${version}"); fi if [[ -n $version ]]; then version_option=("--version" "${version}"); fi
if [[ -n $cross_platform ]]; then cross_platform_option=("--cross-platform" "${cross_platform}"); fi
if [[ -n $cross_arch ]]; then cross_arch_option=("--cross-arch" "${cross_arch}"); fi
if [[ -n $cross_file ]]; then cross_file_option=("--cross-file" "${cross_file}"); fi
source scripts/build.sh \ source scripts/build.sh \
${build_dir_option[@]} \ ${build_dir_option[@]} \
${prefix_option[@]} \ ${prefix_option[@]} \
${cross_platform_option[@]} \
${cross_arch_option[@]} \
${cross_file_option[@]} \
$debug \ $debug \
$force_fallback \ $force_fallback \
$bundle \ $bundle \
@ -191,8 +159,6 @@ main() {
${dest_dir_option[@]} \ ${dest_dir_option[@]} \
${prefix_option[@]} \ ${prefix_option[@]} \
${version_option[@]} \ ${version_option[@]} \
${cross_platform_option[@]} \
${cross_arch_option[@]} \
--binary \ --binary \
--addons \ --addons \
$debug \ $debug \

View File

@ -1,666 +1,6 @@
# Changes Log # Changes Log
## [2.1.4] - 2024-04-16 ## [2.1.0] - 2022-09-25
This release addresses severe bugs not found in previous releases,
and improves the usability of the program.
### Features
* Add `.pyi` extension to `language_python`.
([#1728](https://github.com/lite-xl/lite-xl/pull/1728))
* Improve autocomplete suggestions box behavior with long text
([#1734](https://github.com/lite-xl/lite-xl/pull/1734))
* Improve `CommandView` and autocomplete scroll behavior
([#1732](https://github.com/lite-xl/lite-xl/pull/1732))
* Add `from` symbol to support ESM
([#1754](https://github.com/lite-xl/lite-xl/pull/1754))
* Add Arduino syntax highlighting support in `language_cpp`
([#1767](https://github.com/lite-xl/lite-xl/pull/1767))
* Skip patterns matching nothing in tokenizer
([#1743](https://github.com/lite-xl/lite-xl/pull/1743))
### Fixes
* Fix uninitialized variables in `src/api/process.c`
([#1719](https://github.com/lite-xl/lite-xl/pull/1719))
* Fix `language_js` regex/comment distinction
([#1731](https://github.com/lite-xl/lite-xl/pull/1731))
* Fix compilation on non-MINGW64 platforms
([#1739](https://github.com/lite-xl/lite-xl/pull/1739))
* Limit `language_js` regex avoidance to numbers, and fix starting `/*` comments
([#1744](https://github.com/lite-xl/lite-xl/pull/1744))
* Fix `buffer_size` in `g_read` for Windows
([#1722](https://github.com/lite-xl/lite-xl/pull/1722))
* Fix missing permission for creating releases
([#1770](https://github.com/lite-xl/lite-xl/pull/1770))
### Other Changes
* Rectify LICENSE dates and owners
([#1748](https://github.com/lite-xl/lite-xl/pull/1748))
* Fix some typos in `core.init`
([#1755](https://github.com/lite-xl/lite-xl/pull/1755))
## [2.1.3] - 2024-01-29
This release addresses severe bugs not found in previous releases.
### Fixes
* Fix `doc:create-cursor-{previous,next}-line` with tabs
([#1697](https://github.com/lite-xl/lite-xl/pull/1697))
* Fix heap buffer overflow and memory leaks in process and renderer API
([#1705](https://github.com/lite-xl/lite-xl/pull/1705))
* Improve Python number syntax highlighting
([#1704](https://github.com/lite-xl/lite-xl/pull/1704))
* Fix inconsistent NagView options on `doc:save`
([#1696](https://github.com/lite-xl/lite-xl/pull/1696))
* Fix crashes with autoreload when files are deleted externally and replaced with a directory.
([#1698](https://github.com/lite-xl/lite-xl/pull/1698))
* Improve JavaScript number syntax highlighting
([#1710](https://github.com/lite-xl/lite-xl/pull/1710))
### Other Changes
* Process API style changes
([#1709](https://github.com/lite-xl/lite-xl/pull/1709))
## [2.1.2] - 2023-12-29
This release addresses some issues present in the previous release,
and improves the performance and stability of Lite XL.
### New Features
* The context menu in TreeView is now navigable with a keyboard.
([#1338](https://github.com/lite-xl/lite-xl/pull/1338))
* A universal build of Lite XL is now available for macOS.
This build runs natively on both Intel and Apple Silicon macs.
([#1458](https://github.com/lite-xl/lite-xl/pull/1458))
* Most Unicode characters should be displayed properly
if your fonts support them.
([#1524](https://github.com/lite-xl/lite-xl/pull/1524))
* LogView will no longer scroll automatically if the user had scrolled.
The LogView will only scroll automatically when the user scrolls up
to the last entry.
([#1546](https://github.com/lite-xl/lite-xl/pull/1546))
* When using different fonts (especially fonts that render different scripts),
the letters will be aligned vertically.
([#1560](https://github.com/lite-xl/lite-xl/pull/1560))
* Unsaved named files are now saved in the workspace.
([#1597](https://github.com/lite-xl/lite-xl/pull/1597))
* macOS builds are now signed with a developer certificate.
This allows the user to right click the application in Finder and execute
it directly.
([#1656](https://github.com/lite-xl/lite-xl/pull/1656))
### Performance Improvements
* Allow command buffer to be expanded.
([#1297](https://github.com/lite-xl/lite-xl/pull/1297))
* Use table.move to implement `common.splice`.
([#1324](https://github.com/lite-xl/lite-xl/pull/1324))
* Create renderer only when it doesn't exist.
([#1315](https://github.com/lite-xl/lite-xl/pull/1315))
* Avoid drawing hidden text in `DocView:draw_line_text`.
([#1298](https://github.com/lite-xl/lite-xl/pull/1298))
* Don't calculate widths per-uft8-char when not needed.
([#1409](https://github.com/lite-xl/lite-xl/pull/1409))
* Allow tokenizer to pause and resume in the middle of a line.
([#1444](https://github.com/lite-xl/lite-xl/pull/1444))
* Optimize CI build times on MSYS2.
([#1435](https://github.com/lite-xl/lite-xl/pull/1435))
* Significant memory usage improvements when using huge fonts on Windows.
([#1555](https://github.com/lite-xl/lite-xl/pull/1555))
* Optimize background tasks response time.
([#1601](https://github.com/lite-xl/lite-xl/pull/1601))
### Backward Incompatible Changes
* The native plugin API is now usable on multiple source files,
without causing any duplicated symbol errors during compilation.
Plugins using the new plugin API header must define `LITE_XL_PLUGIN_ENTRYPOINT`
before importing the header, in one of their source files.
([#1335](https://github.com/lite-xl/lite-xl/pull/1335))
* The native plugin API header now follows the Lua 5.4 API.
Previously, the plugin API header followed the Lua 5.2 API.
([#1436](https://github.com/lite-xl/lite-xl/pull/1436))
* On Linux, `process.start()` will now throw an error if `execv()` fails.
([#1363](https://github.com/lite-xl/lite-xl/pull/1363))
* Lite XL will use the default `SCALE` of 1 due to unreliable display
scale detection. This may be fixed in a later version of Lite XL.
Set the `LITE_SCALE` environment variable to override this value.
### Fixes
* Fix minor typos in user module
([#1289](https://github.com/lite-xl/lite-xl/pull/1289))
* Do not allow users to create an empty font group
([#1303](https://github.com/lite-xl/lite-xl/pull/1303))
* Fix a memory leak
([#1305](https://github.com/lite-xl/lite-xl/pull/1305))
* Make dirwatch sorting compatible with what file_bisect expects
([#1300](https://github.com/lite-xl/lite-xl/pull/1300))
* Handle readlink errors
([#1292](https://github.com/lite-xl/lite-xl/pull/1292))
* Disable horizontal scrolling when linewrapping is enabled
([#1309](https://github.com/lite-xl/lite-xl/pull/1309))
* Update widgets install location
* Add missing luaL_typeerror symbol to plugin API
([#1313](https://github.com/lite-xl/lite-xl/pull/1313))
* Defer lua error until after cleanup
([#1310](https://github.com/lite-xl/lite-xl/pull/1310))
* Make empty groups in regex.gmatch return their offset
([#1325](https://github.com/lite-xl/lite-xl/pull/1325))
* Add missing header declaration
* Fix msys build now requiring ca-certificates
([#1348](https://github.com/lite-xl/lite-xl/pull/1348))
* Fix path to macOS arm64 cross file in GitHub workflows
* Fix Doc contextmenu not registering commands if scale plugin is not found
([#1338](https://github.com/lite-xl/lite-xl/pull/1338))
* Fix TreeView contextmenu commands not working if the mouse hovers DocView
([#1338](https://github.com/lite-xl/lite-xl/pull/1338))
* Fix incorrect contextmenu predicate
([#1338](https://github.com/lite-xl/lite-xl/pull/1338))
* Properly rescale NagView on scale change
([#1379](https://github.com/lite-xl/lite-xl/pull/1379))
* Scale plugin also rescales `style.expanded_scrollbar_size`
([#1380](https://github.com/lite-xl/lite-xl/pull/1380))
* Improve DocView:get_visible_line_range precision
([#1382](https://github.com/lite-xl/lite-xl/pull/1382))
* Fix up some post 5.1/JIT Symbols
([#1385](https://github.com/lite-xl/lite-xl/pull/1385))
* Fix incorrect x_offset if opened docs have different tab sizes
([#1383](https://github.com/lite-xl/lite-xl/pull/1383))
* Use correct view for scrolling to find-replace:repeat-find results
([#1400](https://github.com/lite-xl/lite-xl/pull/1400))
* Improve text width calculation precision
([#1408](https://github.com/lite-xl/lite-xl/pull/1408))
* Add asynchronous process reaping
([#1412](https://github.com/lite-xl/lite-xl/pull/1412))
* Fix cursors positions when deleting multiple selections
([#1393](https://github.com/lite-xl/lite-xl/pull/1393),
[#1463](https://github.com/lite-xl/lite-xl/pull/1463))
* Fix invalid EXEFILE and EXEDIR on Windows
([#1396](https://github.com/lite-xl/lite-xl/pull/1396))
* Fix `os.getenv()` not supporting UTF-8 output
([#1397](https://github.com/lite-xl/lite-xl/pull/1397))
* Fix differing stacktrace on stdout and file
([#1404](https://github.com/lite-xl/lite-xl/pull/1404))
* Update api_require to expose more symbols
([#1437](https://github.com/lite-xl/lite-xl/pull/1437))
* Make system.path_compare more case-aware
([#1457](https://github.com/lite-xl/lite-xl/pull/1457))
* Fix for api_require wrong macro && conditions
([#1465](https://github.com/lite-xl/lite-xl/pull/1465))
* Merge carets after doc:move-to-{previous,next}-char
([#1462](https://github.com/lite-xl/lite-xl/pull/1462))
* Process API improvements (again)
([#1370](https://github.com/lite-xl/lite-xl/pull/1370))
* Make system.path_compare more digit-aware
([#1474](https://github.com/lite-xl/lite-xl/pull/1474))
* Check for HANDLE_INVALID in Process API
([#1475](https://github.com/lite-xl/lite-xl/pull/1475))
* Fix linewrapping bug to do with wordwrapping
* Fix compiler warning for printing size_t in rencache.c
* Return error string from C searcher
* Restore horizontal scroll position after scale change
([#494](https://github.com/lite-xl/lite-xl/pull/494))
* Fix memory leak in renderer.c when freeing glyphsets
* Move lineguide below blinking cursor
([#1511](https://github.com/lite-xl/lite-xl/pull/1511))
* Close lua state when exiting on a runtime error
([#1487](https://github.com/lite-xl/lite-xl/pull/1487))
* Mark linewrapping open_files table as weak
* Don't use core.status_view if not yet initialized when logging
* Revert "core syntax: strip the path from filename on syntax.get ([#1168](https://github.com/lite-xl/lite-xl/pull/1168))"
([#1322](https://github.com/lite-xl/lite-xl/pull/1322))
* Make Doc:sanitize_position return a more appropriate col
([#1469](https://github.com/lite-xl/lite-xl/pull/1469))
* Skip checking files if no filename was provided to syntax.get
* Normalize stroke before adding keybind
([#1334](https://github.com/lite-xl/lite-xl/pull/1334))
* Make DocView aware of scrollbars sizes
([#1177](https://github.com/lite-xl/lite-xl/pull/1177))
* Normalize strokes in fixed order
([#1572](https://github.com/lite-xl/lite-xl/pull/1572))
* Defer core:open-log until everything is loaded
([#1585](https://github.com/lite-xl/lite-xl/pull/1585))
* Fix returned percent when clicking the Scrollbar track
* Fix C++14 digit separators
([#1593](https://github.com/lite-xl/lite-xl/pull/1593))
* Make linewrapping consider the expanded Scrollbar size
* Fix dimmed text when antialiasing is turned off
([#1641](https://github.com/lite-xl/lite-xl/pull/1641))
* Mark unsaved named files as dirty
([#1598](https://github.com/lite-xl/lite-xl/pull/1598))
* Make `common.serialize()` locale-independent and nan/inf compatible
([#1640](https://github.com/lite-xl/lite-xl/pull/1640))
* Ignore keypresses during IME composition
([#1573](https://github.com/lite-xl/lite-xl/pull/1573))
* Fix deadlock if error handler jumps somewhere else
([#1647](https://github.com/lite-xl/lite-xl/pull/1647))
* Avoid considering single spaces in detectindent
([#1595](https://github.com/lite-xl/lite-xl/pull/1595))
* Fix deleting indentation with multiple cursors
([#1670](https://github.com/lite-xl/lite-xl/pull/1670))
* Fix `set_target_size` passing the wrong value to plugins
([#1657](https://github.com/lite-xl/lite-xl/pull/1657))
* Limit `system.{sleep,wait_event}` to `timeouts >= 0`
([#1666](https://github.com/lite-xl/lite-xl/pull/1666))
* Fix running core.step when receiving an event while not waiting
([#1667](https://github.com/lite-xl/lite-xl/pull/1667))
* Fix dirmonitor sorting issues
([#1599](https://github.com/lite-xl/lite-xl/pull/1599))
* Scale mouse coordinates by window scale
([#1630](https://github.com/lite-xl/lite-xl/pull/1630))
* Made coroutines make more sense, and fixed a bug
([#1381](https://github.com/lite-xl/lite-xl/pull/1381))
* Fix selecting newlines with `find-replace:select-add-{next,all}`
([#1608](https://github.com/lite-xl/lite-xl/pull/1608))
* Fix editing after undo not clearing the change id
([#1574](https://github.com/lite-xl/lite-xl/pull/1574))
* Fix language_js regex constant detection
([#1581](https://github.com/lite-xl/lite-xl/pull/1581))
* Fix patterns starting with `^` in tokenizer
([#1645](https://github.com/lite-xl/lite-xl/pull/1645))
* Use x offset to define render command rect in rencache_draw_text
([#1618](https://github.com/lite-xl/lite-xl/pull/1618))
* Improve font/color change detection in `language_md`
([#1614](https://github.com/lite-xl/lite-xl/pull/1614))
* Allow long commands and envs on process_start
([#1477](https://github.com/lite-xl/lite-xl/pull/1477))
* Fix typo in `drawwhitespace.lua`
* Fix NagBar save failed message
([#1678](https://github.com/lite-xl/lite-xl/pull/1678))
* Fix typo in `drawwhitespace.lua`
* Add autocompletion to multicursor
([#1394](https://github.com/lite-xl/lite-xl/pull/1394))
### Other Changes
* Make api_require's nodes const
([#1296](https://github.com/lite-xl/lite-xl/pull/1296))
* Don't set a value twice
([#1306](https://github.com/lite-xl/lite-xl/pull/1306))
* Center title and version in emptyview
([#1311](https://github.com/lite-xl/lite-xl/pull/1311))
* Use master branch for packaging plugins for addons release
* Reorganize resources folder and add wasm target
([#1244](https://github.com/lite-xl/lite-xl/pull/1244))
* Replace uses of SDL_Window with RenWindow
([#1319](https://github.com/lite-xl/lite-xl/pull/1319))
* Update dummy dirmonitor method signature to match prototypes
* Remove static libgcc from meson
([#1290](https://github.com/lite-xl/lite-xl/pull/1290))
* Pass RenWindow by argument
([#1321](https://github.com/lite-xl/lite-xl/pull/1321))
* Get rid of annoying forward slash on windows
([#1345](https://github.com/lite-xl/lite-xl/pull/1345))
* Improve plugins config table handling
([#1356](https://github.com/lite-xl/lite-xl/pull/1356))
* Add manifest on Windows
([#1405](https://github.com/lite-xl/lite-xl/pull/1405))
* Split Command struct into different structs for each command type
([#1407](https://github.com/lite-xl/lite-xl/pull/1407))
* Move SetProcessDPIAware to manifests
([#1413](https://github.com/lite-xl/lite-xl/pull/1413))
* Use clipping functions provided by SDL
([#1426](https://github.com/lite-xl/lite-xl/pull/1426))
* Aggregate SDL_Surfaces and their scale in RenSurface
([#1429](https://github.com/lite-xl/lite-xl/pull/1429))
* Disable trimwhitespace and drawwhitespace via their configs
([#1446](https://github.com/lite-xl/lite-xl/pull/1446))
* Bump dependency versions
([#1434](https://github.com/lite-xl/lite-xl/pull/1434))
* Improvements to cross-compilation
([#1458](https://github.com/lite-xl/lite-xl/pull/1458))
* Move native plugin API header into include/
([#1440](https://github.com/lite-xl/lite-xl/pull/1440))
* Build releases with Ubuntu 18.04 container
([#1460](https://github.com/lite-xl/lite-xl/pull/1460))
* Update GitHub Actions dependencies
* Make all parameters for set_window_hit_test optional in documentation
* Attach command buffer to Renderer Window
([#1472](https://github.com/lite-xl/lite-xl/pull/1472))
* Fix comment typo in object.lua
([#1541](https://github.com/lite-xl/lite-xl/pull/1541))
* Allow setting custom glyphset size
([#1542](https://github.com/lite-xl/lite-xl/pull/1542))
* Use FreeType header names in renderer.c
([#1554](https://github.com/lite-xl/lite-xl/pull/1554))
* Add documentation for core.common
([#1510](https://github.com/lite-xl/lite-xl/pull/1510))
* Document missing parameter for system.path_compare
([#1566](https://github.com/lite-xl/lite-xl/pull/1566))
* Add documentation for core.command
([#1564](https://github.com/lite-xl/lite-xl/pull/1564))
* Update the *Installing prebuild* section in README.md
([#1548](https://github.com/lite-xl/lite-xl/pull/1548))
* Update README.md to remove previously installed files
prior to installing a new version
* Use lite-xl Build Box to build releases
([#1571](https://github.com/lite-xl/lite-xl/pull/1571))
* Use Lua wrap by default
([#1481](https://github.com/lite-xl/lite-xl/pull/1481))
* Add documentation for contextmenu
([#1567](https://github.com/lite-xl/lite-xl/pull/1567))
* Use dmgbuild to create DMGs
([#1664](https://github.com/lite-xl/lite-xl/pull/1664))
* Un-hardcode lua subproject detection and update dependencies
([#1676](https://github.com/lite-xl/lite-xl/pull/1676))
* Make license time-independent
([#1655](https://github.com/lite-xl/lite-xl/pull/1655))
## [2.1.1] - 2022-12-29
### New Features
* Add config.keep_newline_whitespace option
([#1184](https://github.com/lite-xl/lite-xl/pull/1184))
* Add regex.find_offsets, regex.find, improve regex.match
([#1232](https://github.com/lite-xl/lite-xl/pull/1232))
* Added regex.gmatch ([#1233](https://github.com/lite-xl/lite-xl/pull/1233))
* add touch events ([#1245](https://github.com/lite-xl/lite-xl/pull/1245))
### Performance Improvements
* highlighter: autostop co-routine when not needed
([#881](https://github.com/lite-xl/lite-xl/pull/881))
* core: ported regex.gsub to faster native version
([#1233](https://github.com/lite-xl/lite-xl/pull/1233))
### Backward Incompatible Changes
* For correctness, the behaviour of `regex.match` was changed to more closely
behave like `string.match`.
* `regex.find_offsets` now provides the previous functionality of `regex.match`
with a more appropriate function name.
* `regex.gsub` doesn't provides the indexes of matches and replacements anymore,
now it behaves more similar to `string.gsub` (the only known affected plugin
was `regexreplacepreview` which has already been adapted)
### UI Enhancements
* statusview: respect right padding of item tooltip
([0373d29f](https://github.com/lite-xl/lite-xl/commit/0373d29f99f286b2fbdda5a6837ef3797c988b88))
* feat: encode home in statusview file path
([#1224](https://github.com/lite-xl/lite-xl/pull/1224))
* autocomplete: wrap the autocomplete results around
([#1223](https://github.com/lite-xl/lite-xl/pull/1223))
* feat: alert user via nagview if file cannot be saved
([#1230](https://github.com/lite-xl/lite-xl/pull/1230))
* contextmenu: make divider less aggressive
([#1228](https://github.com/lite-xl/lite-xl/pull/1228))
* Improve IME location updates
([#1170](https://github.com/lite-xl/lite-xl/pull/1170))
* fix: move tab scroll buttons to remove spacing before 1st tab
([#1231](https://github.com/lite-xl/lite-xl/pull/1231))
* Allow TreeView file operation commands when focused
([#1256](https://github.com/lite-xl/lite-xl/pull/1256))
* contextmenu: adjust y positioning if less than zero
([#1268](https://github.com/lite-xl/lite-xl/pull/1268))
### Fixes
* Don't sort in Doc:get_selection_idx with an invalid index
([b029f599](https://github.com/lite-xl/lite-xl/commit/b029f5993edb7dee5ccd2ba55faac1ec22e24609))
* tokenizer: remove the limit of 3 subsyntaxes depth
([#1186](https://github.com/lite-xl/lite-xl/pull/1186))
* dirmonitor: give kevent a timeout so it doesn't lock forever
([#1180](https://github.com/lite-xl/lite-xl/pull/1180))
* dirmonitor: fix win32 implementation name length to prevent ub
([5ab8dc0](https://github.com/lite-xl/lite-xl/commit/5ab8dc027502146dd947b3d2c7544ba096a3881b))
* Make linewrapping plugin recompute breaks before scrolling
([#1190](https://github.com/lite-xl/lite-xl/pull/1190))
* Add missing get_exe_filename() implementation for FreeBSD
([#1198](https://github.com/lite-xl/lite-xl/pull/1198))
* (Windows) Load fonts with UTF-8 filenames
([#1201](https://github.com/lite-xl/lite-xl/pull/1201))
* Use subsyntax info to toggle comments
([#1202](https://github.com/lite-xl/lite-xl/pull/1202))
* Pass the currently selected item to CommandView validation
([#1203](https://github.com/lite-xl/lite-xl/pull/1203))
* Windows font loading hotfix
([#1205](https://github.com/lite-xl/lite-xl/pull/1205))
* better error messages for checkcolor
([#1211](https://github.com/lite-xl/lite-xl/pull/1211))
* Fix native plugins not reloading upon core:restart
([#1219](https://github.com/lite-xl/lite-xl/pull/1219))
* Converted from bytes to characters, as this is what windows is expecting
([5ab8dc02](https://github.com/lite-xl/lite-xl/commit/5ab8dc027502146dd947b3d2c7544ba096a3881b))
* Fix some syntax errors ([#1243](https://github.com/lite-xl/lite-xl/pull/1243))
* toolbarview: Remove tooltip when hidden
([#1251](https://github.com/lite-xl/lite-xl/pull/1251))
* detectindent: Limit subsyntax depth
([#1253](https://github.com/lite-xl/lite-xl/pull/1253))
* Use Lua string length instead of relying on strlen (#1262)
([#1262](https://github.com/lite-xl/lite-xl/pull/1262))
* dirmonitor: fix high cpu usage
([#1271](https://github.com/lite-xl/lite-xl/pull/1271)),
([#1274](https://github.com/lite-xl/lite-xl/pull/1274))
* Fix popping subsyntaxes that end consecutively
([#1246](https://github.com/lite-xl/lite-xl/pull/1246))
* Fix userdata APIs for Lua 5.4 in native plugin interface
([#1188](https://github.com/lite-xl/lite-xl/pull/1188))
* Fix horizontal scroll with touchpad on MacOS
([74349f8e](https://github.com/lite-xl/lite-xl/commit/74349f8e566ec31acd9a831a060b677d706ae4e8))
### Other Changes
* (Windows) MSVC Support ([#1199](https://github.com/lite-xl/lite-xl/pull/1199))
* meson: updated all subproject wraps
([#1214](https://github.com/lite-xl/lite-xl/pull/1214))
* set arch tuple in meson ([#1254](https://github.com/lite-xl/lite-xl/pull/1254))
* update documentation for system
([#1210](https://github.com/lite-xl/lite-xl/pull/1210))
* docs api: added dirmonitor
([7bb86e16](https://github.com/lite-xl/lite-xl/commit/7bb86e16f291256a99d2e87beb77de890cfaf0fe))
* trimwhitespace: expose functionality and extra features
([#1238](https://github.com/lite-xl/lite-xl/pull/1238))
* plugins projectsearch: expose its functionality
([#1235](https://github.com/lite-xl/lite-xl/pull/1235))
* Simplify SDL message boxes
([#1249](https://github.com/lite-xl/lite-xl/pull/1249))
* Add example settings to _overwrite_ an existing key binding
([#1270](https://github.com/lite-xl/lite-xl/pull/1270))
* Fix two typos in data/init.lua
([#1272](https://github.com/lite-xl/lite-xl/pull/1272))
* Updated meson wraps to latest (SDL v2.26, PCRE2 v10.42)
## [2.1.0] - 2022-11-01
### New Features ### New Features
* Make distinction between * Make distinction between
@ -784,12 +124,6 @@ and improves the performance and stability of Lite XL.
* Added in ability to have init.so as a require for cpath. * Added in ability to have init.so as a require for cpath.
([#1126](https://github.com/lite-xl/lite-xl/pull/1126)) ([#1126](https://github.com/lite-xl/lite-xl/pull/1126))
* Added system.raise_window() ([#1131](https://github.com/lite-xl/lite-xl/pull/1131))
* Initial horizontal scrollbar support ([#1124](https://github.com/lite-xl/lite-xl/pull/1124))
* IME support ([#991](https://github.com/lite-xl/lite-xl/pull/991))
### Performance Improvements ### Performance Improvements
* [Load space metrics only when creating font](https://github.com/lite-xl/lite-xl/pull/1032) * [Load space metrics only when creating font](https://github.com/lite-xl/lite-xl/pull/1032)
@ -993,19 +327,11 @@ and improves the performance and stability of Lite XL.
* [Fix memory leak](https://github.com/lite-xl/lite-xl/pull/1039) and wrong * [Fix memory leak](https://github.com/lite-xl/lite-xl/pull/1039) and wrong
check in font_retrieve 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 * CommandView: do not change caret size with config.line_height
([#1080](https://github.com/lite-xl/lite-xl/pull/1080)) ([#1080](https://github.com/lite-xl/lite-xl/pull/1080))
* Fixed process layer argument quoting; allows for strings with spaces
([#1132](https://github.com/lite-xl/lite-xl/pull/1132))
* Draw lite-xl icon in TitleView ([#1143](https://github.com/lite-xl/lite-xl/pull/1143))
* Add parameter validation to checkcolor and f_font_group
([#1145](https://github.com/lite-xl/lite-xl/pull/1145))
* Many, many, many more changes that are too numerous to list.
## [2.0.5] - 2022-01-29 ## [2.0.5] - 2022-01-29
Revamp the project's user module so that modifications are immediately applied. Revamp the project's user module so that modifications are immediately applied.
@ -1504,10 +830,6 @@ A new global variable `USERDIR` is exposed to point to the user's directory.
- subpixel font rendering with gamma correction - subpixel font rendering with gamma correction
[2.1.4]: https://github.com/lite-xl/lite-xl/releases/tag/v2.1.4
[2.1.3]: https://github.com/lite-xl/lite-xl/releases/tag/v2.1.3
[2.1.2]: https://github.com/lite-xl/lite-xl/releases/tag/v2.1.2
[2.1.1]: https://github.com/lite-xl/lite-xl/releases/tag/v2.1.1
[2.1.0]: https://github.com/lite-xl/lite-xl/releases/tag/v2.1.0 [2.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.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.4]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.4

View File

@ -1,55 +1,25 @@
local core = require "core" local core = require "core"
local command = {} local command = {}
---A predicate function accepts arguments from `command.perform()` and evaluates to a boolean. </br>
---If the function returns true, then the function associated with the command is executed.
---
---The predicate function can also return other values after the boolean, which will
---be passed into the function associated with the command.
---@alias core.command.predicate_function fun(...: any): boolean, ...
---A predicate is a string, an Object or a function, that is used to determine
---whether a command should be executed.
---
---If the predicate is a string, it is resolved into an `Object` via `require()`
---and checked against the active view with `Object:extends()`. </br>
---For example, `"core.docview"` will match any view that inherits from `DocView`. </br>
---A `!` can be appended to the predicate to strictly match the current view via `Object:is()`,
---instead of matching any view that inherits the predicate.
---
---If the predicate is a table, it is checked against the active view with `Object:extends()`.
---Strict matching via `Object:is()` is not available.
---
---If the predicate is a function, it must behave like a predicate function.
---@see core.command.predicate_function
---@alias core.command.predicate string|core.object|core.command.predicate_function
---A command is identified by a command name.
---The command name contains a category and the name itself, separated by a colon (':').
---
---All commands should be in lowercase and should not contain whitespaces; instead
---they should be replaced by a dash ('-').
---@alias core.command.command_name string
---The predicate and its associated function.
---@class core.command.command
---@field predicate core.command.predicate_function
---@field perform fun(...: any)
---@type { [string]: core.command.command }
command.map = {} command.map = {}
---@type core.command.predicate_function
local always_true = function() return true end local always_true = function() return true end
---This function takes in a predicate and produces a predicate function ---Used iternally by command.add, statusview, and contextmenu to generate a
---that is internally used to dispatch and execute commands. ---function with a condition to evaluate returning the boolean result of this
---evaluation.
--- ---
---This function should not be called manually. ---If a string predicate is given it is treated as a require import that should
---@see core.command.predicate ---return a valid object which is checked against the current active view,
---@param predicate core.command.predicate|nil If nil, the predicate always evaluates to true. ---eg: "core.docview" will match any view that inherits from DocView. Appending
---@return core.command.predicate_function ---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) function command.generate_predicate(predicate)
predicate = predicate or always_true predicate = predicate or always_true
local strict = false local strict = false
@ -68,20 +38,10 @@ function command.generate_predicate(predicate)
predicate = function(...) return core.active_view:is(class), core.active_view, ... end predicate = function(...) return core.active_view:is(class), core.active_view, ... end
end end
end end
---@cast predicate core.command.predicate_function
return predicate return predicate
end end
---Adds commands to the map.
---
---The function accepts a table containing a list of commands
---and their functions. </br>
---If a command already exists, it will be replaced.
---@see core.command.predicate
---@see core.command.command_name
---@param predicate core.command.predicate
---@param map { [core.command.command_name]: fun(...: any) }
function command.add(predicate, map) function command.add(predicate, map)
predicate = command.generate_predicate(predicate) predicate = command.generate_predicate(predicate)
for name, fn in pairs(map) do for name, fn in pairs(map) do
@ -97,21 +57,11 @@ local function capitalize_first(str)
return str:sub(1, 1):upper() .. str:sub(2) return str:sub(1, 1):upper() .. str:sub(2)
end end
---Prettifies the command name.
---
---This function adds a space between the colon and the command name,
---replaces dashes with spaces and capitalizes the command appropriately.
---@see core.command.command_name
---@param name core.command.command_name
---@return string
function command.prettify_name(name) function command.prettify_name(name)
---@diagnostic disable-next-line: redundant-return-value
return name:gsub(":", ": "):gsub("-", " "):gsub("%S+", capitalize_first) return name:gsub(":", ": "):gsub("-", " "):gsub("%S+", capitalize_first)
end end
---Returns all the commands that can be executed (their predicates evaluate to true).
---@return core.command.command_name[]
function command.get_all_valid() function command.get_all_valid()
local res = {} local res = {}
local memoized_predicates = {} local memoized_predicates = {}
@ -126,10 +76,6 @@ function command.get_all_valid()
return res return res
end end
---Checks whether a command can be executed (its predicate evaluates to true).
---@param name core.command.command_name
---@param ... any
---@return boolean
function command.is_valid(name, ...) function command.is_valid(name, ...)
return command.map[name] and command.map[name].predicate(...) return command.map[name] and command.map[name].predicate(...)
end end
@ -152,30 +98,16 @@ local function perform(name, ...)
end end
---Performs a command. function command.perform(...)
--- local ok, res = core.try(perform, ...)
---The arguments passed into this function are forwarded to the predicate function. </br>
---If the predicate function returns more than 1 value, the other values are passed
---to the command.
---
---Otherwise, the arguments passed into this function are passed directly
---to the command.
---@see core.command.predicate
---@see core.command.predicate_function
---@param name core.command.command_name
---@param ... any
---@return boolean # true if the command is performed successfully.
function command.perform(name, ...)
local ok, res = core.try(perform, name, ...)
return not ok or res return not ok or res
end end
---Inserts the default commands for Lite XL into the map.
function command.add_defaults() function command.add_defaults()
local reg = { local reg = {
"core", "root", "command", "doc", "findreplace", "core", "root", "command", "doc", "findreplace",
"files", "dialog", "log", "statusbar" "files", "drawwhitespace", "dialog", "log", "statusbar"
} }
for _, name in ipairs(reg) do for _, name in ipairs(reg) do
require("core.commands." .. name) require("core.commands." .. name)

View File

@ -185,7 +185,7 @@ command.add(nil, {
local dirname = common.dirname(core.project_dir) local dirname = common.dirname(core.project_dir)
local text local text
if dirname then if dirname then
text = common.basepath(common.home_encode(dirname)) text = common.home_encode(dirname) .. PATHSEP
end end
core.command_view:enter("Change Project Folder", { core.command_view:enter("Change Project Folder", {
text = text, text = text,

View File

@ -3,9 +3,12 @@ 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 translate = require "core.doc.translate" local translate = require "core.doc.translate"
local style = require "core.style"
local DocView = require "core.docview" local DocView = require "core.docview"
local tokenizer = require "core.tokenizer"
local function dv()
return core.active_view
end
local function doc() local function doc()
@ -37,24 +40,9 @@ local function save(filename)
filename = core.normalize_to_project_dir(filename) filename = core.normalize_to_project_dir(filename)
abs_filename = core.project_absolute_path(filename) abs_filename = core.project_absolute_path(filename)
end end
local ok, err = pcall(doc().save, doc(), filename, abs_filename) doc():save(filename, abs_filename)
if ok then local saved_filename = doc().filename
local saved_filename = doc().filename core.log("Saved \"%s\"", saved_filename)
core.log("Saved \"%s\"", saved_filename)
else
core.error(err)
core.nag_view:show("Saving failed", string.format("Couldn't save file \"%s\". Do you want to save to another location?", doc().filename), {
{ text = "Yes", default_yes = true },
{ text = "No", default_no = true }
}, function(item)
if item.text == "Yes" then
core.add_thread(function()
-- we need to run this in a thread because of the odd way the nagview is.
command.perform("doc:save-as")
end)
end
end)
end
end end
local function cut_or_copy(delete) local function cut_or_copy(delete)
@ -62,18 +50,17 @@ local function cut_or_copy(delete)
local text = "" local text = ""
core.cursor_clipboard = {} core.cursor_clipboard = {}
core.cursor_clipboard_whole_line = {} core.cursor_clipboard_whole_line = {}
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do for idx, line1, col1, line2, col2 in doc():get_selections() do
if line1 ~= line2 or col1 ~= col2 then if line1 ~= line2 or col1 ~= col2 then
text = doc():get_text(line1, col1, line2, col2) text = doc():get_text(line1, col1, line2, col2)
full_text = full_text == "" and text or (text .. " " .. full_text) full_text = full_text == "" and text or (full_text .. " " .. text)
core.cursor_clipboard_whole_line[idx] = false 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 else -- Cut/copy whole line
-- Remove newline from the text. It will be added as needed on paste. text = doc().lines[line1]
text = string.sub(doc().lines[line1], 1, -2) full_text = full_text == "" and text or (full_text .. text)
full_text = full_text == "" and text .. "\n" or (text .. "\n" .. full_text)
core.cursor_clipboard_whole_line[idx] = true core.cursor_clipboard_whole_line[idx] = true
if delete then if delete then
if line1 < #doc().lines then if line1 < #doc().lines then
@ -83,35 +70,22 @@ local function cut_or_copy(delete)
else else
doc():remove(line1 - 1, math.huge, line1, math.huge) doc():remove(line1 - 1, math.huge, line1, math.huge)
end end
doc():set_selections(idx, line1, col1, line2, col2)
end end
end end
core.cursor_clipboard[idx] = text core.cursor_clipboard[idx] = text
end end
if delete then doc():merge_cursors() end
core.cursor_clipboard["full"] = full_text core.cursor_clipboard["full"] = full_text
system.set_clipboard(full_text) system.set_clipboard(full_text)
end end
local function split_cursor(dv, direction) local function split_cursor(direction)
local new_cursors = {} local new_cursors = {}
local dv_translate = direction < 0 for _, line1, col1 in doc():get_selections() do
and DocView.translate.previous_line if line1 + direction >= 1 and line1 + direction <= #doc().lines then
or DocView.translate.next_line table.insert(new_cursors, { line1 + direction, col1 })
for _, line1, col1 in dv.doc:get_selections() do
if line1 + direction >= 1 and line1 + direction <= #dv.doc.lines then
table.insert(new_cursors, { dv_translate(dv.doc, line1, col1, dv) })
end end
end end
-- add selections in the order that will leave the "last" added one as doc.last_selection for i,v in ipairs(new_cursors) do doc():add_selection(v[1], v[2]) end
local start, stop = 1, #new_cursors
if direction < 0 then
start, stop = #new_cursors, 1
end
for i = start, stop, direction do
local v = new_cursors[i]
dv.doc:add_selection(v[1], v[2])
end
core.blink_reset() core.blink_reset()
end end
@ -203,30 +177,10 @@ local function block_comment(comment, line1, col1, line2, col2)
end end
end end
local function insert_paste(doc, value, whole_line, idx)
if whole_line then
local line1, col1 = doc:get_selection_idx(idx)
doc:insert(line1, 1, value:gsub("\r", "").."\n")
-- Because we're inserting at the start of the line,
-- if the cursor is in the middle of the line
-- it gets carried to the next line along with the old text.
-- If it's at the start of the line it doesn't get carried,
-- so we move it of as many characters as we're adding.
if col1 == 1 then
doc:move_to_cursor(idx, #value+1)
end
else
doc:text_input(value:gsub("\r", ""), idx)
end
end
local commands = { local commands = {
["doc:select-none"] = function(dv) ["doc:select-none"] = function(dv)
local l1, c1 = dv.doc:get_selection_idx(dv.doc.last_selection) local line, col = dv.doc:get_selection()
if not l1 then dv.doc:set_selection(line, col)
l1, c1 = dv.doc:get_selection_idx(1)
end
dv.doc:set_selection(l1, c1)
end, end,
["doc:cut"] = function() ["doc:cut"] = function()
@ -248,51 +202,27 @@ local commands = {
["doc:paste"] = function(dv) ["doc:paste"] = function(dv)
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
if core.cursor_clipboard["full"] ~= clipboard then local external_paste = core.cursor_clipboard["full"] ~= clipboard
if external_paste then
core.cursor_clipboard = {} core.cursor_clipboard = {}
core.cursor_clipboard_whole_line = {} core.cursor_clipboard_whole_line = {}
for idx in dv.doc:get_selections() do
insert_paste(dv.doc, clipboard, false, idx)
end
return
end end
-- Use internal clipboard(s) local value, whole_line
-- If there are mixed whole lines and normal lines, consider them all as normal for idx, line1, col1, line2, col2 in dv.doc:get_selections() do
local only_whole_lines = true if #core.cursor_clipboard_whole_line == (#dv.doc.selections/4) then
for _,whole_line in pairs(core.cursor_clipboard_whole_line) do value = core.cursor_clipboard[idx]
if not whole_line then whole_line = core.cursor_clipboard_whole_line[idx] == true
only_whole_lines = false else
break value = clipboard
whole_line = not external_paste and clipboard:find("\n") ~= nil
end end
end if whole_line then
if #core.cursor_clipboard_whole_line == (#dv.doc.selections/4) then dv.doc:insert(line1, 1, value:gsub("\r", ""))
-- If we have the same number of clipboards and selections, if col1 == 1 then
-- paste each clipboard into its corresponding selection dv.doc:move_to_cursor(idx, #value)
for idx in dv.doc:get_selections() do
insert_paste(dv.doc, core.cursor_clipboard[idx], only_whole_lines, idx)
end
else
-- Paste every clipboard and add a selection at the end of each one
local new_selections = {}
for idx in dv.doc:get_selections() do
for cb_idx in ipairs(core.cursor_clipboard_whole_line) do
insert_paste(dv.doc, core.cursor_clipboard[cb_idx], only_whole_lines, idx)
if not only_whole_lines then
table.insert(new_selections, {dv.doc:get_selection_idx(idx)})
end
end
if only_whole_lines then
table.insert(new_selections, {dv.doc:get_selection_idx(idx)})
end
end
local first = true
for _,selection in pairs(new_selections) do
if first then
dv.doc:set_selection(table.unpack(selection))
first = false
else
dv.doc:add_selection(table.unpack(selection))
end end
else
dv.doc:text_input(value:gsub("\r", ""), idx)
end end
end end
end, end,
@ -304,7 +234,7 @@ local commands = {
indent = indent:sub(#indent + 2 - col) indent = indent:sub(#indent + 2 - col)
end end
-- Remove current line if it contains only whitespace -- Remove current line if it contains only whitespace
if not config.keep_newline_whitespace and dv.doc.lines[line]:match("^%s+$") then if dv.doc.lines[line]:match("^%s+$") then
dv.doc:remove(line, 1, line, math.huge) dv.doc:remove(line, 1, line, math.huge)
end end
dv.doc:text_input("\n" .. indent, idx) dv.doc:text_input("\n" .. indent, idx)
@ -328,7 +258,7 @@ local commands = {
end, end,
["doc:delete"] = function(dv) ["doc:delete"] = function(dv)
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true, true) do 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 if line1 == line2 and col1 == col2 and dv.doc.lines[line1]:find("^%s*$", col1) then
dv.doc:remove(line1, col1, line1, math.huge) dv.doc:remove(line1, col1, line1, math.huge)
end end
@ -338,16 +268,15 @@ local commands = {
["doc:backspace"] = function(dv) ["doc:backspace"] = function(dv)
local _, indent_size = dv.doc:get_indent_info() local _, indent_size = dv.doc:get_indent_info()
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true, true) do 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 = dv.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) dv.doc:delete_to_cursor(idx, 0, -indent_size)
goto continue return
end end
end end
dv.doc:delete_to_cursor(idx, translate.previous_char) dv.doc:delete_to_cursor(idx, translate.previous_char)
::continue::
end end
end, end,
@ -451,28 +380,15 @@ local commands = {
end, end,
["doc:toggle-block-comments"] = function(dv) ["doc:toggle-block-comments"] = function(dv)
local comment = dv.doc.syntax.block_comment
if not comment then
if dv.doc.syntax.comment then
command.perform "doc:toggle-line-comments"
end
return
end
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
local current_syntax = dv.doc.syntax
if line1 > 1 then
-- Use the previous line state, as it will be the state
-- of the beginning of the current line
local state = dv.doc.highlighter:get_line(line1 - 1).state
local syntaxes = tokenizer.extract_subsyntaxes(dv.doc.syntax, state)
-- Go through all the syntaxes until the first with `block_comment` defined
for _, s in pairs(syntaxes) do
if s.block_comment then
current_syntax = s
break
end
end
end
local comment = current_syntax.block_comment
if not comment then
if dv.doc.syntax.comment then
command.perform "doc:toggle-line-comments"
end
return
end
-- if nothing is selected, toggle the whole line -- if nothing is selected, toggle the whole line
if line1 == line2 and col1 == col2 then if line1 == line2 and col1 == col2 then
col1 = 1 col1 = 1
@ -483,23 +399,9 @@ local commands = {
end, end,
["doc:toggle-line-comments"] = function(dv) ["doc:toggle-line-comments"] = function(dv)
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do local comment = dv.doc.syntax.comment or dv.doc.syntax.block_comment
local current_syntax = dv.doc.syntax if comment then
if line1 > 1 then for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
-- Use the previous line state, as it will be the state
-- of the beginning of the current line
local state = dv.doc.highlighter:get_line(line1 - 1).state
local syntaxes = tokenizer.extract_subsyntaxes(dv.doc.syntax, state)
-- Go through all the syntaxes until the first with comments defined
for _, s in pairs(syntaxes) do
if s.comment or s.block_comment then
current_syntax = s
break
end
end
end
local comment = current_syntax.comment or current_syntax.block_comment
if comment then
dv.doc:set_selections(idx, line_comment(comment, line1, col1, line2, col2)) dv.doc:set_selections(idx, line_comment(comment, line1, col1, line2, col2))
end end
end end
@ -624,19 +526,19 @@ local commands = {
end, end,
["doc:create-cursor-previous-line"] = function(dv) ["doc:create-cursor-previous-line"] = function(dv)
split_cursor(dv, -1) split_cursor(-1)
dv.doc:merge_cursors() dv.doc:merge_cursors()
end, end,
["doc:create-cursor-next-line"] = function(dv) ["doc:create-cursor-next-line"] = function(dv)
split_cursor(dv, 1) split_cursor(1)
dv.doc:merge_cursors() dv.doc:merge_cursors()
end end
} }
command.add(function(x, y) command.add(function(x, y)
if x == nil or y == nil or not core.active_view:extends(DocView) then return false end if x == nil or y == nil or not core.active_view:is(DocView) then return false end
local dv = core.active_view 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 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 return x >= x1 + dv:get_gutter_width() and x < x2 and y >= y1 and y < y2, dv, x, y
@ -704,7 +606,6 @@ commands["doc:move-to-previous-char"] = function(dv)
dv.doc:move_to_cursor(idx, translate.previous_char) dv.doc:move_to_cursor(idx, translate.previous_char)
end end
end end
dv.doc:merge_cursors()
end end
commands["doc:move-to-next-char"] = function(dv) commands["doc:move-to-next-char"] = function(dv)
@ -715,7 +616,6 @@ commands["doc:move-to-next-char"] = function(dv)
dv.doc:move_to_cursor(idx, translate.next_char) dv.doc:move_to_cursor(idx, translate.next_char)
end end
end end
dv.doc:merge_cursors()
end end
command.add("core.docview", commands) command.add("core.docview", commands)

View File

@ -0,0 +1,16 @@
local command = require "core.command"
local config = require "core.config"
command.add(nil, {
["draw-whitespace:toggle"] = function()
config.draw_whitespace = not config.draw_whitespace
end,
["draw-whitespace:disable"] = function()
config.draw_whitespace = false
end,
["draw-whitespace:enable"] = function()
config.draw_whitespace = true
end,
})

View File

@ -164,16 +164,13 @@ local function is_in_any_selection(line, col)
end end
local function select_add_next(all) local function select_add_next(all)
local il1, ic1 local il1, ic1 = doc():get_selection(true)
for _, l1, c1, l2, c2 in doc():get_selections(true, true) do for idx, l1, c1, l2, c2 in doc():get_selections(true, true) do
if not il1 then
il1, ic1 = l1, c1
end
local text = doc():get_text(l1, c1, l2, c2) local text = doc():get_text(l1, c1, l2, c2)
repeat repeat
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true }) l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
if l1 == il1 and c1 == ic1 then break end if l1 == il1 and c1 == ic1 then break end
if l2 and not is_in_any_selection(l2, c2) then if l2 and (all or not is_in_any_selection(l2, c2)) then
doc():add_selection(l2, c2, l1, c1) doc():add_selection(l2, c2, l1, c1)
if not all then if not all then
core.active_view:scroll_to_make_visible(l2, c2) core.active_view:scroll_to_make_visible(l2, c2)
@ -219,7 +216,7 @@ command.add("core.docview!", {
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil) return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
end end
local result, matches = regex.gsub(regex.compile(old, "m"), text, new) local result, matches = regex.gsub(regex.compile(old, "m"), text, new)
return result, matches return result, #matches
end) end)
end, end,
@ -243,38 +240,34 @@ command.add("core.docview!", {
}) })
local function valid_for_finding() local function valid_for_finding()
-- Allow using this while in the CommandView return core.active_view:is(DocView) or core.active_view:is(CommandView)
if core.active_view:is(CommandView) and last_view then
return true, last_view
end
return core.active_view:is(DocView), core.active_view
end end
command.add(valid_for_finding, { command.add(valid_for_finding, {
["find-replace:repeat-find"] = function(dv) ["find-replace:repeat-find"] = function()
if not last_fn then if not last_fn then
core.error("No find to continue from") core.error("No find to continue from")
else else
local sl1, sc1, sl2, sc2 = dv.doc:get_selection(true) local sl1, sc1, sl2, sc2 = doc():get_selection(true)
local line1, col1, line2, col2 = last_fn(dv.doc, sl2, sc2, last_text, case_sensitive, find_regex, false) local line1, col1, line2, col2 = last_fn(doc(), sl1, sc2, last_text, case_sensitive, find_regex, false)
if line1 then if line1 then
dv.doc:set_selection(line2, col2, line1, col1) doc():set_selection(line2, col2, line1, col1)
dv:scroll_to_line(line2, true) last_view:scroll_to_line(line2, true)
else else
core.error("Couldn't find %q", last_text) core.error("Couldn't find %q", last_text)
end end
end end
end, end,
["find-replace:previous-find"] = function(dv) ["find-replace:previous-find"] = function()
if not last_fn then if not last_fn then
core.error("No find to continue from") core.error("No find to continue from")
else else
local sl1, sc1, sl2, sc2 = dv.doc:get_selection(true) local sl1, sc1, sl2, sc2 = doc():get_selection(true)
local line1, col1, line2, col2 = last_fn(dv.doc, sl1, sc1, last_text, case_sensitive, find_regex, true) local line1, col1, line2, col2 = last_fn(doc(), sl1, sc1, last_text, case_sensitive, find_regex, true)
if line1 then if line1 then
dv.doc:set_selection(line2, col2, line1, col1) doc():set_selection(line2, col2, line1, col1)
dv:scroll_to_line(line2, true) last_view:scroll_to_line(line2, true)
else else
core.error("Couldn't find %q", last_text) core.error("Couldn't find %q", last_text)
end end

View File

@ -123,13 +123,5 @@ command.add(nil, {
return true return true
end end
return false return false
end,
["root:horizontal-scroll"] = function(delta)
local view = (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) or core.active_view
if view and view.scrollable then
view.scroll.to.x = view.scroll.to.x + delta * -config.mouse_wheel_scroll
return true
end
return false
end end
}) })

View File

@ -50,7 +50,6 @@ local default_state = {
function CommandView:new() function CommandView:new()
CommandView.super.new(self, SingleLineDoc()) CommandView.super.new(self, SingleLineDoc())
self.suggestion_idx = 1 self.suggestion_idx = 1
self.suggestions_offset = 1
self.suggestions = {} self.suggestions = {}
self.suggestions_height = 0 self.suggestions_height = 0
self.last_change_id = 0 self.last_change_id = 0
@ -89,10 +88,6 @@ function CommandView:get_scrollable_size()
return 0 return 0
end end
function CommandView:get_h_scrollable_size()
return 0
end
function CommandView:scroll_to_make_visible() function CommandView:scroll_to_make_visible()
-- no-op function to disable this functionality -- no-op function to disable this functionality
@ -124,24 +119,6 @@ function CommandView:move_suggestion_idx(dir)
end end
end end
local function get_suggestions_offset()
local max_visible = math.min(max_suggestions, #self.suggestions)
if dir > 0 then
if self.suggestions_offset + max_visible < self.suggestion_idx + 1 then
return self.suggestion_idx - max_visible + 1
elseif self.suggestions_offset > self.suggestion_idx then
return self.suggestion_idx
end
else
if self.suggestions_offset > self.suggestion_idx then
return self.suggestion_idx
elseif self.suggestions_offset + max_visible < self.suggestion_idx + 1 then
return self.suggestion_idx - max_visible + 1
end
end
return self.suggestions_offset
end
if self.state.show_suggestions then if self.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 = overflow_suggestion_idx(n, #self.suggestions)
@ -165,8 +142,6 @@ function CommandView:move_suggestion_idx(dir)
self.last_change_id = self.doc:get_change_id() self.last_change_id = self.doc:get_change_id()
self.state.suggest(self:get_text()) self.state.suggest(self:get_text())
end end
self.suggestions_offset = get_suggestions_offset()
end end
@ -180,7 +155,7 @@ end
function CommandView:submit() function CommandView:submit()
local suggestion = self.suggestions[self.suggestion_idx] local suggestion = self.suggestions[self.suggestion_idx]
local text = self:get_text() local text = self:get_text()
if self.state.validate(text, suggestion) then if self.state.validate(text) then
local submit = self.state.submit local submit = self.state.submit
self:exit(true) self:exit(true)
submit(text, suggestion) submit(text, suggestion)
@ -277,7 +252,6 @@ function CommandView:update_suggestions()
end end
self.suggestions = res self.suggestions = res
self.suggestion_idx = 1 self.suggestion_idx = 1
self.suggestions_offset = 1
end end
@ -321,7 +295,7 @@ function CommandView:update()
self:move_towards("suggestions_height", dest, nil, "commandview") self:move_towards("suggestions_height", dest, nil, "commandview")
-- update suggestion cursor offset -- update suggestion cursor offset
local dest = (self.suggestion_idx - self.suggestions_offset + 1) * 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, nil, "commandview")
-- update size based on whether this is the active_view -- update size based on whether this is the active_view
@ -357,7 +331,6 @@ local function draw_suggestions_box(self)
local h = math.ceil(self.suggestions_height) local h = math.ceil(self.suggestions_height)
local rx, ry, rw, rh = self.position.x, self.position.y - h - dh, self.size.x, h local rx, ry, rw, rh = self.position.x, self.position.y - h - dh, self.size.x, h
core.push_clip_rect(rx, ry, rw, rh)
-- draw suggestions background -- draw suggestions background
if #self.suggestions > 0 then if #self.suggestions > 0 then
renderer.draw_rect(rx, ry, rw, rh, style.background3) renderer.draw_rect(rx, ry, rw, rh, style.background3)
@ -367,12 +340,14 @@ local function draw_suggestions_box(self)
end end
-- draw suggestion text -- draw suggestion text
local first = math.max(self.suggestions_offset, 1) local offset = math.max(self.suggestion_idx - max_suggestions, 0)
local last = math.min(self.suggestions_offset + max_suggestions, #self.suggestions) local last = math.min(offset + max_suggestions, #self.suggestions)
core.push_clip_rect(rx, ry, rw, rh)
local first = 1 + offset
for i=first, last do for i=first, last 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 - first + 1) * lh - dh local y = self.position.y - (i - 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

View File

@ -1,41 +1,21 @@
local common = {} local common = {}
---Checks if the byte at offset is a UTF-8 continuation byte.
---
---UTF-8 encodes code points in 1 to 4 bytes.
---For a multi-byte sequence, each byte following the start byte is a continuation byte.
---@param s string
---@param offset? integer The offset of the string to start searching. Defaults to 1.
---@return boolean
function common.is_utf8_cont(s, offset) 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
end end
---Returns an iterator that yields a UTF-8 character on each iteration.
---@param text string
---@return fun(): string
function common.utf8_chars(text) function common.utf8_chars(text)
return text:gmatch("[\0-\x7f\xc2-\xf4][\x80-\xbf]*") return text:gmatch("[\0-\x7f\xc2-\xf4][\x80-\xbf]*")
end end
---Clamps the number n between lo and hi.
---@param n number
---@param lo number
---@param hi number
---@return number
function common.clamp(n, lo, hi) function common.clamp(n, lo, hi)
return math.max(math.min(n, hi), lo) return math.max(math.min(n, hi), lo)
end end
---Returns a new table containing the contents of b merged into a.
---@param a table|nil
---@param b table?
---@return table
function common.merge(a, b) function common.merge(a, b)
a = type(a) == "table" and a or {} a = type(a) == "table" and a or {}
local t = {} local t = {}
@ -51,19 +31,11 @@ function common.merge(a, b)
end end
---Returns the value of a number rounded to the nearest integer.
---@param n number
---@return number
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
---Returns the first index where a subtable in tbl has prop set.
---If none is found, nil is returned.
---@param tbl table
---@param prop any
---@return number|nil
function common.find_index(tbl, prop) function common.find_index(tbl, prop)
for i, o in ipairs(tbl) do for i, o in ipairs(tbl) do
if o[prop] then return i end if o[prop] then return i end
@ -71,16 +43,6 @@ function common.find_index(tbl, prop)
end end
---Returns a value between a and b on a linear scale, based on the
---interpolation point t.
---
---If a and b are tables, a table containing the result for all the
---elements in a and b is returned.
---@param a number
---@param b number
---@param t number
---@return number
---@overload fun(a: table, b: table, t: number): table
function common.lerp(a, b, t) function common.lerp(a, b, t)
if type(a) ~= "table" then if type(a) ~= "table" then
return a + (b - a) * t return a + (b - a) * t
@ -93,29 +55,11 @@ function common.lerp(a, b, t)
end end
---Returns the euclidean distance between two points.
---@param x1 number
---@param y1 number
---@param x2 number
---@param y2 number
---@return number
function common.distance(x1, y1, x2, y2) function common.distance(x1, y1, x2, y2)
return math.sqrt(((x2-x1) ^ 2)+((y2-y1) ^ 2)) return math.sqrt(((x2-x1) ^ 2)+((y2-y1) ^ 2))
end end
---Parses a CSS color string.
---
---Only these formats are supported:
---* `rgb(r, g, b)`
---* `rgba(r, g, b, a)`
---* `#rrggbbaa`
---* `#rrggbb`
---@param str string
---@return number r
---@return number g
---@return number b
---@return number a
function common.color(str) function common.color(str)
local r, g, b, a = str:match("^#(%x%x)(%x%x)(%x%x)(%x?%x?)$") local r, g, b, a = str:match("^#(%x%x)(%x%x)(%x%x)(%x?%x?)$")
if r then if r then
@ -136,21 +80,26 @@ function common.color(str)
end end
---Splices a numerically indexed table.
---This function mutates the original table.
---@param t any[]
---@param at number Index at which to start splicing.
---@param remove number Number of elements to remove.
---@param insert? any[] A table containing elements to insert after splicing.
function common.splice(t, at, remove, insert) function common.splice(t, at, remove, insert)
assert(remove >= 0, "bad argument #3 to 'splice' (non-negative value expected)")
insert = insert or {} insert = insert or {}
local len = #insert local offset = #insert - remove
if remove ~= len then table.move(t, at + remove, #t + remove, at + len) end local old_len = #t
table.move(insert, 1, len, at, t) if offset < 0 then
for i = at - offset, old_len - offset do
t[i + offset] = t[i]
end
elseif offset > 0 then
for i = old_len, at, -1 do
t[i + offset] = t[i]
end
end
for i, item in ipairs(insert) do
t[at + i - 1] = item
end
end end
local function compare_score(a, b) local function compare_score(a, b)
return a.score > b.score return a.score > b.score
end end
@ -171,16 +120,6 @@ local function fuzzy_match_items(items, needle, files)
end end
---Performs fuzzy matching.
---
---If the haystack is a string, a score ranging from 0 to 1 is returned. </br>
---If the haystack is a table, a table containing the haystack sorted in ascending
---order of similarity is returned.
---@param haystack string
---@param needle string
---@param files boolean If true, the matching process will be performed in reverse to better match paths.
---@return number
---@overload fun(haystack: string[], needle: string, files: boolean): string[]
function common.fuzzy_match(haystack, needle, files) function common.fuzzy_match(haystack, needle, files)
if type(haystack) == "table" then if type(haystack) == "table" then
return fuzzy_match_items(haystack, needle, files) return fuzzy_match_items(haystack, needle, files)
@ -189,14 +128,6 @@ function common.fuzzy_match(haystack, needle, files)
end end
---Performs fuzzy matching and returns recently used strings if needed.
---
---If the needle is empty, then a list of recently used strings
---are added to the result, followed by strings from the haystack.
---@param haystack string[]
---@param recents string[]
---@param needle string
---@return string[]
function common.fuzzy_match_with_recents(haystack, recents, needle) function common.fuzzy_match_with_recents(haystack, recents, needle)
if needle == "" then if needle == "" then
local recents_ext = {} local recents_ext = {}
@ -215,24 +146,11 @@ function common.fuzzy_match_with_recents(haystack, recents, needle)
end end
---Returns a list of paths that are relative to the input path.
---
---If a root directory is specified, the function returns paths
---that are relative to the root directory.
---@param text string The input path.
---@param root? string The root directory.
---@return string[]
function common.path_suggest(text, root) function common.path_suggest(text, root)
if root and root:sub(-1) ~= PATHSEP then if root and root:sub(-1) ~= PATHSEP then
root = root .. PATHSEP root = root .. PATHSEP
end end
local path, name local path, name = text:match("^(.-)([^:/\\]*)$")
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
path, name = text:match("^(.-)([^:"..PATHSEP.."]*)$")
else
path, name = text:match("^(.-)([^"..PATHSEP.."]*)$")
end
local clean_dotslash = false local clean_dotslash = false
-- ignore root if path is absolute -- ignore root if path is absolute
local is_absolute = common.is_absolute_path(text) local is_absolute = common.is_absolute_path(text)
@ -281,16 +199,8 @@ function common.path_suggest(text, root)
end end
---Returns a list of directories that are related to a path.
---@param text string The input path.
---@return string[]
function common.dir_path_suggest(text) function common.dir_path_suggest(text)
local path, name local path, name = text:match("^(.-)([^:/\\]*)$")
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
path, name = text:match("^(.-)([^:"..PATHSEP.."]*)$")
else
path, name = text:match("^(.-)([^"..PATHSEP.."]*)$")
end
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
@ -304,18 +214,8 @@ function common.dir_path_suggest(text)
end end
---Filters a list of paths to find those that are related to the input path.
---@param text string The input path.
---@param dir_list string[] A list of paths to filter.
---@return string[]
function common.dir_list_suggest(text, dir_list) function common.dir_list_suggest(text, dir_list)
local path, name local path, name = text:match("^(.-)([^:/\\]*)$")
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
path, name = text:match("^(.-)([^:"..PATHSEP.."]*)$")
else
path, name = text:match("^(.-)([^"..PATHSEP.."]*)$")
end
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
@ -326,15 +226,6 @@ function common.dir_list_suggest(text, dir_list)
end end
---Matches a string against a list of patterns.
---
---If a match was found, its start and end index is returned.
---Otherwise, false is returned.
---@param text string
---@param pattern string|string[]
---@param ... any Other options for string.find().
---@return number|boolean start_index
---@return number|nil end_index
function common.match_pattern(text, pattern, ...) function common.match_pattern(text, pattern, ...)
if type(pattern) == "string" then if type(pattern) == "string" then
return text:find(pattern, ...) return text:find(pattern, ...)
@ -347,24 +238,8 @@ function common.match_pattern(text, pattern, ...)
end end
---Draws text onto the window.
---The function returns the X and Y coordinates of the bottom-right
---corner of the text.
---@param font renderer.font
---@param color renderer.color
---@param text string
---@param align string
---| '"left"' # Align text to the left of the bounding box
---| '"right"' # Align text to the right of the bounding box
---| '"center"' # Center text in the bounding box
---@param x number
---@param y number
---@param w number
---@param h number
---@return number x_advance
---@return number y_advance
function common.draw_text(font, color, text, align, x,y,w,h) function common.draw_text(font, color, text, align, x,y,w,h)
local tw, th = font:get_width(text), font:get_height() local tw, th = font:get_width(text), font:get_height(text)
if align == "center" then if align == "center" then
x = x + (w - tw) / 2 x = x + (w - tw) / 2
elseif align == "right" then elseif align == "right" then
@ -375,16 +250,6 @@ function common.draw_text(font, color, text, align, x,y,w,h)
end end
---Prints the execution time of a function.
---
---The execution time and percentage of frame time
---for the function is printed to standard output. </br>
---The frame rate is always assumed to be 60 FPS, thus
---a value of 100% would mean that the benchmark took
---1/60 of a second to execute.
---@param name string
---@param fn fun(...: any): any
---@return any # The result returned by the function
function common.bench(name, fn, ...) function common.bench(name, fn, ...)
local start = system.get_time() local start = system.get_time()
local res = fn(...) local res = fn(...)
@ -395,15 +260,12 @@ function common.bench(name, fn, ...)
return res return res
end end
-- From gvx/Ser
local oddvals = {[tostring(1/0)] = "1/0", [tostring(-1/0)] = "-1/0", [tostring(-(0/0))] = "-(0/0)", [tostring(0/0)] = "0/0"}
local function serialize(val, pretty, indent_str, escape, sort, limit, level) local function serialize(val, pretty, indent_str, escape, sort, limit, level)
local space = pretty and " " or "" local space = pretty and " " or ""
local indent = pretty and string.rep(indent_str, level) or "" local indent = pretty and string.rep(indent_str, level) or ""
local newline = pretty and "\n" or "" local newline = pretty and "\n" or ""
local ty = type(val) if type(val) == "string" then
if ty == "string" then
local out = string.format("%q", val) local out = string.format("%q", val)
if escape then if escape then
out = string.gsub(out, "\\\n", "\\n") out = string.gsub(out, "\\\n", "\\n")
@ -415,7 +277,7 @@ local function serialize(val, pretty, indent_str, escape, sort, limit, level)
out = string.gsub(out, "\\13", "\\r") out = string.gsub(out, "\\13", "\\r")
end end
return out return out
elseif ty == "table" then elseif type(val) == "table" then
-- early exit -- early exit
if level >= limit then return tostring(val) end if level >= limit then return tostring(val) end
local next_indent = pretty and (indent .. indent_str) or "" local next_indent = pretty and (indent .. indent_str) or ""
@ -430,37 +292,17 @@ local function serialize(val, pretty, indent_str, escape, sort, limit, level)
if sort then table.sort(t) end if sort then table.sort(t) end
return "{" .. newline .. table.concat(t, "," .. newline) .. newline .. indent .. "}" return "{" .. newline .. table.concat(t, "," .. newline) .. newline .. indent .. "}"
end end
if ty == "number" then
-- tostring is locale-dependent, so we need to replace an eventual `,` with `.`
local res, _ = tostring(val):gsub(",", ".")
-- handle inf/nan
return oddvals[res] or res
end
return tostring(val) return tostring(val)
end end
-- Serialize `val` into a parsable string.
---@class common.serializeoptions -- Available options
---@field pretty boolean Enables pretty printing. -- * pretty: enable pretty printing
---@field indent_str string The indentation character to use. Defaults to `" "`. -- * indent_str: indent to use (" " by default)
---@field escape boolean Uses normal escape characters ("\n") instead of decimal escape sequences ("\10"). -- * escape: use normal escape characters instead of the ones used by string.format("%q", ...)
---@field limit number Limits the depth when serializing nested tables. Defaults to `math.huge`. -- * sort: sort the keys inside tables
---@field sort boolean Sorts the output if it is a sortable table. -- * limit: limit how deep to serialize
---@field initial_indent number The initial indentation level. Defaults to 0. -- * initial_indent: the initial indentation level
---Serializes a value into a Lua string that is loadable with load().
---
---Only these basic types are supported:
---* nil
---* boolean
---* number (except very large numbers and special constants, e.g. `math.huge`, `inf` and `nan`)
---* integer
---* string
---* table
---
---@param val any
---@param opts? common.serializeoptions
---@return string
function common.serialize(val, opts) function common.serialize(val, opts)
opts = opts or {} opts = opts or {}
local indent_str = opts.indent_str or " " local indent_str = opts.indent_str or " "
@ -472,52 +314,19 @@ function common.serialize(val, opts)
end end
---Returns the last portion of a path.
---@param path string
---@return string
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
-- 'X:\' (windows drive) -- 'X:\' (windows drive)
return path:match("[^"..PATHSEP.."]+$") or path return path:match("[^\\/]+$") or path
end end
---Returns the base path with the pathsep, if needed. -- can return nil if there is no directory part in the path
---@param path string
---@return string
function common.basepath(path)
-- Check for AmigaOS 4 and MorphOS if the last character is semicolon
-- In these systems the volume name doesn't have a / or \ after the name
-- but it is like VOLUME:
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") and (string.sub(path, -1) == ":") then
return path
end
return path .. PATHSEP
end
---Returns the directory name of a path.
---If the path doesn't have a directory, this function may return nil.
---@param path string
---@return string|nil
function common.dirname(path) function common.dirname(path)
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then return path:match("(.+)[:\\/][^\\/]+$")
local drive, relpath = path:match('^([%w%s]*:)(.+)')
if drive and relpath then
local dir = relpath:match("(.+)["..PATHSEP.."][^"..PATHSEP.."]+$")
if dir then
return drive .. dir
end
end
return path
end
return path:match("(.+)["..PATHSEP.."][^"..PATHSEP.."]+$")
end end
---Returns a path where the user's home directory is replaced by `"~"`.
---@param text string
---@return string
function common.home_encode(text) function common.home_encode(text)
if HOME and string.find(text, HOME, 1, true) == 1 then if HOME and string.find(text, HOME, 1, true) == 1 then
local dir_pos = #HOME + 1 local dir_pos = #HOME + 1
@ -531,9 +340,6 @@ function common.home_encode(text)
end end
---Returns a list of paths where the user's home directory is replaced by `"~"`.
---@param paths string[] A list of paths to encode
---@return string[]
function common.home_encode_list(paths) function common.home_encode_list(paths)
local t = {} local t = {}
for i = 1, #paths do for i = 1, #paths do
@ -543,10 +349,6 @@ function common.home_encode_list(paths)
end end
---Expands the `"~"` prefix in a path into the user's home directory.
---This function is not guaranteed to return an absolute path.
---@param text string
---@return string
function common.home_expand(text) function common.home_expand(text)
if text == nil then if text == nil then
return HOME return HOME
@ -557,30 +359,22 @@ end
local function split_on_slash(s, sep_pattern) local function split_on_slash(s, sep_pattern)
local t = {} local t = {}
if s:match("^["..PATHSEP.."]") then if s:match("^[/\\]") then
t[#t + 1] = "" t[#t + 1] = ""
end end
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then for fragment in string.gmatch(s, "([^/\\]+)") do
local drive = s:match("^([%w%s]*:)")
if drive then
t[#t + 1] = ""
s = s:gsub("^" .. drive, "")
end
end
for fragment in string.gmatch(s, "([^"..PATHSEP.."]+)") do
t[#t + 1] = fragment t[#t + 1] = fragment
end end
return t return t
end end
---Normalizes the drive letter in a Windows path to uppercase. -- The filename argument given to the function is supposed to
---This function expects an absolute path, e.g. a path from `system.absolute_path`. -- come from system.absolute_path and as such should be an
--- -- absolute path without . or .. elements.
---This function is needed because the path returned by `system.absolute_path` -- This function exists because on Windows the drive letter returned
---may contain drive letters in upper or lowercase. -- by system.absolute_path is sometimes with a lower case and sometimes
---@param filename string|nil The input path. -- with an upper case so we normalize to upper case.
---@return string|nil
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
@ -589,23 +383,10 @@ function common.normalize_volume(filename)
return drive:upper() .. rem return drive:upper() .. rem
end end
end end
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
local drive, rem = filename:match('^([%w%s]*:)(.-)' .. PATHSEP .. '?$')
if drive then
return drive .. rem
end
end
return filename return filename
end end
---Normalizes a path into the same format across platforms.
---
---On Windows, all drive letters are converted to uppercase.
---UNC paths with drive letters are converted back to ordinary Windows paths.
---All path separators (`"/"`, `"\\"`) are converted to platform-specific ones.
---@param filename string|nil
---@return string|nil
function common.normalize_path(filename) function common.normalize_path(filename)
if not filename then return end if not filename then return end
local volume local volume
@ -620,11 +401,6 @@ function common.normalize_path(filename)
volume, filename = drive, rem volume, filename = drive, rem
end end
end end
elseif (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
local drive, relpath = filename:match('^([%w%s]*:)(.+)')
if relpath then
volume, filename = drive, relpath
end
else else
local relpath = filename:match('^/(.+)') local relpath = filename:match('^/(.+)')
if relpath then if relpath then
@ -651,32 +427,18 @@ function common.normalize_path(filename)
end end
---Checks whether a path is absolute or relative.
---@param path string
---@return boolean
function common.is_absolute_path(path) function common.is_absolute_path(path)
return path:sub(1, 1) == PATHSEP or path:match("^(%a):\\") or path:match('^([%w%s]*):') return path:sub(1, 1) == PATHSEP or path:match("^(%a):\\") or path:match('^(%w*):')
end end
---Checks whether a path belongs to a parent directory.
---@param filename string The path to check.
---@param path string The parent path.
---@return boolean
function common.path_belongs_to(filename, path) function common.path_belongs_to(filename, path)
return string.find(filename, common.basepath(path), 1, true) == 1 return string.find(filename, path .. PATHSEP, 1, true) == 1
end end
---Checks whether a path is relative to another path.
---@param ref_dir string The path to check against.
---@param dir string The input path.
---@return boolean
function common.relative_path(ref_dir, dir) function common.relative_path(ref_dir, dir)
local drive_pattern = "^(%a):\\" local drive_pattern = "^(%a):\\"
if (PLATFORM == "AmigaOS 4" or PLATFORM == "MorphOS") then
drive_pattern = "^([%w%s]*:)"
end
local drive, ref_drive = dir:match(drive_pattern), ref_dir:match(drive_pattern) local drive, ref_drive = dir:match(drive_pattern), ref_dir:match(drive_pattern)
if drive and ref_drive and drive ~= ref_drive then if drive and ref_drive and drive ~= ref_drive then
-- Windows, different drives, system.absolute_path fails for C:\..\D:\ -- Windows, different drives, system.absolute_path fails for C:\..\D:\
@ -700,11 +462,6 @@ function common.relative_path(ref_dir, dir)
end end
---Creates a directory recursively if necessary.
---@param path string
---@return boolean success
---@return string|nil error
---@return string|nil path The path where an error occured.
function common.mkdirp(path) function common.mkdirp(path)
local stat = system.get_file_info(path) local stat = system.get_file_info(path)
if stat and stat.type then if stat and stat.type then
@ -714,12 +471,12 @@ function common.mkdirp(path)
while path and path ~= "" do while path and path ~= "" do
local success_mkdir = system.mkdir(path) local success_mkdir = system.mkdir(path)
if success_mkdir then break end if success_mkdir then break end
local updir, basedir = path:match("(.*)["..PATHSEP.."](.+)$") local updir, basedir = path:match("(.*)[/\\](.+)$")
table.insert(subdirs, 1, basedir or path) table.insert(subdirs, 1, basedir or path)
path = updir path = updir
end end
for _, dirname in ipairs(subdirs) do for _, dirname in ipairs(subdirs) do
path = path and common.basepath(path) .. dirname or dirname path = path and path .. PATHSEP .. dirname or dirname
if not system.mkdir(path) then if not system.mkdir(path) then
return false, "cannot create directory", path return false, "cannot create directory", path
end end
@ -727,13 +484,6 @@ function common.mkdirp(path)
return true return true
end end
---Removes a path.
---@param path string
---@param recursively boolean If true, the function will attempt to remove everything in the specified path.
---@return boolean success
---@return string|nil error
---@return string|nil path The path where the error occured.
function common.rm(path, recursively) function common.rm(path, recursively)
local stat = system.get_file_info(path) local stat = system.get_file_info(path)
if not stat or (stat.type ~= "file" and stat.type ~= "dir") then if not stat or (stat.type ~= "file" and stat.type ~= "dir") then
@ -783,3 +533,4 @@ end
return common return common

View File

@ -1,5 +1,3 @@
local common = require "core.common"
local config = {} local config = {}
config.fps = 60 config.fps = 60
@ -8,19 +6,8 @@ config.message_timeout = 5
config.mouse_wheel_scroll = 50 * SCALE config.mouse_wheel_scroll = 50 * SCALE
config.animate_drag_scroll = false config.animate_drag_scroll = false
config.scroll_past_end = true config.scroll_past_end = true
---@type "expanded" | "contracted" | false @Force the scrollbar status of the DocView
config.force_scrollbar_status = false
config.file_size_limit = 10 config.file_size_limit = 10
config.ignore_files = { config.ignore_files = { "^%." }
-- folders
"^%.svn/", "^%.git/", "^%.hg/", "^CVS/", "^%.Trash/", "^%.Trash%-.*/",
"^node_modules/", "^%.cache/", "^__pycache__/",
-- files
"%.pyc$", "%.pyo$", "%.exe$", "%.dll$", "%.obj$", "%.o$",
"%.a$", "%.lib$", "%.so$", "%.dylib$", "%.ncb$", "%.sdf$",
"%.suo$", "%.pdb$", "%.idb$", "%.class$", "%.psd$", "%.db$",
"^desktop%.ini$", "^%.DS_Store$", "^%.directory$",
}
config.symbol_pattern = "[%a_][%w_]*" config.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
@ -32,7 +19,6 @@ 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.keep_newline_whitespace = false
config.line_limit = 80 config.line_limit = 80
config.max_project_files = 2000 config.max_project_files = 2000
config.transitions = true config.transitions = true
@ -57,49 +43,17 @@ config.max_clicks = 3
-- set as true to be able to test non supported plugins -- set as true to be able to test non supported plugins
config.skip_plugins_version = false config.skip_plugins_version = false
-- holds the plugins real config table
local plugins_config = {}
-- virtual representation of plugins config table
config.plugins = {} config.plugins = {}
-- Allow you to set plugin configs even if we haven't seen the plugin before.
-- allows virtual access to the plugins config table
setmetatable(config.plugins, { setmetatable(config.plugins, {
__index = function(_, k) __index = function(t, k)
if not plugins_config[k] then if rawget(t, k) == nil then rawset(t, k, {}) end
plugins_config[k] = { enabled = true, config = {} } return rawget(t, k)
end
if plugins_config[k].enabled ~= false then
return plugins_config[k].config
end
return false
end,
__newindex = function(_, k, v)
if not plugins_config[k] then
plugins_config[k] = { enabled = nil, config = {} }
end
if v == false and package.loaded["plugins."..k] then
local core = require "core"
core.warn("[%s] is already enabled, restart the editor for the change to take effect", k)
return
elseif plugins_config[k].enabled == false and v ~= false then
plugins_config[k].enabled = true
end
if v == false then
plugins_config[k].enabled = false
elseif type(v) == "table" then
plugins_config[k].enabled = true
plugins_config[k].config = common.merge(plugins_config[k].config, v)
end
end,
__pairs = function()
return coroutine.wrap(function()
for name, status in pairs(plugins_config) do
coroutine.yield(name, status.config)
end
end)
end end
}) })
-- Disable these plugins by default.
config.plugins.trimwhitespace = false
config.plugins.drawwhitespace = false
return config return config

View File

@ -9,34 +9,13 @@ local View = require "core.view"
local border_width = 1 local border_width = 1
local divider_width = 1 local divider_width = 1
local divider_padding = 5
local DIVIDER = {} local DIVIDER = {}
---An item in the context menu.
---@class core.contextmenu.item
---@field text string
---@field info string|nil If provided, this text is displayed on the right side of the menu.
---@field command string|fun()
---A list of items with the same predicate.
---@see core.command.predicate
---@class core.contextmenu.itemset
---@field predicate core.command.predicate
---@field items core.contextmenu.item[]
---A context menu.
---@class core.contextmenu : core.object ---@class core.contextmenu : core.object
---@field itemset core.contextmenu.itemset[]
---@field show_context_menu boolean
---@field selected number
---@field position core.view.position
---@field current_scale number
local ContextMenu = Object:extend() local ContextMenu = Object:extend()
---A unique value representing the divider in a context menu.
ContextMenu.DIVIDER = DIVIDER ContextMenu.DIVIDER = DIVIDER
---Creates a new context menu.
function ContextMenu:new() function ContextMenu:new()
self.itemset = {} self.itemset = {}
self.show_context_menu = false self.show_context_menu = false
@ -50,7 +29,7 @@ local function get_item_size(item)
local lw, lh local lw, lh
if item == DIVIDER then if item == DIVIDER then
lw = 0 lw = 0
lh = divider_width + divider_padding * SCALE * 2 lh = divider_width
else else
lw = style.font:get_width(item.text) lw = style.font:get_width(item.text)
if item.info then if item.info then
@ -75,19 +54,12 @@ local function update_items_size(items, update_binding)
items.width, items.height = width, height items.width, items.height = width, height
end end
---Registers a list of items into the context menu with a predicate.
---@param predicate core.command.predicate
---@param items core.contextmenu.item[]
function ContextMenu:register(predicate, items) function ContextMenu:register(predicate, items)
predicate = command.generate_predicate(predicate) predicate = command.generate_predicate(predicate)
update_items_size(items, true) update_items_size(items, true)
table.insert(self.itemset, { predicate = predicate, items = items }) table.insert(self.itemset, { predicate = predicate, items = items })
end end
---Shows the context menu.
---@param x number
---@param y number
---@return boolean # If true, the context menu is shown.
function ContextMenu:show(x, y) function ContextMenu:show(x, y)
self.items = nil self.items = nil
local items_list = { width = 0, height = 0 } local items_list = { width = 0, height = 0 }
@ -110,8 +82,12 @@ function ContextMenu:show(x, y)
local w, h = self.items.width, self.items.height local w, h = self.items.width, self.items.height
-- by default the box is opened on the right and below -- by default the box is opened on the right and below
x = common.clamp(x, 0, core.root_view.size.x - w - style.padding.x) if x + w >= core.root_view.size.x then
y = common.clamp(y, 0, core.root_view.size.y - h) x = x - w
end
if y + h >= core.root_view.size.y then
y = y - h
end
self.position.x, self.position.y = x, y self.position.x, self.position.y = x, y
self.show_context_menu = true self.show_context_menu = true
@ -121,7 +97,6 @@ function ContextMenu:show(x, y)
return false return false
end end
---Hides the context menu.
function ContextMenu:hide() function ContextMenu:hide()
self.show_context_menu = false self.show_context_menu = false
self.items = nil self.items = nil
@ -130,8 +105,6 @@ function ContextMenu:hide()
core.request_cursor(core.active_view.cursor) core.request_cursor(core.active_view.cursor)
end end
---Returns an iterator that iterates over each context menu item and their dimensions.
---@return fun(): number, core.contextmenu.item, number, number, number, number
function ContextMenu:each_item() function ContextMenu:each_item()
local x, y, w = self.position.x, self.position.y, self.items.width local x, y, w = self.position.x, self.position.y, self.items.width
local oy = y local oy = y
@ -145,12 +118,8 @@ function ContextMenu:each_item()
end) end)
end end
---Event handler for mouse movements.
---@param px any
---@param py any
---@return boolean # true if the event is caught.
function ContextMenu:on_mouse_moved(px, py) function ContextMenu:on_mouse_moved(px, py)
if not self.show_context_menu then return false end if not self.show_context_menu then return end
self.selected = -1 self.selected = -1
for i, item, x, y, w, h in self:each_item() do for i, item, x, y, w, h in self:each_item() do
@ -162,8 +131,6 @@ function ContextMenu:on_mouse_moved(px, py)
return true return true
end end
---Event handler for when the selection is confirmed.
---@param item core.contextmenu.item
function ContextMenu:on_selected(item) function ContextMenu:on_selected(item)
if type(item.command) == "string" then if type(item.command) == "string" then
command.perform(item.command) command.perform(item.command)
@ -176,7 +143,6 @@ local function change_value(value, change)
return value + change return value + change
end end
---Selects the the previous item.
function ContextMenu:focus_previous() function ContextMenu:focus_previous()
self.selected = (self.selected == -1 or self.selected == 1) and #self.items or change_value(self.selected, -1) 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 if self:get_item_selected() == DIVIDER then
@ -184,7 +150,6 @@ function ContextMenu:focus_previous()
end end
end end
---Selects the next item.
function ContextMenu:focus_next() function ContextMenu:focus_next()
self.selected = (self.selected == -1 or self.selected == #self.items) and 1 or change_value(self.selected, 1) 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 if self:get_item_selected() == DIVIDER then
@ -192,13 +157,10 @@ function ContextMenu:focus_next()
end end
end end
---Gets the currently selected item.
---@return core.contextmenu.item|nil
function ContextMenu:get_item_selected() function ContextMenu:get_item_selected()
return (self.items or {})[self.selected] return (self.items or {})[self.selected]
end end
---Hides the context menu and performs the command if an item is selected.
function ContextMenu:call_selected_item() function ContextMenu:call_selected_item()
local selected = self:get_item_selected() local selected = self:get_item_selected()
self:hide() self:hide()
@ -207,12 +169,6 @@ function ContextMenu:call_selected_item()
end end
end end
---Event handler for mouse press.
---@param button core.view.mousebutton
---@param px number
---@param py number
---@param clicks number
---@return boolean # true if the event is caught.
function ContextMenu:on_mouse_pressed(button, px, py, clicks) function ContextMenu:on_mouse_pressed(button, px, py, clicks)
local caught = false local caught = false
@ -233,20 +189,14 @@ function ContextMenu:on_mouse_pressed(button, px, py, clicks)
return caught return caught
end end
---@type fun(self: table, k: string, dest: number, rate?: number, name?: string)
ContextMenu.move_towards = View.move_towards ContextMenu.move_towards = View.move_towards
---Event handler for content update.
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, nil, "contextmenu")
end end
end end
---Draws the context menu.
---
---This wraps `ContextMenu:draw_context_menu()`.
---@see core.contextmenu.draw_context_menu
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 if self.current_scale ~= SCALE then
@ -259,7 +209,6 @@ function ContextMenu:draw()
core.root_view:defer_draw(self.draw_context_menu, self) core.root_view:defer_draw(self.draw_context_menu, self)
end end
---Draws the context menu.
function ContextMenu:draw_context_menu() function ContextMenu:draw_context_menu()
if not self.items then return end if not self.items then return end
local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height
@ -275,7 +224,7 @@ function ContextMenu:draw_context_menu()
for i, item, x, y, w, h in self:each_item() do for i, item, x, y, w, h in self:each_item() do
if item == DIVIDER then if item == DIVIDER then
renderer.draw_rect(x, y + divider_padding * SCALE, w, divider_width, style.divider) renderer.draw_rect(x, y, w, h, style.caret)
else else
if i == self.selected then if i == self.selected then
renderer.draw_rect(x, y, w, h, style.selection) renderer.draw_rect(x, y, w, h, style.selection)

View File

@ -2,7 +2,7 @@ local common = require "core.common"
local config = require "core.config" local config = require "core.config"
local dirwatch = {} local dirwatch = {}
function dirwatch:__index(idx) function dirwatch:__index(idx)
local value = rawget(self, idx) local value = rawget(self, idx)
if value ~= nil then return value end if value ~= nil then return value end
return dirwatch[idx] return dirwatch[idx]
@ -14,8 +14,8 @@ function dirwatch.new()
watched = {}, watched = {},
reverse_watched = {}, reverse_watched = {},
monitor = dirmonitor.new(), monitor = dirmonitor.new(),
single_watch_top = nil, windows_watch_top = nil,
single_watch_count = 0 windows_watch_count = 0
} }
setmetatable(t, dirwatch) setmetatable(t, dirwatch)
return t return t
@ -38,23 +38,23 @@ function dirwatch:watch(directory, bool)
local info = system.get_file_info(directory) local info = system.get_file_info(directory)
if not info then return end if not info then return end
if not self.watched[directory] and not self.scanned[directory] then if not self.watched[directory] and not self.scanned[directory] then
if self.monitor:mode() == "single" then if PLATFORM == "Windows" then
if info.type ~= "dir" then return self:scan(directory) end if info.type ~= "dir" then return self:scan(directory) end
if not self.single_watch_top or directory:find(self.single_watch_top, 1, true) ~= 1 then if 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. -- Get the highest level of directory that is common to this directory, and the original.
local target = directory local target = directory
while self.single_watch_top and self.single_watch_top:find(target, 1, true) ~= 1 do while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do
target = common.dirname(target) target = common.dirname(target)
end end
if target ~= self.single_watch_top then if target ~= self.windows_watch_top then
local value = self.monitor:watch(target) local value = self.monitor:watch(target)
if value and value < 0 then if value and value < 0 then
return self:scan(directory) return self:scan(directory)
end end
self.single_watch_top = target self.windows_watch_top = target
end end
end end
self.single_watch_count = self.single_watch_count + 1 self.windows_watch_count = self.windows_watch_count + 1
self.watched[directory] = true self.watched[directory] = true
else else
local value = self.monitor:watch(directory) local value = self.monitor:watch(directory)
@ -72,13 +72,13 @@ end
-- this should be an absolute path -- this should be an absolute path
function dirwatch:unwatch(directory) function dirwatch:unwatch(directory)
if self.watched[directory] then if self.watched[directory] then
if self.monitor:mode() == "multiple" then if PLATFORM ~= "Windows" then
self.monitor:unwatch(self.watched[directory]) self.monitor:unwatch(self.watched[directory])
self.reverse_watched[directory] = nil self.reverse_watched[directory] = nil
else else
self.single_watch_count = self.single_watch_count - 1 self.windows_watch_count = self.windows_watch_count - 1
if self.single_watch_count == 0 then if self.windows_watch_count == 0 then
self.single_watch_top = nil self.windows_watch_top = nil
self.monitor:unwatch(directory) self.monitor:unwatch(directory)
end end
end end
@ -91,22 +91,14 @@ end
-- designed to be run inside a coroutine. -- designed to be run inside a coroutine.
function dirwatch:check(change_callback, scan_time, wait_time) function dirwatch:check(change_callback, scan_time, wait_time)
local had_change = false local had_change = false
local last_error
self.monitor:check(function(id) self.monitor:check(function(id)
had_change = true had_change = true
if self.monitor:mode() == "single" then if PLATFORM == "Windows" then
local path = common.dirname(id) change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id))
if not string.match(id, "^/") and not string.match(id, "^%a:[/\\]") then
path = common.dirname(self.single_watch_top .. PATHSEP .. id)
end
change_callback(path)
elseif self.reverse_watched[id] then elseif self.reverse_watched[id] then
change_callback(self.reverse_watched[id]) change_callback(self.reverse_watched[id])
end end
end, function(err)
last_error = err
end) end)
if last_error ~= nil then error(last_error) end
local start_time = system.get_time() local start_time = system.get_time()
for directory, old_modified in pairs(self.scanned) do for directory, old_modified in pairs(self.scanned) do
if old_modified then if old_modified then
@ -170,15 +162,14 @@ end
local function compare_file(a, b) local function compare_file(a, b)
return system.path_compare(a.filename, a.type, b.filename, b.type) return a.filename < b.filename
end end
-- compute a file's info entry completed with "filename" to be used -- 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. -- in project scan or falsy if it shouldn't appear in the list.
local function get_project_file_info(root, file, ignore_compiled) local function get_project_file_info(root, file, ignore_compiled)
local info = system.get_file_info(common.basepath(root) .. file) 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 -- 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. -- a directory, for example for /dev/* entries on linux.
if info and info.type then if info and info.type then
@ -191,48 +182,49 @@ end
-- "root" will by an absolute path without trailing '/' -- "root" will by an absolute path without trailing '/'
-- "path" will be a path starting without '/' and without trailing '/' -- "path" will be a path starting without '/' and without trailing '/'
-- or the empty string. -- or the empty string.
-- It identifies a sub-path within "root". -- It will identifies a sub-path within "root.
-- The current path location will therefore always be: root .. path. -- The current path location will therefore always be: root .. path.
-- When recursing, "root" will always be the same, only "path" will change. -- 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 -- 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 '/'. -- complete file path relative to "root" *without* the trailing '/', and without the starting '/'.
function dirwatch.get_directory_files(dir, root, path, entries_count, recurse_pred) function dirwatch.get_directory_files(dir, root, path, t, entries_count, recurse_pred)
local t = {}
local t0 = system.get_time() local t0 = system.get_time()
local t_elapsed = system.get_time() - t0
local dirs, files = {}, {}
local ignore_compiled = compile_ignore_files() local ignore_compiled = compile_ignore_files()
local all = system.list_dir(common.basepath(root) .. path)
local all = system.list_dir(root .. PATHSEP .. path)
if not all then return nil end if not all then return nil end
local entries = { }
for _, file in ipairs(all) do for _, file in ipairs(all or {}) do
local info = get_project_file_info(root, (path ~= "" and (path .. PATHSEP) or "") .. file, ignore_compiled) local info = get_project_file_info(root, (path ~= "" and (path .. PATHSEP) or "") .. file, ignore_compiled)
if info then if info then
table.insert(entries, info) table.insert(info.type == "dir" and dirs or files, info)
entries_count = entries_count + 1
end end
end end
table.sort(entries, compare_file)
local recurse_complete = true local recurse_complete = true
for _, info in ipairs(entries) do table.sort(dirs, compare_file)
table.insert(t, info) for _, f in ipairs(dirs) do
entries_count = entries_count + 1 table.insert(t, f)
if info.type == "dir" then if recurse_pred(dir, f.filename, entries_count, t_elapsed) then
if recurse_pred(dir, info.filename, entries_count, system.get_time() - t0) then local _, complete, n = dirwatch.get_directory_files(dir, root, f.filename, t, entries_count, recurse_pred)
local t_rec, complete, n = dirwatch.get_directory_files(dir, root, info.filename, entries_count, recurse_pred) recurse_complete = recurse_complete and complete
recurse_complete = recurse_complete and complete if n ~= nil then
if n ~= nil then entries_count = n
entries_count = n
for _, info_rec in ipairs(t_rec) do
table.insert(t, info_rec)
end
end
else
recurse_complete = false
end end
else
recurse_complete = false
end end
end end
table.sort(files, compare_file)
for _, f in ipairs(files) do
table.insert(t, f)
end
return t, recurse_complete, entries_count return t, recurse_complete, entries_count
end end

View File

@ -10,58 +10,42 @@ local Highlighter = Object:extend()
function Highlighter:new(doc) function Highlighter:new(doc)
self.doc = doc self.doc = doc
self.running = false
self:reset() self:reset()
end
-- init incremental syntax highlighting -- init incremental syntax highlighting
function Highlighter:start()
if self.running then return end
self.running = true
core.add_thread(function() core.add_thread(function()
while self.first_invalid_line <= self.max_wanted_line do while true do
local max = math.min(self.first_invalid_line + 40, self.max_wanted_line) if self.first_invalid_line > self.max_wanted_line then
local retokenized_from self.max_wanted_line = 0
for i = self.first_invalid_line, max do coroutine.yield(1 / config.fps)
local state = (i > 1) and self.lines[i - 1].state
local line = self.lines[i] else
if line and line.resume and (line.init_state ~= state or line.text ~= self.doc.lines[i]) then local max = math.min(self.first_invalid_line + 40, self.max_wanted_line)
-- Reset the progress if no longer valid
line.resume = nil local retokenized_from
end for i = self.first_invalid_line, max do
if not (line and line.init_state == state and line.text == self.doc.lines[i] and not line.resume) then local state = (i > 1) and self.lines[i - 1].state
retokenized_from = retokenized_from or i local line = self.lines[i]
self.lines[i] = self:tokenize_line(i, state, line and line.resume) if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
if self.lines[i].resume then retokenized_from = retokenized_from or i
self.first_invalid_line = i self.lines[i] = self:tokenize_line(i, state)
goto yield elseif retokenized_from then
self:update_notify(retokenized_from, i - retokenized_from - 1)
retokenized_from = nil
end end
elseif retokenized_from then
self:update_notify(retokenized_from, i - retokenized_from - 1)
retokenized_from = nil
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
::yield:: core.redraw = true
if retokenized_from then coroutine.yield()
self:update_notify(retokenized_from, max - retokenized_from)
end end
core.redraw = true
coroutine.yield()
end end
self.max_wanted_line = 0
self.running = false
end, self) end, self)
end end
local function set_max_wanted_lines(self, amount)
self.max_wanted_line = amount
if self.first_invalid_line <= self.max_wanted_line then
self:start()
end
end
function Highlighter:reset() function Highlighter:reset()
self.lines = {} self.lines = {}
@ -78,7 +62,7 @@ end
function Highlighter:invalidate(idx) function Highlighter:invalidate(idx)
self.first_invalid_line = math.min(self.first_invalid_line, idx) self.first_invalid_line = math.min(self.first_invalid_line, idx)
set_max_wanted_lines(self, math.min(self.max_wanted_line, #self.doc.lines)) self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines)
end end
function Highlighter:insert_notify(line, n) function Highlighter:insert_notify(line, n)
@ -100,11 +84,11 @@ function Highlighter:update_notify(line, n)
end end
function Highlighter:tokenize_line(idx, state, resume) function Highlighter:tokenize_line(idx, state)
local res = {} local res = {}
res.init_state = state res.init_state = state
res.text = self.doc.lines[idx] res.text = self.doc.lines[idx]
res.tokens, res.state, res.resume = tokenizer.tokenize(self.doc.syntax, res.text, state, resume) res.tokens, res.state = tokenizer.tokenize(self.doc.syntax, res.text, state)
return res return res
end end
@ -117,7 +101,7 @@ function Highlighter:get_line(idx)
self.lines[idx] = line self.lines[idx] = line
self:update_notify(idx, 0) self:update_notify(idx, 0)
end end
set_max_wanted_lines(self, math.max(self.max_wanted_line, idx)) self.max_wanted_line = math.max(self.max_wanted_line, idx)
return line return line
end end

View File

@ -33,7 +33,6 @@ 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.last_selection = 1
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
@ -44,12 +43,7 @@ end
function Doc:reset_syntax() function Doc:reset_syntax()
local header = self:get_text(1, 1, self:position_offset(1, 1, 128)) local header = self:get_text(1, 1, self:position_offset(1, 1, 128))
local path = self.abs_filename local syn = syntax.get(self.filename or "", header)
if not path and self.filename then
path = common.basepath(core.project_dir) .. self.filename
end
if path then path = common.normalize_path(path) end
local syn = syntax.get(path, header)
if self.syntax ~= syn then if self.syntax ~= syn then
self.syntax = syn self.syntax = syn
self.highlighter:soft_reset() self.highlighter:soft_reset()
@ -123,7 +117,6 @@ end
function Doc:is_dirty() function Doc:is_dirty()
if self.new_file then if self.new_file then
if self.filename then return true end
return #self.lines > 1 or #self.lines[1] > 1 return #self.lines > 1 or #self.lines[1] > 1
else else
return self.clean_change_id ~= self:get_change_id() return self.clean_change_id ~= self:get_change_id()
@ -148,39 +141,15 @@ function Doc:get_change_id()
return self.undo_stack.idx return self.undo_stack.idx
end end
local function sort_positions(line1, col1, line2, col2)
if line1 > line2 or line1 == line2 and col1 > col2 then
return line2, col2, line1, col1, true
end
return line1, col1, line2, col2, false
end
-- Cursor section. Cursor indices are *only* valid during a get_selections() call. -- Cursor section. Cursor indices are *only* valid during a get_selections() call.
-- Cursors will always be iterated in order from top to bottom. Through normal operation -- Cursors will always be iterated in order from top to bottom. Through normal operation
-- curors can never swap positions; only merge or split, or change their position in cursor -- 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 line1, col1, line2, col2, swap = self:get_selection_idx(self.last_selection, sort) local idx, line1, col1, line2, col2, swap = self:get_selections(sort)({ self.selections, sort }, 0)
if not line1 then
line1, col1, line2, col2, swap = self:get_selection_idx(1, sort)
end
return line1, col1, line2, col2, swap return line1, col1, line2, col2, swap
end end
---Get the selection specified by `idx`
---@param idx integer @the index of the selection to retrieve
---@param sort? boolean @whether to sort the selection returned
---@return integer,integer,integer,integer,boolean? @line1, col1, line2, col2, was the selection sorted
function Doc:get_selection_idx(idx, sort)
local line1, col1, line2, col2 = self.selections[idx*4-3], self.selections[idx*4-2], self.selections[idx*4-1], self.selections[idx*4]
if line1 and sort then
return sort_positions(line1, col1, line2, col2)
else
return line1, col1, line2, col2
end
end
function Doc:get_selection_text(limit) function Doc:get_selection_text(limit)
limit = limit or math.huge limit = limit or math.huge
local result = {} local result = {}
@ -212,6 +181,13 @@ function Doc:sanitize_selection()
end end
end end
local function sort_positions(line1, col1, line2, col2)
if line1 > line2 or line1 == line2 and col1 > col2 then
return line2, col2, line1, col1, true
end
return line1, col1, line2, col2, false
end
function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm) function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm)
assert(not line2 == not col2, "expected 3 or 5 arguments") assert(not line2 == not col2, "expected 3 or 5 arguments")
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
@ -230,14 +206,10 @@ function Doc:add_selection(line1, col1, line2, col2, swap)
end end
end end
self:set_selections(target, line1, col1, line2, col2, swap, 0) self:set_selections(target, line1, col1, line2, col2, swap, 0)
self.last_selection = target
end end
function Doc:remove_selection(idx) function Doc:remove_selection(idx)
if self.last_selection >= idx then
self.last_selection = self.last_selection - 1
end
common.splice(self.selections, (idx - 1) * 4 + 1, 4) common.splice(self.selections, (idx - 1) * 4 + 1, 4)
end end
@ -245,7 +217,6 @@ end
function Doc:set_selection(line1, col1, line2, col2, swap) function Doc:set_selection(line1, col1, line2, col2, swap)
self.selections = {} self.selections = {}
self:set_selections(1, line1, col1, line2, col2, swap) self:set_selections(1, line1, col1, line2, col2, swap)
self.last_selection = 1
end end
function Doc:merge_cursors(idx) function Doc:merge_cursors(idx)
@ -254,9 +225,6 @@ 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)
if self.last_selection >= (i+3)/4 then
self.last_selection = self.last_selection - 1
end
break break
end end
end end
@ -282,13 +250,9 @@ end
-- End of cursor seciton. -- End of cursor seciton.
function Doc:sanitize_position(line, col) function Doc:sanitize_position(line, col)
local nlines = #self.lines line = common.clamp(line, 1, #self.lines)
if line > nlines then col = common.clamp(col, 1, #self.lines[line])
return nlines, #self.lines[nlines] return line, col
elseif line < 1 then
return 1, 1
end
return line, common.clamp(col, 1, #self.lines[line])
end end
@ -437,61 +401,25 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
local before = self.lines[line1]:sub(1, col1 - 1) local before = self.lines[line1]:sub(1, col1 - 1)
local after = self.lines[line2]:sub(col2) local after = self.lines[line2]:sub(col2)
local line_removal = line2 - line1
local col_removal = col2 - col1
-- splice line into line array -- splice line into line array
common.splice(self.lines, line1, line_removal + 1, { before .. after }) common.splice(self.lines, line1, line2 - line1 + 1, { before .. after })
local merge = false -- move all cursors back if they share a line with the removed text
-- keep selections in correct positions: each pair (line, col)
-- * remains unchanged if before the deleted text
-- * is set to (line1, col1) if in the deleted text
-- * is set to (line1, col - col_removal) if on line2 but out of the deleted text
-- * is set to (line - line_removal, col) if after line2
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 cline2 < line1 then break end if cline1 < line2 then break end
local l1, c1, l2, c2 = cline1, ccol1, cline2, ccol2 local line_removal = line2 - line1
local column_removal = line2 == cline2 and col2 < ccol1 and (line2 == line1 and col2 - col1 or col2) or 0
if cline1 > line1 or (cline1 == line1 and ccol1 > col1) then self:set_selections(idx, cline1 - line_removal, ccol1 - column_removal, cline2 - line_removal, ccol2 - column_removal)
if cline1 > line2 then
l1 = l1 - line_removal
else
l1 = line1
c1 = (cline1 == line2 and ccol1 > col2) and c1 - col_removal or col1
end
end
if cline2 > line1 or (cline2 == line1 and ccol2 > col1) then
if cline2 > line2 then
l2 = l2 - line_removal
else
l2 = line1
c2 = (cline2 == line2 and ccol2 > col2) and c2 - col_removal or col1
end
end
if l1 == line1 and c1 == col1 then merge = true end
self:set_selections(idx, l1, c1, l2, c2)
end
if merge then
self:merge_cursors()
end end
-- update highlighter and assure selection is in bounds -- update highlighter and assure selection is in bounds
self.highlighter:remove_notify(line1, line_removal) self.highlighter:remove_notify(line1, line2 - line1)
self:sanitize_selection() self:sanitize_selection()
end end
function Doc:insert(line, col, text) function Doc:insert(line, col, text)
self.redo_stack = { idx = 1 } self.redo_stack = { idx = 1 }
-- Reset the clean id when we're pushing something new before it
if self:get_change_id() < self.clean_change_id then
self.clean_change_id = -1
end
line, col = self:sanitize_position(line, col) line, col = self:sanitize_position(line, col)
self:raw_insert(line, col, text, self.undo_stack, system.get_time()) self:raw_insert(line, col, text, self.undo_stack, system.get_time())
self:on_text_change("insert") self:on_text_change("insert")
@ -528,18 +456,6 @@ function Doc:text_input(text, idx)
end end
end end
function Doc:ime_text_editing(text, start, length, idx)
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do
if line1 ~= line2 or col1 ~= col2 then
self:delete_to_cursor(sidx)
end
self:insert(line1, col1, text)
self:set_selections(sidx, line1, col1 + #text, line1, col1)
end
end
function Doc:replace_cursor(idx, line1, col1, line2, col2, fn) 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, res = fn(old_text)

View File

@ -66,18 +66,7 @@ function search.find(doc, line, col, text, opt)
s, e = search_func(line_text, pattern, col, plain) s, e = search_func(line_text, pattern, col, plain)
end end
if s then if s then
local line2 = line return line, s, line, e + 1
-- If we've matched the newline too,
-- return until the initial character of the next line.
if e >= #doc.lines[line] then
line2 = line + 1
e = 0
end
-- Avoid returning matches that go beyond the last line.
-- This is needed to avoid selecting the "last" newline.
if line2 <= #doc.lines then
return line, s, line2, e + 1
end
end end
col = opt.reverse and -1 or 1 col = opt.reverse and -1 or 1
end end

View File

@ -4,7 +4,6 @@ local config = require "core.config"
local style = require "core.style" local style = require "core.style"
local keymap = require "core.keymap" local keymap = require "core.keymap"
local translate = require "core.doc.translate" local translate = require "core.doc.translate"
local ime = require "core.ime"
local View = require "core.view" local View = require "core.view"
---@class core.docview : core.view ---@class core.docview : core.view
@ -61,11 +60,6 @@ function DocView:new(doc)
self.doc = assert(doc) self.doc = assert(doc)
self.font = "code_font" self.font = "code_font"
self.last_x_offset = {} self.last_x_offset = {}
self.ime_selection = { from = 0, size = 0 }
self.ime_status = false
self.hovering_gutter = false
self.v_scrollbar:set_forced_status(config.force_scrollbar_status)
self.h_scrollbar:set_forced_status(config.force_scrollbar_status)
end end
@ -112,16 +106,11 @@ end
function DocView:get_scrollable_size() function DocView:get_scrollable_size()
if not config.scroll_past_end then if not config.scroll_past_end then
local _, _, _, h_scroll = self.h_scrollbar:get_track_rect() return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2
return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2 + h_scroll
end end
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
end end
function DocView:get_h_scrollable_size()
return math.huge
end
function DocView:get_font() function DocView:get_font()
return style[self.font] return style[self.font]
@ -161,36 +150,24 @@ end
function DocView:get_visible_line_range() function DocView:get_visible_line_range()
local x, y, x2, y2 = self:get_content_bounds() local x, y, x2, y2 = self:get_content_bounds()
local lh = self:get_line_height() local lh = self:get_line_height()
local minline = math.max(1, math.floor((y - style.padding.y) / lh) + 1) local minline = math.max(1, math.floor(y / lh))
local maxline = math.min(#self.doc.lines, math.floor((y2 - style.padding.y) / lh) + 1) local maxline = math.min(#self.doc.lines, math.floor(y2 / lh) + 1)
return minline, maxline return minline, maxline
end end
function DocView:get_col_x_offset(line, col) function DocView:get_col_x_offset(line, col)
local default_font = self:get_font() local default_font = self:get_font()
local _, indent_size = self.doc:get_indent_info()
default_font:set_tab_size(indent_size)
local column = 1 local column = 1
local xoffset = 0 local xoffset = 0
for _, type, text in self.doc.highlighter:each_token(line) do for _, type, text in self.doc.highlighter:each_token(line) do
local font = style.syntax_fonts[type] or default_font local font = style.syntax_fonts[type] or default_font
if font ~= default_font then font:set_tab_size(indent_size) end for char in common.utf8_chars(text) do
local length = #text if column == col then
if column + length <= col then
xoffset = xoffset + font:get_width(text)
column = column + length
if column >= col then
return xoffset return xoffset
end end
else xoffset = xoffset + font:get_width(char)
for char in common.utf8_chars(text) do column = column + #char
if column >= col then
return xoffset
end
xoffset = xoffset + font:get_width(char)
column = column + #char
end
end end
end end
@ -203,27 +180,16 @@ function DocView:get_x_offset_col(line, x)
local xoffset, last_i, i = 0, 1, 1 local xoffset, last_i, i = 0, 1, 1
local default_font = self:get_font() local default_font = self:get_font()
local _, indent_size = self.doc:get_indent_info()
default_font:set_tab_size(indent_size)
for _, type, text in self.doc.highlighter:each_token(line) do for _, type, text in self.doc.highlighter:each_token(line) do
local font = style.syntax_fonts[type] or default_font local font = style.syntax_fonts[type] or default_font
if font ~= default_font then font:set_tab_size(indent_size) end for char in common.utf8_chars(text) do
local width = font:get_width(text) local w = font:get_width(char)
-- Don't take the shortcut if the width matches x, if xoffset >= x then
-- because we need last_i which should be calculated using utf-8. return (xoffset - x > w / 2) and last_i or i
if xoffset + width < x then
xoffset = xoffset + width
i = i + #text
else
for char in common.utf8_chars(text) do
local w = font:get_width(char)
if xoffset >= x then
return (xoffset - x > w / 2) and last_i or i
end
xoffset = xoffset + w
last_i = i
i = i + #char
end end
xoffset = xoffset + w
last_i = i
i = i + #char
end end
end end
@ -245,8 +211,7 @@ function DocView:scroll_to_line(line, ignore_if_visible, instant)
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 x, y = self:get_line_screen_position(line)
local ox, oy = self:get_content_offset() local ox, oy = self:get_content_offset()
local _, _, _, scroll_h = self.h_scrollbar:get_track_rect() self.scroll.to.y = math.max(0, y - oy - self.size.y / 2)
self.scroll.to.y = math.max(0, y - oy - (self.size.y - scroll_h) / 2)
if instant then if instant then
self.scroll.y = self.scroll.to.y self.scroll.y = self.scroll.to.y
end end
@ -255,20 +220,17 @@ end
function DocView:scroll_to_make_visible(line, col) function DocView:scroll_to_make_visible(line, col)
local _, oy = self:get_content_offset() local ox, oy = self:get_content_offset()
local _, ly = self:get_line_screen_position(line, col) local _, ly = self:get_line_screen_position(line, col)
local lh = self:get_line_height() local lh = self:get_line_height()
local _, _, _, scroll_h = self.h_scrollbar:get_track_rect() self.scroll.to.y = common.clamp(self.scroll.to.y, ly - oy - self.size.y + lh * 2, ly - oy - lh)
self.scroll.to.y = common.clamp(self.scroll.to.y, ly - oy - self.size.y + scroll_h + lh * 2, ly - oy - lh)
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(' ')
local xsup = xoffset + gw + xmargin local xsup = xoffset + gw + xmargin
local xinf = xoffset - xmargin local xinf = xoffset - xmargin
local _, _, scroll_w = self.v_scrollbar:get_track_rect() if xsup > self.scroll.x + self.size.x then
local size_x = math.max(0, self.size.x - scroll_w) self.scroll.to.x = xsup - self.size.x
if xsup > self.scroll.x + size_x then
self.scroll.to.x = xsup - size_x
elseif xinf < self.scroll.x then elseif xinf < self.scroll.x then
self.scroll.to.x = math.max(0, xinf) self.scroll.to.x = math.max(0, xinf)
end end
@ -277,14 +239,8 @@ 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, ...)
self.hovering_gutter = false if self.hovered_scrollbar_track or self.dragging_scrollbar then
local gw = self:get_gutter_width()
if self:scrollbar_hovering() or self:scrollbar_dragging() then
self.cursor = "arrow" self.cursor = "arrow"
elseif gw > 0 and x >= self.position.x and x <= (self.position.x + gw) then
self.cursor = "arrow"
self.hovering_gutter = true
else else
self.cursor = "ibeam" self.cursor = "ibeam"
end end
@ -326,29 +282,6 @@ function DocView:mouse_selection(doc, snap_type, line1, col1, line2, col2)
end end
function DocView:on_mouse_pressed(button, x, y, clicks)
if button ~= "left" or not self.hovering_gutter then
return DocView.super.on_mouse_pressed(self, button, x, y, clicks)
end
local line = self:resolve_screen_position(x, y)
if keymap.modkeys["shift"] then
local sline, scol, sline2, scol2 = self.doc:get_selection(true)
if line > sline then
self.doc:set_selection(sline, 1, line, #self.doc.lines[line])
else
self.doc:set_selection(line, 1, sline2, #self.doc.lines[sline2])
end
else
if clicks == 1 then
self.doc:set_selection(line, 1, line, 1)
elseif clicks == 2 then
self.doc:set_selection(line, 1, line, #self.doc.lines[line])
end
end
return true
end
function DocView:on_mouse_released(...) function DocView:on_mouse_released(...)
DocView.super.on_mouse_released(self, ...) DocView.super.on_mouse_released(self, ...)
self.mouse_selecting = nil self.mouse_selecting = nil
@ -359,53 +292,13 @@ function DocView:on_text_input(text)
self.doc:text_input(text) self.doc:text_input(text)
end end
function DocView:on_ime_text_editing(text, start, length)
self.doc:ime_text_editing(text, start, length)
self.ime_status = #text > 0
self.ime_selection.from = start
self.ime_selection.size = length
-- Set the composition bounding box that the system IME
-- will consider when drawing its interface
local line1, col1, line2, col2 = self.doc:get_selection(true)
local col = math.min(col1, col2)
self:update_ime_location()
self:scroll_to_make_visible(line1, col + start)
end
---Update the composition bounding box that the system IME
---will consider when drawing its interface
function DocView:update_ime_location()
if not self.ime_status then return end
local line1, col1, line2, col2 = self.doc:get_selection(true)
local x, y = self:get_line_screen_position(line1)
local h = self:get_line_height()
local col = math.min(col1, col2)
local x1, x2 = 0, 0
if self.ime_selection.size > 0 then
-- focus on a part of the text
local from = col + self.ime_selection.from
local to = from + self.ime_selection.size
x1 = self:get_col_x_offset(line1, from)
x2 = self:get_col_x_offset(line1, to)
else
-- focus the whole text
x1 = self:get_col_x_offset(line1, col1)
x2 = self:get_col_x_offset(line2, col2)
end
ime.set_location(x + x1, y, x2 - x1, h)
end
function DocView:update() 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 line1, col1, line2, col2 = self.doc:get_selection()
if (line1 ~= self.last_line1 or col1 ~= self.last_col1 or if (line1 ~= self.last_line1 or col1 ~= self.last_col1 or
line2 ~= self.last_line2 or col2 ~= self.last_col2) and self.size.x > 0 then line2 ~= self.last_line2 or col2 ~= self.last_col2) and self.size.x > 0 then
if core.active_view == self and not ime.editing then if core.active_view == self then
self:scroll_to_make_visible(line1, col1) self:scroll_to_make_visible(line1, col1)
end end
core.blink_reset() core.blink_reset()
@ -423,8 +316,6 @@ function DocView:update()
core.blink_timer = tb core.blink_timer = tb
end end
self:update_ime_location()
DocView.super.update(self) DocView.super.update(self)
end end
@ -438,19 +329,10 @@ end
function DocView:draw_line_text(line, x, y) function DocView:draw_line_text(line, 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()
local last_token = nil for _, type, text in self.doc.highlighter:each_token(line) do
local tokens = self.doc.highlighter:get_line(line).tokens
local tokens_count = #tokens
if tokens[tokens_count] ~= nil and string.sub(tokens[tokens_count], -1) == "\n" then
last_token = tokens_count - 1
end
for tidx, type, text in self.doc.highlighter:each_token(line) do
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
-- do not render newline, fixes issue #1164
if tidx == last_token then text = text:sub(1, -2) end
tx = renderer.draw_text(font, text, tx, ty, color) tx = renderer.draw_text(font, text, tx, ty, color)
if tx > self.position.x + self.size.x then break end
end end
return self:get_line_height() return self:get_line_height()
end end
@ -517,45 +399,17 @@ function DocView:draw_line_gutter(line, x, y, width)
end end
function DocView:draw_ime_decoration(line1, col1, line2, col2)
local x, y = self:get_line_screen_position(line1)
local line_size = math.max(1, SCALE)
local lh = self:get_line_height()
-- Draw IME underline
local x1 = self:get_col_x_offset(line1, col1)
local x2 = self:get_col_x_offset(line2, col2)
renderer.draw_rect(x + math.min(x1, x2), y + lh - line_size, math.abs(x1 - x2), line_size, style.text)
-- Draw IME selection
local col = math.min(col1, col2)
local from = col + self.ime_selection.from
local to = from + self.ime_selection.size
x1 = self:get_col_x_offset(line1, from)
if from ~= to then
x2 = self:get_col_x_offset(line1, to)
line_size = style.caret_width
renderer.draw_rect(x + math.min(x1, x2), y + lh - line_size, math.abs(x1 - x2), line_size, style.caret)
end
self:draw_caret(x + x1, y)
end
function DocView:draw_overlay() function DocView:draw_overlay()
if core.active_view == self then if core.active_view == self then
local minline, maxline = self:get_visible_line_range() local minline, maxline = self:get_visible_line_range()
-- draw caret if it overlaps this line -- draw caret if it overlaps this line
local T = config.blink_period local T = config.blink_period
for _, line1, col1, line2, col2 in self.doc:get_selections() do for _, line, col in self.doc:get_selections() do
if line1 >= minline and line1 <= maxline if line >= minline and line <= maxline
and system.window_has_focus() then and system.window_has_focus() then
if ime.editing then if config.disable_blink
self:draw_ime_decoration(line1, col1, line2, col2) or (core.blink_timer - core.blink_start) % T < T / 2 then
else self:draw_caret(self:get_line_screen_position(line, col))
if config.disable_blink
or (core.blink_timer - core.blink_start) % T < T / 2 then
self:draw_caret(self:get_line_screen_position(line1, col1))
end
end end
end end
end end

View File

@ -7,15 +7,9 @@ local View = require "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 lines = {
{ fmt = "%s to run a command", cmd = "core:find-command" },
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
}
local th = style.big_font:get_height() local 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) / #lines) local x1, y1 = x, y + (dh - th) / 2
local xv = x1 local xv = x1
local title = "Lite XL" local title = "Lite XL"
local version = "version " .. VERSION local version = "version " .. VERSION
@ -30,6 +24,12 @@ local function draw_text(x, y, color)
renderer.draw_text(style.font, version, xv, y1 + th, 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 = {
{ fmt = "%s to run a command", cmd = "core:find-command" },
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
}
th = style.font:get_height() th = style.font:get_height()
y = y + (dh - (th + style.padding.y) * #lines) / 2 y = y + (dh - (th + style.padding.y) * #lines) / 2
local w = 0 local w = 0

View File

@ -1,92 +0,0 @@
local core = require "core"
local ime = { }
function ime.reset()
ime.editing = false
ime.last_location = { x = 0, y = 0, w = 0, h = 0 }
end
---Convert from utf-8 offset and length (from SDL) to byte offsets
---@param text string @Textediting string
---@param start integer @0-based utf-8 offset of the starting position of the selection
---@param length integer @Size of the utf-8 length of the selection
function ime.ingest(text, start, length)
if #text == 0 then
-- finished textediting
ime.reset()
return "", 0, 0
end
ime.editing = true
if start < 0 then
-- we assume no selection and caret at the end
return text, #text, 0
end
-- start is 0-based, so we use start + 1
local start_byte = utf8.offset(text, start + 1)
if not start_byte then
-- bad start offset
-- we assume it meant the last byte of the text
start_byte = #text
else
start_byte = math.min(start_byte - 1, #text)
end
if length < 0 then
-- caret only
return text, start_byte, 0
end
local end_byte = utf8.offset(text, start + length + 1)
if not end_byte or end_byte - 1 < start_byte then
-- bad length, assume caret only
return text, start_byte, 0
end
end_byte = math.min(end_byte - 1, #text)
return text, start_byte, end_byte - start_byte
end
---Forward the given textediting SDL event data to Views.
---@param text string @Textediting string
---@param start integer @0-based utf-8 offset of the starting position of the selection
---@param length integer @Size of the utf-8 length of the selection
function ime.on_text_editing(text, start, length, ...)
if ime.editing or #text > 0 then
core.root_view:on_ime_text_editing(ime.ingest(text, start, length, ...))
end
end
---Stop IME composition.
---Might not completely work on every platform.
function ime.stop()
if ime.editing then
-- SDL_ClearComposition for now doesn't work everywhere
system.clear_ime()
ime.on_text_editing("", 0, 0)
end
end
---Set the bounding box of the text pertaining the IME.
---The IME will draw its interface based on this info.
---@param x number
---@param y number
---@param w number
---@param h number
function ime.set_location(x, y, w, h)
if not ime.last_location or
ime.last_location.x ~= x or
ime.last_location.y ~= y or
ime.last_location.w ~= w or
ime.last_location.h ~= h
then
ime.last_location.x, ime.last_location.y, ime.last_location.w, ime.last_location.h = x, y, w, h
system.set_text_input_rect(x, y, w, h)
end
end
ime.reset()
return ime

View File

@ -6,7 +6,6 @@ local style = require "colors.default"
local command local command
local keymap local keymap
local dirwatch local dirwatch
local ime
local RootView local RootView
local StatusView local StatusView
local TitleView local TitleView
@ -18,13 +17,13 @@ local Doc
local core = {} local core = {}
local function load_session() local function load_session()
local ok, t = pcall(dofile, common.basepath(USERDIR) .. "session.lua") local ok, t = pcall(dofile, USERDIR .. "/session.lua")
return ok and t or {} return ok and t or {}
end end
local function save_session() local function save_session()
local fp = io.open(common.basepath(USERDIR) .. "session.lua", "w") local fp = io.open(USERDIR .. "/session.lua", "w")
if fp then if fp then
fp:write("return {recents=", common.serialize(core.recent_projects), fp:write("return {recents=", common.serialize(core.recent_projects),
", window=", common.serialize(table.pack(system.get_window_size())), ", window=", common.serialize(table.pack(system.get_window_size())),
@ -102,7 +101,7 @@ local function strip_leading_path(filename)
end end
local function strip_trailing_slash(filename) local function strip_trailing_slash(filename)
if filename:match("[^:]["..PATHSEP.."]$") then if filename:match("[^:][/\\]$") then
return filename:sub(1, -2) return filename:sub(1, -2)
end end
return filename return filename
@ -120,7 +119,9 @@ local function show_max_files_warning(dir)
"Too many files in project directory: stopped reading at ".. "Too many files in project directory: stopped reading at "..
config.max_project_files.." files. For more information see ".. config.max_project_files.." files. For more information see "..
"usage.md at https://github.com/lite-xl/lite-xl." "usage.md at https://github.com/lite-xl/lite-xl."
core.warn(message) if core.status_view then
core.status_view:show_message("!", style.accent, message)
end
end end
@ -182,13 +183,13 @@ local function refresh_directory(topdir, target)
directory_start_idx = directory_start_idx + 1 directory_start_idx = directory_start_idx + 1
end end
local files = dirwatch.get_directory_files(topdir, topdir.name, (target or ""), 0, function() return false end) local files = dirwatch.get_directory_files(topdir, topdir.name, (target or ""), {}, 0, function() return false end)
local change = false local change = false
-- If this file doesn't exist, we should be calling this on our parent directory, assume we'll do that. -- If this file doesn't exist, we should be calling this on our parent directory, assume we'll do that.
-- Unwatch just in case. -- Unwatch just in case.
if files == nil then if files == nil then
topdir.watch:unwatch(common.basepath(topdir.name) .. (target or "")) topdir.watch:unwatch(topdir.name .. PATHSEP .. (target or ""))
return true return true
end end
@ -212,7 +213,7 @@ local function refresh_directory(topdir, target)
-- If it's not there, remove the entry from the list as being out of order. -- If it's not there, remove the entry from the list as being out of order.
table.remove(topdir.files, old_idx) table.remove(topdir.files, old_idx)
if old_info.type == "dir" then if old_info.type == "dir" then
topdir.watch:unwatch(common.basepath(topdir.name) .. old_info.filename) topdir.watch:unwatch(topdir.name .. PATHSEP .. old_info.filename)
end end
directory_end_idx = directory_end_idx - 1 directory_end_idx = directory_end_idx - 1
end end
@ -223,7 +224,7 @@ local function refresh_directory(topdir, target)
end end
end end
for i, v in ipairs(new_directories) do for i, v in ipairs(new_directories) do
topdir.watch:watch(common.basepath(topdir.name) .. v.filename) topdir.watch:watch(topdir.name .. PATHSEP .. v.filename)
if not topdir.files_limit or core.project_subdir_is_shown(topdir, v.filename) then if not topdir.files_limit or core.project_subdir_is_shown(topdir, v.filename) then
refresh_directory(topdir, v.filename) refresh_directory(topdir, v.filename)
end end
@ -263,7 +264,7 @@ function core.add_project_directory(path)
local fstype = PLATFORM == "Linux" and system.get_fs_type(topdir.name) or "unknown" local fstype = PLATFORM == "Linux" and system.get_fs_type(topdir.name) or "unknown"
topdir.force_scans = (fstype == "nfs" or fstype == "fuse") topdir.force_scans = (fstype == "nfs" or fstype == "fuse")
local t, complete, entries_count = dirwatch.get_directory_files(topdir, topdir.name, "", 0, timed_max_files_pred) local t, complete, entries_count = dirwatch.get_directory_files(topdir, topdir.name, "", {}, 0, timed_max_files_pred)
topdir.files = t topdir.files = t
if not complete then if not complete then
topdir.slow_filesystem = not complete and (entries_count <= config.max_project_files) topdir.slow_filesystem = not complete and (entries_count <= config.max_project_files)
@ -272,7 +273,7 @@ function core.add_project_directory(path)
refresh_directory(topdir) refresh_directory(topdir)
else else
for i,v in ipairs(t) do for i,v in ipairs(t) do
if v.type == "dir" then topdir.watch:watch(common.basepath(path) .. v.filename) end if v.type == "dir" then topdir.watch:watch(path .. PATHSEP .. v.filename) end
end end
end end
topdir.watch:watch(topdir.name) topdir.watch:watch(topdir.name)
@ -286,7 +287,7 @@ function core.add_project_directory(path)
local changed = topdir.watch:check(function(target) local changed = topdir.watch:check(function(target)
if target == topdir.name then return refresh_directory(topdir) end if target == topdir.name then return refresh_directory(topdir) end
local dirpath = target:sub(#topdir.name + 2) local dirpath = target:sub(#topdir.name + 2)
local abs_dirpath = common.basepath(topdir.name) .. dirpath local abs_dirpath = topdir.name .. PATHSEP .. dirpath
if dirpath then if dirpath then
-- check if the directory is in the project files list, if not exit. -- check if the directory is in the project files list, if not exit.
local dir_index, dir_match = file_search(topdir.files, {filename = dirpath, type = "dir"}) local dir_index, dir_match = file_search(topdir.files, {filename = dirpath, type = "dir"})
@ -294,19 +295,7 @@ function core.add_project_directory(path)
end end
return refresh_directory(topdir, dirpath) return refresh_directory(topdir, dirpath)
end, 0.01, 0.01) end, 0.01, 0.01)
-- properly exit coroutine if project not open anymore to clear dir watch coroutine.yield(changed and 0.05 or 0)
local project_dir_open = false
for _, prj in ipairs(core.project_directories) do
if topdir == prj then
project_dir_open = true
break
end
end
if project_dir_open then
coroutine.yield(changed and 0 or 0.05)
else
return
end
end end
end) end)
@ -372,11 +361,6 @@ end
function core.update_project_subdir(dir, filename, expanded) function core.update_project_subdir(dir, filename, expanded)
assert(dir.files_limit, "function should be called only when directory is in files limit mode") assert(dir.files_limit, "function should be called only when directory is in files limit mode")
dir.shown_subdir[filename] = expanded dir.shown_subdir[filename] = expanded
if expanded then
dir.watch:watch(common.basepath(dir.name) .. filename)
else
dir.watch:unwatch(common.basepath(dir.name) .. filename)
end
return refresh_directory(dir, filename) return refresh_directory(dir, filename)
end end
@ -387,7 +371,7 @@ end
local function find_files_rec(root, path) local function find_files_rec(root, path)
local all = system.list_dir(root .. path) or {} local all = system.list_dir(root .. path) or {}
for _, file in ipairs(all) do for _, file in ipairs(all) do
local file = common.basepath(path) .. file local file = path .. PATHSEP .. file
local info = system.get_file_info(root .. file) local info = system.get_file_info(root .. file)
if info then if info then
info.filename = strip_leading_path(file) info.filename = strip_leading_path(file)
@ -462,7 +446,7 @@ local function create_user_directory()
error("cannot create directory \"" .. USERDIR .. "\": " .. err) error("cannot create directory \"" .. USERDIR .. "\": " .. err)
end end
for _, modname in ipairs {'plugins', 'colors', 'fonts'} do for _, modname in ipairs {'plugins', 'colors', 'fonts'} do
local subdirname = common.basepath(USERDIR) .. modname local subdirname = USERDIR .. PATHSEP .. modname
if not system.mkdir(subdirname) then if not system.mkdir(subdirname) then
error("cannot create directory: \"" .. subdirname .. "\"") error("cannot create directory: \"" .. subdirname .. "\"")
end end
@ -493,9 +477,6 @@ local style = require "core.style"
-- key binding: -- key binding:
-- keymap.add { ["ctrl+escape"] = "core:quit" } -- keymap.add { ["ctrl+escape"] = "core:quit" }
-- pass 'true' for second parameter to overwrite an existing binding
-- keymap.add({ ["ctrl+pageup"] = "root:switch-to-previous-tab" }, true)
-- keymap.add({ ["ctrl+pagedown"] = "root:switch-to-next-tab" }, true)
------------------------------- Fonts ---------------------------------------- ------------------------------- Fonts ----------------------------------------
@ -529,25 +510,13 @@ local style = require "core.style"
------------------------------ Plugins ---------------------------------------- ------------------------------ Plugins ----------------------------------------
-- disable plugin loading setting config entries: -- enable or disable plugin loading setting config entries:
-- disable plugin detectindent, otherwise it is enabled by default: -- enable plugins.trimwhitespace, otherwise it is disable by default:
-- config.plugins.trimwhitespace = true
--
-- disable detectindent, otherwise it is enabled by default
-- config.plugins.detectindent = false -- config.plugins.detectindent = false
---------------------------- Miscellaneous -------------------------------------
-- modify list of files to ignore when indexing the project:
-- config.ignore_files = {
-- -- folders
-- "^%.svn/", "^%.git/", "^%.hg/", "^CVS/", "^%.Trash/", "^%.Trash%-.*/",
-- "^node_modules/", "^%.cache/", "^__pycache__/",
-- -- files
-- "%.pyc$", "%.pyo$", "%.exe$", "%.dll$", "%.obj$", "%.o$",
-- "%.a$", "%.lib$", "%.so$", "%.dylib$", "%.ncb$", "%.sdf$",
-- "%.suo$", "%.pdb$", "%.idb$", "%.class$", "%.psd$", "%.db$",
-- "^desktop%.ini$", "^%.DS_Store$", "^%.directory$",
-- }
]]) ]])
init_file:close() init_file:close()
end end
@ -572,24 +541,24 @@ local config = require "core.config"
-- --
-- Here some examples: -- Here some examples:
-- --
-- "^%." matches any file of directory whose basename begins with a dot. -- "^%." match any file of directory whose basename begins with a dot.
-- --
-- When there is an '/' or a '/$' at the end, the pattern will only match -- When there is an '/' or a '/$' at the end the pattern it will only match
-- directories. When using such a pattern a final '/' will be added to the name -- directories. When using such a pattern a final '/' will be added to the name
-- of any directory entry before checking if it matches. -- of any directory entry before checking if it matches.
-- --
-- "^%.git/" matches any directory named ".git" anywhere in the project. -- "^%.git/" matches any directory named ".git" anywhere in the project.
-- --
-- If a "/" appears anywhere in the pattern (except when it appears at the end or -- If a "/" appears anywhere in the pattern except if it appears at the end or
-- is immediately followed by a '$'), then the pattern will be applied to the full -- is immediately followed by a '$' then the pattern will be applied to the full
-- path of the file or directory. An initial "/" will be prepended to the file's -- path of the file or directory. An initial "/" will be prepended to the file's
-- or directory's path to indicate the project's root. -- or directory's path to indicate the project's root.
-- --
-- "^/node_modules/" will match a directory named "node_modules" at the project's root. -- "^/node_modules/" will match a directory named "node_modules" at the project's root.
-- "^/build.*/" will match any top level directory whose name begins with "build". -- "^/build.*/" match any top level directory whose name begins with "build"
-- "^/subprojects/.+/" will match any directory inside a top-level folder named "subprojects". -- "^/subprojects/.+/" match any directory inside a top-level folder named "subprojects".
-- You may activate some plugins on a per-project basis to override the user's settings. -- You may activate some plugins on a pre-project base to override the user's settings.
-- config.plugins.trimwitespace = true -- config.plugins.trimwitespace = true
]]) ]])
init_file:close() init_file:close()
@ -602,7 +571,7 @@ function core.load_user_directory()
if not stat_dir then if not stat_dir then
create_user_directory() create_user_directory()
end end
local init_filename = common.basepath(USERDIR) .. "init.lua" local init_filename = USERDIR .. "/init.lua"
local stat_file = system.get_file_info(init_filename) local stat_file = system.get_file_info(init_filename)
if not stat_file then if not stat_file then
write_user_init_file(init_filename) write_user_init_file(init_filename)
@ -635,7 +604,7 @@ end
local function add_config_files_hooks() local function add_config_files_hooks()
-- auto-realod style when user's module is saved by overriding Doc:Save() -- auto-realod style when user's module is saved by overriding Doc:Save()
local doc_save = Doc.save local doc_save = Doc.save
local user_filename = system.absolute_path(common.basepath(USERDIR) .. "init.lua") local user_filename = system.absolute_path(USERDIR .. PATHSEP .. "init.lua")
function Doc:save(filename, abs_filename) function Doc:save(filename, abs_filename)
local module_filename = system.absolute_path(".lite_project.lua") local module_filename = system.absolute_path(".lite_project.lua")
doc_save(self, filename, abs_filename) doc_save(self, filename, abs_filename)
@ -660,9 +629,9 @@ function core.project_absolute_path(filename)
return common.normalize_path(filename) return common.normalize_path(filename)
elseif not core.project_dir then elseif not core.project_dir then
local cwd = system.absolute_path(".") local cwd = system.absolute_path(".")
return common.basepath(cwd) .. common.normalize_path(filename) return cwd .. PATHSEP .. common.normalize_path(filename)
else else
return common.basepath(core.project_dir) .. filename return core.project_dir .. PATHSEP .. filename
end end
end end
@ -671,7 +640,6 @@ function core.init()
command = require "core.command" command = require "core.command"
keymap = require "core.keymap" keymap = require "core.keymap"
dirwatch = require "core.dirwatch" dirwatch = require "core.dirwatch"
ime = require "core.ime"
RootView = require "core.rootview" RootView = require "core.rootview"
StatusView = require "core.statusview" StatusView = require "core.statusview"
TitleView = require "core.titleview" TitleView = require "core.titleview"
@ -713,7 +681,7 @@ function core.init()
local file_abs = core.project_absolute_path(arg_filename) local file_abs = core.project_absolute_path(arg_filename)
if file_abs then if file_abs then
table.insert(files, file_abs) table.insert(files, file_abs)
project_dir = file_abs:match("^(.+)[:/\\].+$") project_dir = file_abs:match("^(.+)[/\\].+$")
end end
end end
end end
@ -806,19 +774,15 @@ function core.init()
end end
if not plugins_success or got_user_error or got_project_error then if not plugins_success or got_user_error or got_project_error then
-- defer LogView to after everything is initialized, command.perform("core:open-log")
-- so that EmptyView won't be added after LogView.
core.add_thread(function()
command.perform("core:open-log")
end)
end end
core.configure_borderless_window() core.configure_borderless_window()
if #plugins_refuse_list.userdir.plugins > 0 or #plugins_refuse_list.datadir.plugins > 0 then if #plugins_refuse_list.userdir.plugins > 0 or #plugins_refuse_list.datadir.plugins > 0 then
local opt = { local opt = {
{ text = "Exit", default_no = true }, { font = style.font, text = "Exit", default_no = true },
{ text = "Continue", default_yes = true } { font = style.font, text = "Continue" , default_yes = true }
} }
local msg = {} local msg = {}
for _, entry in pairs(plugins_refuse_list) do for _, entry in pairs(plugins_refuse_list) do
@ -859,8 +823,8 @@ function core.confirm_close_docs(docs, close_fn, ...)
end end
local args = {...} local args = {...}
local opt = { local opt = {
{ text = "Yes", default_yes = true }, { font = style.font, text = "Yes", default_yes = true },
{ text = "No", default_no = true } { font = style.font, text = "No" , default_no = true }
} }
core.nag_view:show("Unsaved Changes", text, opt, function(item) core.nag_view:show("Unsaved Changes", text, opt, function(item)
if item.text == "Yes" then close_fn(table.unpack(args)) end if item.text == "Yes" then close_fn(table.unpack(args)) end
@ -878,7 +842,7 @@ function core.delete_temp_files(dir)
dir = type(dir) == "string" and common.normalize_path(dir) or USERDIR dir = type(dir) == "string" and common.normalize_path(dir) or USERDIR
for _, filename in ipairs(system.list_dir(dir) or {}) do for _, filename in ipairs(system.list_dir(dir) or {}) do
if filename:find(temp_file_prefix, 1, true) == 1 then if filename:find(temp_file_prefix, 1, true) == 1 then
os.remove(common.basepath(dir) .. filename) os.remove(dir .. PATHSEP .. filename)
end end
end end
end end
@ -886,7 +850,7 @@ end
function core.temp_filename(ext, dir) function core.temp_filename(ext, dir)
dir = type(dir) == "string" and common.normalize_path(dir) or USERDIR dir = type(dir) == "string" and common.normalize_path(dir) or USERDIR
temp_file_counter = temp_file_counter + 1 temp_file_counter = temp_file_counter + 1
return common.basepath(dir) .. temp_file_prefix return dir .. PATHSEP .. temp_file_prefix
.. string.format("%06x", temp_file_counter) .. (ext or "") .. string.format("%06x", temp_file_counter) .. (ext or "")
end end
@ -923,7 +887,7 @@ end
local function get_plugin_details(filename) local function get_plugin_details(filename)
local info = system.get_file_info(filename) local info = system.get_file_info(filename)
if info ~= nil and info.type == "dir" then if info ~= nil and info.type == "dir" then
filename = filename .. PATHSEP .. "init.lua" filename = filename .. "/init.lua"
info = system.get_file_info(filename) info = system.get_file_info(filename)
end end
if not info or not filename:match("%.lua$") then return false end if not info or not filename:match("%.lua$") then return false end
@ -962,7 +926,7 @@ function core.load_plugins()
} }
local files, ordered = {}, {} local files, ordered = {}, {}
for _, root_dir in ipairs {DATADIR, USERDIR} do for _, root_dir in ipairs {DATADIR, USERDIR} do
local plugin_dir = common.basepath(root_dir) .. "plugins" local plugin_dir = root_dir .. "/plugins"
for _, filename in ipairs(system.list_dir(plugin_dir) or {}) do for _, filename in ipairs(system.list_dir(plugin_dir) or {}) do
if not files[filename] then if not files[filename] then
table.insert( table.insert(
@ -977,7 +941,7 @@ function core.load_plugins()
for _, plugin in ipairs(ordered) do for _, plugin in ipairs(ordered) do
local dir = files[plugin.file] local dir = files[plugin.file]
local name = plugin.file:match("(.-)%.lua$") or plugin.file local name = plugin.file:match("(.-)%.lua$") or plugin.file
local is_lua_file, details = get_plugin_details(common.basepath(dir) .. plugin.file) local is_lua_file, details = get_plugin_details(dir .. '/' .. plugin.file)
plugin.valid = is_lua_file plugin.valid = is_lua_file
plugin.name = name plugin.name = name
@ -1070,8 +1034,6 @@ end
function core.set_active_view(view) function core.set_active_view(view)
assert(view, "Tried to set active view to nil") assert(view, "Tried to set active view to nil")
-- Reset the IME even if the focus didn't change
ime.stop()
if view ~= core.active_view then if view ~= core.active_view then
if core.active_view and core.active_view.force_focus then if core.active_view and core.active_view.force_focus then
core.next_active_view = view core.next_active_view = view
@ -1164,9 +1126,7 @@ function core.custom_log(level, show, backtrace, fmt, ...)
local text = string.format(fmt, ...) local text = string.format(fmt, ...)
if show then if show then
local s = style.log[level] local s = style.log[level]
if core.status_view then core.status_view:show_message(s.icon, s.color, text)
core.status_view:show_message(s.icon, s.color, text)
end
end end
local info = debug.getinfo(2, "Sl") local info = debug.getinfo(2, "Sl")
@ -1176,7 +1136,7 @@ function core.custom_log(level, show, backtrace, fmt, ...)
text = text, text = text,
time = os.time(), time = os.time(),
at = at, at = at,
info = backtrace and debug.traceback("", 2):gsub("\t", "") info = backtrace and debug.traceback(nil, 2):gsub("\t", "")
} }
table.insert(core.log_items, item) table.insert(core.log_items, item)
if #core.log_items > config.max_log_items then if #core.log_items > config.max_log_items then
@ -1225,7 +1185,7 @@ function core.try(fn, ...)
local err local err
local ok, res = xpcall(fn, function(msg) local ok, res = xpcall(fn, function(msg)
local item = core.error("%s", msg) local item = core.error("%s", msg)
item.info = debug.traceback("", 2):gsub("\t", "") item.info = debug.traceback(nil, 2):gsub("\t", "")
err = msg err = msg
end, ...) end, ...)
if ok then if ok then
@ -1238,12 +1198,7 @@ function core.on_event(type, ...)
local did_keymap = false local did_keymap = false
if type == "textinput" then if type == "textinput" then
core.root_view:on_text_input(...) core.root_view:on_text_input(...)
elseif type == "textediting" then
ime.on_text_editing(...)
elseif type == "keypressed" then elseif type == "keypressed" then
-- In some cases during IME composition input is still sent to us
-- so we just ignore it.
if ime.editing then return false end
did_keymap = keymap.on_key_pressed(...) did_keymap = keymap.on_key_pressed(...)
elseif type == "keyreleased" then elseif type == "keyreleased" then
keymap.on_key_released(...) keymap.on_key_released(...)
@ -1354,7 +1309,7 @@ end
local run_threads = coroutine.wrap(function() local run_threads = coroutine.wrap(function()
while true do while true do
local max_time = 1 / config.fps - 0.004 local max_time = 1 / config.fps - 0.004
local minimal_time_to_wake = math.huge local need_more_work = false
for k, thread in pairs(core.threads) do for k, thread in pairs(core.threads) do
-- run thread -- run thread
@ -1368,72 +1323,48 @@ local run_threads = coroutine.wrap(function()
end end
elseif wait then elseif wait then
thread.wake = system.get_time() + wait thread.wake = system.get_time() + wait
minimal_time_to_wake = math.min(minimal_time_to_wake, wait)
else else
minimal_time_to_wake = 0 need_more_work = true
end end
else
minimal_time_to_wake = math.min(minimal_time_to_wake, thread.wake - system.get_time())
end end
-- stop running threads if we're about to hit the end of frame -- stop running threads if we're about to hit the end of frame
if system.get_time() - core.frame_start > max_time then if system.get_time() - core.frame_start > max_time then
coroutine.yield(0, false) coroutine.yield(true)
end end
end end
coroutine.yield(minimal_time_to_wake, true) if not need_more_work then coroutine.yield(false) end
end end
end) end)
function core.run() function core.run()
local next_step local idle_iterations = 0
local last_frame_time
local run_threads_full = 0
while true do while true do
core.frame_start = system.get_time() core.frame_start = system.get_time()
local time_to_wake, threads_done = run_threads() local need_more_work = run_threads()
if threads_done then local did_redraw = core.step()
run_threads_full = run_threads_full + 1
end
local did_redraw = false
local did_step = false
local force_draw = core.redraw and last_frame_time and core.frame_start - last_frame_time > (1 / config.fps)
if force_draw or not next_step or system.get_time() >= next_step then
if core.step() then
did_redraw = true
last_frame_time = core.frame_start
end
next_step = nil
did_step = true
end
if core.restart_request or core.quit_request then break end if core.restart_request or core.quit_request then break end
if not did_redraw and not need_more_work then
if not did_redraw then idle_iterations = idle_iterations + 1
if system.window_has_focus() or not did_step or run_threads_full < 2 then -- do not wait of events at idle_iterations = 1 to give a chance at core.step to run
local now = system.get_time() -- and set "redraw" flag.
if not next_step then -- compute the time until the next blink if idle_iterations > 1 then
local t = now - core.blink_start if system.window_has_focus() then
-- keep running even with no events to make the cursor blinks
local t = system.get_time() - core.blink_start
local h = config.blink_period / 2 local h = config.blink_period / 2
local dt = math.ceil(t / h) * h - t local dt = math.ceil(t / h) * h - t
local cursor_time_to_wake = dt + 1 / config.fps system.wait_event(dt + 1 / config.fps)
next_step = now + cursor_time_to_wake else
system.wait_event()
end end
if system.wait_event(math.min(next_step - now, time_to_wake)) then
next_step = nil -- if we've recevied an event, perform a step
end
else
system.wait_event()
next_step = nil -- perform a step when we're not in focus if get we an event
end end
else -- if we redrew, then make sure we only draw at most FPS/sec else
run_threads_full = 0 idle_iterations = 0
local now = system.get_time() local elapsed = system.get_time() - core.frame_start
local elapsed = now - core.frame_start system.sleep(math.max(0, 1 / config.fps - elapsed))
local next_frame = math.max(0, 1 / config.fps - elapsed)
next_step = next_step or (now + next_frame)
system.sleep(math.min(next_frame, time_to_wake))
end end
end end
end end
@ -1451,9 +1382,9 @@ end
function core.on_error(err) function core.on_error(err)
-- write error to file -- write error to file
local fp = io.open(common.basepath(USERDIR) .. "error.txt", "wb") local fp = io.open(USERDIR .. "/error.txt", "wb")
fp:write("Error: " .. tostring(err) .. "\n") fp:write("Error: " .. tostring(err) .. "\n")
fp:write(debug.traceback("", 4) .. "\n") fp:write(debug.traceback(nil, 4) .. "\n")
fp:close() fp:close()
-- save copy of all unsaved documents -- save copy of all unsaved documents
for _, doc in ipairs(core.docs) do for _, doc in ipairs(core.docs) do
@ -1465,3 +1396,4 @@ end
return core return core

View File

@ -6,7 +6,7 @@ local function keymap_macos(keymap)
["cmd+n"] = "core:new-doc", ["cmd+n"] = "core:new-doc",
["cmd+shift+c"] = "core:change-project-folder", ["cmd+shift+c"] = "core:change-project-folder",
["cmd+shift+o"] = "core:open-project-folder", ["cmd+shift+o"] = "core:open-project-folder",
["cmd+option+r"] = "core:restart", ["cmd+shift+r"] = "core:restart",
["cmd+ctrl+return"] = "core:toggle-fullscreen", ["cmd+ctrl+return"] = "core:toggle-fullscreen",
["cmd+ctrl+shift+j"] = "root:split-left", ["cmd+ctrl+shift+j"] = "root:split-left",
@ -34,9 +34,7 @@ local function keymap_macos(keymap)
["cmd+8"] = "root:switch-to-tab-8", ["cmd+8"] = "root:switch-to-tab-8",
["cmd+9"] = "root:switch-to-tab-9", ["cmd+9"] = "root:switch-to-tab-9",
["wheel"] = "root:scroll", ["wheel"] = "root:scroll",
["hwheel"] = "root:horizontal-scroll",
["shift+hwheel"] = "root:horizontal-scroll",
["cmd+f"] = "find-replace:find", ["cmd+f"] = "find-replace:find",
["cmd+r"] = "find-replace:replace", ["cmd+r"] = "find-replace:replace",
["f3"] = "find-replace:repeat-find", ["f3"] = "find-replace:repeat-find",

View File

@ -1,7 +1,6 @@
local core = require "core" 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 ime = require "core.ime"
local keymap = {} local keymap = {}
---@alias keymap.shortcut string ---@alias keymap.shortcut string
@ -37,38 +36,17 @@ local modkey_map = modkeys_os.map
local modkeys = modkeys_os.keys local modkeys = modkeys_os.keys
---Normalizes a stroke sequence to follow the modkeys table
---@param stroke string
---@return string
local function normalize_stroke(stroke)
local stroke_table = {}
for key in stroke:gmatch("[^+]+") do
table.insert(stroke_table, key)
end
table.sort(stroke_table, function(a, b)
if a == b then return false end
for _, mod in ipairs(modkeys) do
if a == mod or b == mod then
return a == mod
end
end
return a < b
end)
return table.concat(stroke_table, "+")
end
---Generates a stroke sequence including currently pressed mod keys. ---Generates a stroke sequence including currently pressed mod keys.
---@param key string ---@param key string
---@return string ---@return string
local function key_to_stroke(key) local function key_to_stroke(key)
local keys = { key } local stroke = ""
for _, mk in ipairs(modkeys) do for _, mk in ipairs(modkeys) do
if keymap.modkeys[mk] then if keymap.modkeys[mk] then
table.insert(keys, mk) stroke = stroke .. mk .. "+"
end end
end end
return normalize_stroke(table.concat(keys, "+")) return stroke .. key
end end
@ -97,12 +75,11 @@ end
---@param map keymap.map ---@param map keymap.map
local function remove_duplicates(map) local function remove_duplicates(map)
for stroke, commands in pairs(map) do for stroke, commands in pairs(map) do
local normalized_stroke = normalize_stroke(stroke)
if type(commands) == "string" or type(commands) == "function" then if type(commands) == "string" or type(commands) == "function" then
commands = { commands } commands = { commands }
end end
if keymap.map[normalized_stroke] then if keymap.map[stroke] then
for _, registered_cmd in ipairs(keymap.map[normalized_stroke]) do for _, registered_cmd in ipairs(keymap.map[stroke]) do
local j = 0 local j = 0
for i=1, #commands do for i=1, #commands do
while commands[i + j] == registered_cmd do while commands[i + j] == registered_cmd do
@ -120,12 +97,11 @@ local function remove_duplicates(map)
end end
end end
---Add bindings by replacing commands that were previously assigned to a shortcut. ---Add bindings by replacing commands that were previously assigned to a shortcut.
---@param map keymap.map ---@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
stroke = normalize_stroke(stroke)
if type(commands) == "string" or type(commands) == "function" then if type(commands) == "string" or type(commands) == "function" then
commands = { commands } commands = { commands }
end end
@ -153,7 +129,6 @@ function keymap.add(map, overwrite)
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
stroke = normalize_stroke(stroke)
if overwrite then if overwrite then
if keymap.map[stroke] then if keymap.map[stroke] then
for _, cmd in ipairs(keymap.map[stroke]) do for _, cmd in ipairs(keymap.map[stroke]) do
@ -179,7 +154,6 @@ end
---@param shortcut string ---@param shortcut string
---@param cmd string ---@param cmd string
function keymap.unbind(shortcut, cmd) function keymap.unbind(shortcut, cmd)
shortcut = normalize_stroke(shortcut)
remove_only(keymap.map, shortcut, cmd) remove_only(keymap.map, shortcut, cmd)
remove_only(keymap.reverse_map, cmd, shortcut) remove_only(keymap.reverse_map, cmd, shortcut)
end end
@ -235,32 +209,9 @@ function keymap.on_key_pressed(k, ...)
return false return false
end end
function keymap.on_mouse_wheel(delta_y, delta_x, ...) function keymap.on_mouse_wheel(delta, ...)
local y_direction = delta_y > 0 and "up" or "down" return not (keymap.on_key_pressed("wheel" .. (delta > 0 and "up" or "down"), delta, ...)
local x_direction = delta_x > 0 and "left" or "right" or keymap.on_key_pressed("wheel", delta, ...))
-- Try sending a "cumulative" event for both scroll directions
if delta_y ~= 0 and delta_x ~= 0 then
local result = keymap.on_key_pressed("wheel" .. y_direction .. x_direction, delta_y, delta_x, ...)
if not result then
result = keymap.on_key_pressed("wheelyx", delta_y, delta_x, ...)
end
if result then return true end
end
-- Otherwise send each direction as its own separate event
local y_result, x_result
if delta_y ~= 0 then
y_result = keymap.on_key_pressed("wheel" .. y_direction, delta_y, ...)
if not y_result then
y_result = keymap.on_key_pressed("wheel", delta_y, ...)
end
end
if delta_x ~= 0 then
x_result = keymap.on_key_pressed("wheel" .. x_direction, delta_x, ...)
if not x_result then
x_result = keymap.on_key_pressed("hwheel", delta_x, ...)
end
end
return y_result or x_result
end end
function keymap.on_mouse_pressed(button, x, y, clicks) function keymap.on_mouse_pressed(button, x, y, clicks)
@ -323,8 +274,6 @@ keymap.add_direct {
["alt+8"] = "root:switch-to-tab-8", ["alt+8"] = "root:switch-to-tab-8",
["alt+9"] = "root:switch-to-tab-9", ["alt+9"] = "root:switch-to-tab-9",
["wheel"] = "root:scroll", ["wheel"] = "root:scroll",
["hwheel"] = "root:horizontal-scroll",
["shift+wheel"] = "root:horizontal-scroll",
["ctrl+f"] = "find-replace:find", ["ctrl+f"] = "find-replace:find",
["ctrl+r"] = "find-replace:replace", ["ctrl+r"] = "find-replace:replace",
@ -411,3 +360,4 @@ keymap.add_direct {
} }
return keymap return keymap

View File

@ -125,19 +125,9 @@ end
function LogView:update() function LogView:update()
local item = core.log_items[#core.log_items] local item = core.log_items[#core.log_items]
if self.last_item ~= item then if self.last_item ~= item then
local lh = style.font:get_height() + style.padding.y
if 0 < self.scroll.to.y then
local index = #core.log_items
while index > 1 and self.last_item ~= core.log_items[index] do
index = index - 1
end
local diff_index = #core.log_items - index
self.scroll.to.y = self.scroll.to.y + diff_index * lh
self.scroll.y = self.scroll.to.y
else
self.yoffset = -lh
end
self.last_item = item self.last_item = item
self.scroll.to.y = 0
self.yoffset = -(style.font:get_height() + style.padding.y)
end end
local expanding = self.expanding[1] local expanding = self.expanding[1]

View File

@ -9,6 +9,6 @@ modkeys.map = {
["right alt"] = "altgr", ["right alt"] = "altgr",
} }
modkeys.keys = { "ctrl", "shift", "alt", "altgr" } modkeys.keys = { "ctrl", "alt", "altgr", "shift" }
return modkeys return modkeys

View File

@ -13,6 +13,6 @@ modkeys.map = {
["right alt"] = "altgr", ["right alt"] = "altgr",
} }
modkeys.keys = { "ctrl", "alt", "option", "altgr", "shift", "cmd" } modkeys.keys = { "cmd", "ctrl", "alt", "option", "altgr", "shift" }
return modkeys return modkeys

View File

@ -91,7 +91,7 @@ function NagView:each_option()
for i = #self.options, 1, -1 do for i = #self.options, 1, -1 do
opt = self.options[i] opt = self.options[i]
bw = style.font:get_width(opt.text) + 2 * BORDER_WIDTH + style.padding.x bw = opt.font:get_width(opt.text) + 2 * BORDER_WIDTH + style.padding.x
ox = ox - bw - style.padding.x ox = ox - bw - style.padding.x
coroutine.yield(i, opt, ox,oy,bw,bh) coroutine.yield(i, opt, ox,oy,bw,bh)
@ -230,7 +230,7 @@ local function draw_nagview_message(self)
renderer.draw_rect(lx,ly,uw,UNDERLINE_WIDTH, style.nagbar_text) renderer.draw_rect(lx,ly,uw,UNDERLINE_WIDTH, style.nagbar_text)
end end
common.draw_text(style.font, style.nagbar_text, opt.text, "center", fx,fy,fw,fh) common.draw_text(opt.font, style.nagbar_text, opt.text, "center", fx,fy,fw,fh)
end end
self:draw_scrollbar() self:draw_scrollbar()
@ -245,16 +245,6 @@ function NagView:draw()
core.root_view:defer_draw(draw_nagview_message, self) core.root_view:defer_draw(draw_nagview_message, self)
end end
function NagView:on_scale_change(new_scale, old_scale)
BORDER_WIDTH = common.round(1 * new_scale)
UNDERLINE_WIDTH = common.round(2 * new_scale)
UNDERLINE_MARGIN = common.round(1 * new_scale)
self.target_height = math.max(
self:get_message_height(),
self:get_buttons_height()
)
end
function NagView:get_message_height() function NagView:get_message_height()
local h = 0 local h = 0
for str in string.gmatch(self.message, "(.-)\n") do for str in string.gmatch(self.message, "(.-)\n") do

View File

@ -323,14 +323,15 @@ end
function Node:get_scroll_button_rect(index) function Node:get_scroll_button_rect(index)
local w, pad = get_scroll_button_width() local w, pad = get_scroll_button_width()
local h = style.font:get_height() + style.padding.y * 2 local h = style.font:get_height() + style.padding.y * 2
local x = self.position.x + (index == 1 and self.size.x - w * 2 or self.size.x - w) local x = self.position.x + (index == 1 and 0 or self.size.x - w)
return x, self.position.y, w, h, pad return x, self.position.y, w, h, pad
end end
function Node:get_tab_rect(idx) function Node:get_tab_rect(idx)
local maxw = self.size.x local sbw = get_scroll_button_width()
local x0 = self.position.x local maxw = self.size.x - 2 * sbw
local x0 = self.position.x + sbw
local x1 = x0 + common.clamp(self.tab_width * (idx - 1) - self.tab_shift, 0, maxw) local x1 = x0 + common.clamp(self.tab_width * (idx - 1) - self.tab_shift, 0, maxw)
local x2 = x0 + common.clamp(self.tab_width * idx - self.tab_shift, 0, maxw) local x2 = x0 + common.clamp(self.tab_width * idx - self.tab_shift, 0, maxw)
local h = style.font:get_height() + style.padding.y * 2 local h = style.font:get_height() + style.padding.y * 2
@ -468,10 +469,7 @@ end
function Node:target_tab_width() function Node:target_tab_width()
local n = self:get_visible_tabs_number() local n = self:get_visible_tabs_number()
local w = self.size.x local w = self.size.x - get_scroll_button_width() * 2
if #self.views > n then
w = self.size.x - get_scroll_button_width() * 2
end
return common.clamp(style.tab_width, w / config.max_tabs, w / n) return common.clamp(style.tab_width, w / config.max_tabs, w / n)
end end
@ -549,14 +547,24 @@ function Node:draw_tab(view, is_active, is_hovered, is_close_hovered, x, y, w, h
end end
function Node:draw_tabs() function Node:draw_tabs()
local _, y, w, h, scroll_padding = self:get_scroll_button_rect(1) local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
local x = self.position.x
local ds = style.divider_size local ds = style.divider_size
local dots_width = style.font:get_width("") local dots_width = style.font:get_width("")
core.push_clip_rect(x, y, self.size.x, h) core.push_clip_rect(x, y, self.size.x, h)
renderer.draw_rect(x, y, self.size.x, h, style.background2) renderer.draw_rect(x, y, self.size.x, h, style.background2)
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider) renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
if self.tab_offset > 1 then
local button_style = self.hovered_scroll_button == 1 and style.text or style.dim
common.draw_text(style.icon_font, button_style, "<", nil, x + scroll_padding, y, 0, h)
end
local tabs_number = self:get_visible_tabs_number() local tabs_number = self:get_visible_tabs_number()
if #self.views > self.tab_offset + tabs_number - 1 then
local xrb, yrb, wrb = self:get_scroll_button_rect(2)
local button_style = self.hovered_scroll_button == 2 and style.text or style.dim
common.draw_text(style.icon_font, button_style, ">", nil, xrb + scroll_padding, yrb, 0, h)
end
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
local view = self.views[i] local view = self.views[i]
@ -566,18 +574,6 @@ function Node:draw_tabs()
x, y, w, h) x, y, w, h)
end end
if #self.views > tabs_number then
local _, pad = get_scroll_button_width()
local xrb, yrb, wrb, hrb = self:get_scroll_button_rect(1)
renderer.draw_rect(xrb + pad, yrb, wrb * 2, hrb, style.background2)
local left_button_style = (self.hovered_scroll_button == 1 and self.tab_offset > 1) and style.text or style.dim
common.draw_text(style.icon_font, left_button_style, "<", nil, xrb + scroll_padding, yrb, 0, h)
xrb, yrb, wrb = self:get_scroll_button_rect(2)
local right_button_style = (self.hovered_scroll_button == 2 and #self.views > self.tab_offset + tabs_number - 1) and style.text or style.dim
common.draw_text(style.icon_font, right_button_style, ">", nil, xrb + scroll_padding, yrb, 0, h)
end
core.pop_clip_rect() core.pop_clip_rect()
end end

View File

@ -47,7 +47,7 @@ function Object:__tostring()
return "Object" return "Object"
end end
---Metamethod to allow using the object call as a constructor. ---Methamethod to allow using the object call as a constructor.
---@return core.object ---@return core.object
function Object:__call(...) function Object:__call(...)
local obj = setmetatable({}, self) local obj = setmetatable({}, self)

View File

@ -2,81 +2,70 @@
-- pattern:gsub(string). -- pattern:gsub(string).
regex.__index = function(table, key) return regex[key]; end regex.__index = function(table, key) return regex[key]; end
---Looks for the first match of `pattern` in the string `str`. regex.match = function(pattern_string, string, offset, options)
---If it finds a match, it returns the indices of `str` where this occurrence local pattern = type(pattern_string) == "table" and
---starts and ends; otherwise, it returns `nil`. pattern_string or regex.compile(pattern_string)
---If the pattern has captures, the captured start and end indexes are returned, local res = { regex.cmatch(pattern, string, offset or 1, options or 0) }
---after the two initial ones. res[2] = res[2] and res[2] - 1
---
---@param pattern string|table The regex pattern to use, either as a simple string or precompiled.
---@param str string The string to search for valid matches.
---@param offset? integer The position on the subject to start searching.
---@param options? integer A bit field of matching options, eg: regex.NOTBOL | regex.NOTEMPTY
---
---@return integer? start Offset where the first match was found; `nil` if no match.
---@return integer? end Offset where the first match ends; `nil` if no match.
---@return integer? ... #Captured matches offsets.
regex.find_offsets = function(pattern, str, offset, options)
if type(pattern) ~= "table" then
pattern = regex.compile(pattern)
end
local res = { regex.cmatch(pattern, str, offset or 1, options or 0) }
-- Reduce every end delimiter by 1
for i = 2,#res,2 do
res[i] = res[i] - 1
end
return table.unpack(res) return table.unpack(res)
end end
---Behaves like `string.match`. -- Will iterate back through any UTF-8 bytes so that we don't replace bits
---Looks for the first match of `pattern` in the string `str`. -- mid character.
---If it finds a match, it returns the matched string; otherwise, it returns `nil`. local function previous_character(str, index)
---If the pattern has captures, only the captured strings are returned. local byte
---If a capture is empty, its offset is returned instead. repeat
--- index = index - 1
---@param pattern string|table The regex pattern to use, either as a simple string or precompiled. byte = string.byte(str, index)
---@param str string The string to search for valid matches. until byte < 128 or byte >= 192
---@param offset? integer The position on the subject to start searching. return index
---@param options? integer A bit field of matching options, eg: regex.NOTBOL | regex.NOTEMPTY
---
---@return (string|integer)? ... #List of captured matches; the entire match if no matches were specified; if the match is empty, its offset is returned instead.
regex.match = function(pattern, str, offset, options)
local res = { regex.find(pattern, str, offset, options) }
if #res == 0 then return end
-- If available, only return captures
if #res > 2 then return table.unpack(res, 3) end
return string.sub(str, res[1], res[2])
end end
---Behaves like `string.find`. -- Moves to the end of the identified character.
---Looks for the first match of `pattern` in the string `str`. local function end_character(str, index)
---If it finds a match, it returns the indices of `str` where this occurrence local byte = string.byte(str, index + 1)
---starts and ends; otherwise, it returns `nil`. while byte and byte >= 128 and byte < 192 do
---If the pattern has captures, the captured strings are returned, index = index + 1
---after the two indexes ones. byte = string.byte(str, index + 1)
---If a capture is empty, its offset is returned instead.
---
---@param pattern string|table The regex pattern to use, either as a simple string or precompiled.
---@param str string The string to search for valid matches.
---@param offset? integer The position on the subject to start searching.
---@param options? integer A bit field of matching options, eg: regex.NOTBOL | regex.NOTEMPTY
---
---@return integer? start Offset where the first match was found; `nil` if no match.
---@return integer? end Offset where the first match ends; `nil` if no match.
---@return (string|integer)? ... #List of captured matches; if the match is empty, its offset is returned instead.
regex.find = function(pattern, str, offset, options)
local res = { regex.find_offsets(pattern, str, offset, options) }
local out = { }
if #res == 0 then return end
out[1] = res[1]
out[2] = res[2]
for i = 3,#res,2 do
if res[i] > res[i+1] then
-- Like in string.find, if the group has size 0, return the index
table.insert(out, res[i])
else
table.insert(out, string.sub(str, res[i], res[i+1]))
end
end end
return table.unpack(out) return index
end end
-- Build off matching. For now, only support basic replacements, but capture
-- groupings should be doable. We can even have custom group replacements and
-- transformations and stuff in lua. Currently, this takes group replacements
-- as \1 - \9.
-- Should work on UTF-8 text.
regex.gsub = function(pattern_string, str, replacement)
local pattern = type(pattern_string) == "table" and
pattern_string or regex.compile(pattern_string)
local result, indices = ""
local matches, replacements = {}, {}
repeat
indices = { regex.cmatch(pattern, str) }
if #indices > 0 then
table.insert(matches, indices)
local currentReplacement = replacement
if #indices > 2 then
for i = 1, (#indices/2 - 1) do
currentReplacement = string.gsub(
currentReplacement,
"\\" .. i,
str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))
)
end
end
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
table.insert(replacements, { indices[1], #currentReplacement+indices[1] })
if indices[1] > 1 then
result = result ..
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
else
result = result .. currentReplacement
end
str = str:sub(indices[2])
end
until #indices == 0 or indices[1] == indices[2]
return result .. str, matches, replacements
end

View File

@ -259,10 +259,10 @@ function RootView:on_mouse_moved(x, y, dx, dy)
if self.dragged_divider then if self.dragged_divider then
local node = self.dragged_divider local node = self.dragged_divider
if node.type == "hsplit" then if node.type == "hsplit" then
x = common.clamp(x - node.position.x, 0, self.root_node.size.x * 0.95) x = common.clamp(x, 0, self.root_node.size.x * 0.95)
resize_child_node(node, "x", x, dx) resize_child_node(node, "x", x, dx)
elseif node.type == "vsplit" then elseif node.type == "vsplit" then
y = common.clamp(y - node.position.y, 0, self.root_node.size.y * 0.95) y = common.clamp(y, 0, self.root_node.size.y * 0.95)
resize_child_node(node, "y", y, dy) resize_child_node(node, "y", y, dy)
end end
node.divider = common.clamp(node.divider, 0.01, 0.99) node.divider = common.clamp(node.divider, 0.01, 0.99)
@ -334,9 +334,6 @@ function RootView:on_text_input(...)
core.active_view:on_text_input(...) core.active_view:on_text_input(...)
end end
function RootView:on_ime_text_editing(...)
core.active_view:on_ime_text_editing(...)
end
function RootView:on_focus_lost(...) function RootView:on_focus_lost(...)
-- We force a redraw so documents can redraw without the cursor. -- We force a redraw so documents can redraw without the cursor.

View File

@ -1,343 +0,0 @@
local core = require "core"
local common = require "core.common"
local config = require "core.config"
local style = require "core.style"
local Object = require "core.object"
---Scrollbar
---Use Scrollbar:set_size to set the bounding box of the view the scrollbar belongs to.
---Use Scrollbar:update to update the scrollbar animations.
---Use Scrollbar:draw to draw the scrollbar.
---Use Scrollbar:on_mouse_pressed, Scrollbar:on_mouse_released,
---Scrollbar:on_mouse_moved and Scrollbar:on_mouse_left to react to mouse movements;
---the scrollbar won't update automatically.
---Use Scrollbar:set_percent to set the scrollbar location externally.
---
---To manage all the orientations, the scrollbar changes the coordinates system
---accordingly. The "normal" coordinate system adapts the scrollbar coordinates
---as if it's always a vertical scrollbar, positioned at the end of the bounding box.
---@class core.scrollbar : core.object
local Scrollbar = Object:extend()
---@class ScrollbarOptions
---@field direction "v" | "h" @Vertical or Horizontal
---@field alignment "s" | "e" @Start or End (left to right, top to bottom)
---@field force_status "expanded" | "contracted" | false @Force the scrollbar status
---@field expanded_size number? @Override the default value specified by `style.expanded_scrollbar_size`
---@field contracted_size number? @Override the default value specified by `style.scrollbar_size`
---@param options ScrollbarOptions
function Scrollbar:new(options)
---Position information of the owner
self.rect = {
x = 0, y = 0, w = 0, h = 0,
---Scrollable size
scrollable = 0
}
self.normal_rect = {
across = 0,
along = 0,
across_size = 0,
along_size = 0,
scrollable = 0
}
---@type integer @Position in percent [0-1]
self.percent = 0
---@type boolean @Scrollbar dragging status
self.dragging = false
---@type integer @Private. Used to offset the start of the drag from the top of the thumb
self.drag_start_offset = 0
---What is currently being hovered. `thumb` implies` track`
self.hovering = { track = false, thumb = false }
---@type "v" | "h"@Vertical or Horizontal
self.direction = options.direction or "v"
---@type "s" | "e" @Start or End (left to right, top to bottom)
self.alignment = options.alignment or "e"
---@type number @Private. Used to keep track of animations
self.expand_percent = 0
---@type "expanded" | "contracted" | false @Force the scrollbar status
self.force_status = options.force_status
self:set_forced_status(options.force_status)
---@type number? @Override the default value specified by `style.expanded_scrollbar_size`
self.contracted_size = options.contracted_size
---@type number? @Override the default value specified by `style.scrollbar_size`
self.expanded_size = options.expanded_size
end
---Set the status the scrollbar is forced to keep
---@param status "expanded" | "contracted" | false @The status to force
function Scrollbar:set_forced_status(status)
self.force_status = status
if self.force_status == "expanded" then
self.expand_percent = 1
end
end
function Scrollbar:real_to_normal(x, y, w, h)
x, y, w, h = x or 0, y or 0, w or 0, h or 0
if self.direction == "v" then
if self.alignment == "s" then
x = (self.rect.x + self.rect.w) - x - w
end
return x, y, w, h
else
if self.alignment == "s" then
y = (self.rect.y + self.rect.h) - y - h
end
return y, x, h, w
end
end
function Scrollbar:normal_to_real(x, y, w, h)
x, y, w, h = x or 0, y or 0, w or 0, h or 0
if self.direction == "v" then
if self.alignment == "s" then
x = (self.rect.x + self.rect.w) - x - w
end
return x, y, w, h
else
if self.alignment == "s" then
x = (self.rect.y + self.rect.h) - x - w
end
return y, x, h, w
end
end
function Scrollbar:_get_thumb_rect_normal()
local nr = self.normal_rect
local sz = nr.scrollable
if sz == math.huge or sz <= nr.along_size
then
return 0, 0, 0, 0
end
local scrollbar_size = self.contracted_size or style.scrollbar_size
local expanded_scrollbar_size = self.expanded_size or style.expanded_scrollbar_size
local along_size = math.max(20, nr.along_size * nr.along_size / sz)
local across_size = scrollbar_size
across_size = across_size + (expanded_scrollbar_size - scrollbar_size) * self.expand_percent
return
nr.across + nr.across_size - across_size,
nr.along + self.percent * nr.scrollable * (nr.along_size - along_size) / (sz - nr.along_size),
across_size,
along_size
end
---Get the thumb rect (the part of the scrollbar that can be dragged)
---@return integer,integer,integer,integer @x, y, w, h
function Scrollbar:get_thumb_rect()
return self:normal_to_real(self:_get_thumb_rect_normal())
end
function Scrollbar:_get_track_rect_normal()
local nr = self.normal_rect
local sz = nr.scrollable
if sz <= nr.along_size or sz == math.huge then
return 0, 0, 0, 0
end
local scrollbar_size = self.contracted_size or style.scrollbar_size
local expanded_scrollbar_size = self.expanded_size or style.expanded_scrollbar_size
local across_size = scrollbar_size
across_size = across_size + (expanded_scrollbar_size - scrollbar_size) * self.expand_percent
return
nr.across + nr.across_size - across_size,
nr.along,
across_size,
nr.along_size
end
---Get the track rect (the "background" of the scrollbar)
---@return number,number,number,number @x, y, w, h
function Scrollbar:get_track_rect()
return self:normal_to_real(self:_get_track_rect_normal())
end
function Scrollbar:_overlaps_normal(x, y)
local sx, sy, sw, sh = self:_get_thumb_rect_normal()
local scrollbar_size = self.contracted_size or style.scrollbar_size
local result
if x >= sx - scrollbar_size * 3 and x <= sx + sw and y >= sy and y <= sy + sh then
result = "thumb"
else
sx, sy, sw, sh = self:_get_track_rect_normal()
if x >= sx - scrollbar_size * 3 and x <= sx + sw and y >= sy and y <= sy + sh then
result = "track"
end
end
return result
end
---Get what part of the scrollbar the coordinates overlap
---@return "thumb"|"track"|nil
function Scrollbar:overlaps(x, y)
x, y = self:real_to_normal(x, y)
return self:_overlaps_normal(x, y)
end
function Scrollbar:_on_mouse_pressed_normal(button, x, y, clicks)
local overlaps = self:_overlaps_normal(x, y)
if overlaps then
local _, along, _, along_size = self:_get_thumb_rect_normal()
self.dragging = true
if overlaps == "thumb" then
self.drag_start_offset = along - y
return true
elseif overlaps == "track" then
local nr = self.normal_rect
self.drag_start_offset = - along_size / 2
return common.clamp((y - nr.along - along_size / 2) / (nr.along_size - along_size), 0, 1)
end
end
end
---Updates the scrollbar with mouse pressed info.
---Won't update the scrollbar position automatically.
---Use Scrollbar:set_percent to update it.
---
---This sets the dragging status if needed.
---
---Returns a falsy value if the event happened outside the scrollbar.
---Returns `true` if the thumb was pressed.
---If the track was pressed this returns a value between 0 and 1
---representing the percent of the position.
---@return boolean|number
function Scrollbar:on_mouse_pressed(button, x, y, clicks)
if button ~= "left" then return end
x, y = self:real_to_normal(x, y)
return self:_on_mouse_pressed_normal(button, x, y, clicks)
end
---Updates the scrollbar hover status.
---This gets called by other functions and shouldn't be called manually
function Scrollbar:_update_hover_status_normal(x, y)
local overlaps = self:_overlaps_normal(x, y)
self.hovering.thumb = overlaps == "thumb"
self.hovering.track = self.hovering.thumb or overlaps == "track"
return self.hovering.track or self.hovering.thumb
end
function Scrollbar:_on_mouse_released_normal(button, x, y)
self.dragging = false
return self:_update_hover_status_normal(x, y)
end
---Updates the scrollbar dragging status
function Scrollbar:on_mouse_released(button, x, y)
if button ~= "left" then return end
x, y = self:real_to_normal(x, y)
return self:_on_mouse_released_normal(button, x, y)
end
function Scrollbar:_on_mouse_moved_normal(x, y, dx, dy)
if self.dragging then
local nr = self.normal_rect
return common.clamp((y - nr.along + self.drag_start_offset) / nr.along_size, 0, 1)
end
return self:_update_hover_status_normal(x, y)
end
---Updates the scrollbar with mouse moved info.
---Won't update the scrollbar position automatically.
---Use Scrollbar:set_percent to update it.
---
---This updates the hovering status.
---
---Returns a falsy value if the event happened outside the scrollbar.
---Returns `true` if the scrollbar is hovered.
---If the scrollbar was being dragged, this returns a value between 0 and 1
---representing the percent of the position.
---@return boolean|number
function Scrollbar:on_mouse_moved(x, y, dx, dy)
x, y = self:real_to_normal(x, y)
dx, dy = self:real_to_normal(dx, dy) -- TODO: do we need this? (is this even correct?)
return self:_on_mouse_moved_normal(x, y, dx, dy)
end
---Updates the scrollbar hovering status
function Scrollbar:on_mouse_left()
self.hovering.track, self.hovering.thumb = false, false
end
---Updates the bounding box of the view the scrollbar belongs to.
---@param x number
---@param y number
---@param w number
---@param h number
---@param scrollable number @size of the scrollable area
function Scrollbar:set_size(x, y, w, h, scrollable)
self.rect.x, self.rect.y, self.rect.w, self.rect.h = x, y, w, h
self.rect.scrollable = scrollable
local nr = self.normal_rect
nr.across, nr.along, nr.across_size, nr.along_size = self:real_to_normal(x, y, w, h)
nr.scrollable = scrollable
end
---Updates the scrollbar location
---@param percent number @number between 0 and 1 representing the position of the middle part of the thumb
function Scrollbar:set_percent(percent)
self.percent = percent
end
---Updates the scrollbar animations
function Scrollbar:update()
-- TODO: move the animation code to its own class
if not self.force_status then
local dest = (self.hovering.track or self.dragging) and 1 or 0
local diff = math.abs(self.expand_percent - dest)
if not config.transitions or diff < 0.05 or config.disabled_transitions["scroll"] then
self.expand_percent = dest
else
local rate = 0.3
if config.fps ~= 60 or config.animation_rate ~= 1 then
local dt = 60 / config.fps
rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt)
end
self.expand_percent = common.lerp(self.expand_percent, dest, rate)
end
if diff > 1e-8 then
core.redraw = true
end
elseif self.force_status == "expanded" then
self.expand_percent = 1
elseif self.force_status == "contracted" then
self.expand_percent = 0
end
end
---Draw the scrollbar track
function Scrollbar:draw_track()
if not (self.hovering.track or self.dragging)
and self.expand_percent == 0 then
return
end
local color = { table.unpack(style.scrollbar_track) }
color[4] = color[4] * self.expand_percent
local x, y, w, h = self:get_track_rect()
renderer.draw_rect(x, y, w, h, color)
end
---Draw the scrollbar thumb
function Scrollbar:draw_thumb()
local highlight = self.hovering.thumb or self.dragging
local color = highlight and style.scrollbar2 or style.scrollbar
local x, y, w, h = self:get_thumb_rect()
renderer.draw_rect(x, y, w, h, color)
end
---Draw both the scrollbar track and thumb
function Scrollbar:draw()
self:draw_track()
self:draw_thumb()
end
return Scrollbar

View File

@ -1,21 +1,20 @@
-- 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.4r1" VERSION = "2.1.0r1"
MOD_VERSION = "3" MOD_VERSION = "3"
SCALE = tonumber(os.getenv("LITE_SCALE") or os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or 1 SCALE = tonumber(os.getenv("LITE_SCALE") or os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or SCALE
PATHSEP = package.config:sub(1, 1) PATHSEP = package.config:sub(1, 1)
EXEDIR = EXEFILE:match("^(.+)[/\\][^/\\]+$") EXEDIR = EXEFILE:match("^(.+)[/\\][^/\\]+$")
if MACOS_RESOURCES then if MACOS_RESOURCES then
DATADIR = MACOS_RESOURCES DATADIR = MACOS_RESOURCES
else else
local prefix = os.getenv('LITE_PREFIX') or EXEDIR:match("^(.+)[/\\]bin$") local prefix = EXEDIR:match("^(.+)[/\\]bin$")
DATADIR = prefix and (prefix .. PATHSEP .. 'share' .. PATHSEP .. 'lite-xl') or (EXEDIR .. PATHSEP .. 'data') DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
end end
USERDIR = (system.get_file_info(EXEDIR .. PATHSEP .. 'user') and (EXEDIR .. PATHSEP .. 'user')) USERDIR = (system.get_file_info(EXEDIR .. '/user') and (EXEDIR .. '/user'))
or os.getenv("LITE_USERDIR") or ((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") .. PATHSEP .. "lite-xl")) or (HOME and (HOME .. '/.config/lite-xl'))
or (HOME and (HOME .. PATHSEP .. '.config' .. PATHSEP .. 'lite-xl'))
package.path = DATADIR .. '/?.lua;' package.path = DATADIR .. '/?.lua;'
package.path = DATADIR .. '/?/init.lua;' .. package.path package.path = DATADIR .. '/?/init.lua;' .. package.path
@ -35,8 +34,8 @@ package.cpath =
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, err = package.searchpath(modname, package.cpath) local path = package.searchpath(modname, package.cpath)
if not path then return err end if not path then return nil end
return system.load_native_plugin, path return system.load_native_plugin, path
end } end }
@ -55,3 +54,4 @@ local appimage_owd = os.getenv("OWD")
if os.getenv("APPIMAGE") and appimage_owd then if os.getenv("APPIMAGE") and appimage_owd then
system.chdir(appimage_owd) system.chdir(appimage_owd)
end end

View File

@ -11,27 +11,26 @@ local Object = require "core.object"
---@alias core.statusview.styledtext table<integer, renderer.font|renderer.color|string> ---@alias core.statusview.styledtext table<integer, renderer.font|renderer.color|string>
---@alias core.statusview.position '"left"' | '"right"'
---A status bar implementation for lite, check core.status_view. ---A status bar implementation for lite, check core.status_view.
---@class core.statusview : core.view ---@class core.statusview : core.view
---@field super core.view ---@field public super core.view
---@field items core.statusview.item[] ---@field private items core.statusview.item[]
---@field active_items core.statusview.item[] ---@field private active_items core.statusview.item[]
---@field hovered_item core.statusview.item ---@field private hovered_item core.statusview.item
---@field message_timeout number ---@field private message_timeout number
---@field message core.statusview.styledtext ---@field private message core.statusview.styledtext
---@field tooltip_mode boolean ---@field private tooltip_mode boolean
---@field tooltip core.statusview.styledtext ---@field private tooltip core.statusview.styledtext
---@field left_width number ---@field private left_width number
---@field right_width number ---@field private right_width number
---@field r_left_width number ---@field private r_left_width number
---@field r_right_width number ---@field private r_right_width number
---@field left_xoffset number ---@field private left_xoffset number
---@field right_xoffset number ---@field private right_xoffset number
---@field dragged_panel '""' | core.statusview.position ---@field private dragged_panel '"left"' | '"right"'
---@field hovered_panel '""' | core.statusview.position ---@field private hovered_panel '"left"' | '"right"'
---@field hide_messages boolean ---@field private hide_messages boolean
local StatusView = View:extend() local StatusView = View:extend()
---Space separator ---Space separator
@ -43,73 +42,81 @@ StatusView.separator = " "
StatusView.separator2 = " | " StatusView.separator2 = " | "
---@alias core.statusview.item.separator ---@alias core.statusview.item.separator
---|>`StatusView.separator` ---|>'core.statusview.separator' # Space separator
---| `StatusView.separator2` ---| 'core.statusview.separator2' # Pipe separator
---@alias core.statusview.item.predicate fun():boolean ---@alias core.statusview.item.predicate fun():boolean
---@alias core.statusview.item.onclick fun(button: string, x: number, y: number) ---@alias core.statusview.item.onclick fun(button: string, x: number, y: number)
---@alias core.statusview.item.get_item fun(self: core.statusview.item):core.statusview.styledtext?,core.statusview.styledtext? ---@alias core.statusview.item.get_item fun():core.statusview.styledtext,core.statusview.styledtext
---@alias core.statusview.item.ondraw fun(x, y, h, hovered: boolean, calc_only?: boolean):number ---@alias core.statusview.item.ondraw fun(x, y, h, hovered: boolean, calc_only?: boolean):number
---@class core.statusview.item : core.object ---@class core.statusview.item : core.object
---@field name string ---@field name string
---@field predicate core.statusview.item.predicate ---@field predicate core.statusview.item.predicate
---@field alignment core.statusview.item.alignment ---@field alignment core.statusview.item.alignment
---@field tooltip string ---@field tooltip string | nil
---@field command string | nil @Command to perform when the item is clicked. ---@field command string | nil @Command to perform when the item is clicked.
---Function called when item is clicked and no command is set. ---@field on_click core.statusview.item.onclick | nil @Function called when item is clicked and no command is set.
---@field on_click core.statusview.item.onclick | nil ---@field on_draw core.statusview.item.ondraw | nil @Custom drawing that when passed calc true should return the needed width for drawing and when false should draw.
---Custom drawing that when passed calc true should return the needed width for
---drawing and when false should draw.
---@field on_draw core.statusview.item.ondraw | nil
---@field background_color renderer.color | nil ---@field background_color renderer.color | nil
---@field background_color_hover renderer.color | nil ---@field background_color_hover renderer.color | nil
---@field visible boolean ---@field visible boolean
---@field separator core.statusview.item.separator ---@field separator core.statusview.item.separator
---@field active boolean ---@field private active boolean
---@field x number ---@field private x number
---@field w number ---@field private w number
---@field cached_item core.statusview.styledtext ---@field private cached_item core.statusview.styledtext
local StatusViewItem = Object:extend() local StatusViewItem = Object:extend()
---Available StatusViewItem options. ---Available StatusViewItem options.
---@class core.statusview.item.options : table ---@class core.statusview.item.options : table
---A condition to evaluate if the item should be displayed. If a string
---is given it is treated as a require import that should return a valid object
---which is checked against the current active view, the sames applies if a
---table is given. A function that returns a boolean can be used instead to
---perform a custom evaluation, setting to nil means always evaluates to true.
---@field predicate string | table | core.statusview.item.predicate ---@field predicate string | table | core.statusview.item.predicate
---A unique name to identify the item on the status bar. ---@field name string
---@field name string @A unique name to identify the item on the status bar.
---@field alignment core.statusview.item.alignment ---@field alignment core.statusview.item.alignment
---A function that should return a core.statusview.styledtext element,
---returning an empty table is allowed.
---@field get_item core.statusview.item.get_item ---@field get_item core.statusview.item.get_item
---The name of a valid registered command or a callback function to execute ---@field command? string | core.statusview.item.onclick
---when the item is clicked.
---@field command string | core.statusview.item.onclick | nil
---The position in which to insert the given item on the internal table,
---a value of -1 inserts the item at the end which is the default. A value
---of 1 will insert the item at the beggining.
---@field position? integer ---@field position? integer
---@field tooltip? string @Text displayed when mouse hovers the item. ---@field tooltip? string
---@field visible boolean @Flag to show or hide the item
---The type of separator rendered to the right of the item if another item
---follows it.
---@field separator? core.statusview.item.separator ---@field separator? core.statusview.item.separator
local StatusViewItemOptions = {
---A condition to evaluate if the item should be displayed. If a string
---is given it is treated as a require import that should return a valid object
---which is checked against the current active view, the sames applies if a
---table is given. A function that returns a boolean can be used instead to
---perform a custom evaluation, setting to nil means always evaluates to true.
predicate = nil,
---A unique name to identify the item on the status bar.
name = nil,
alignment = nil,
---A function that should return a core.statusview.styledtext element,
---returning empty table is allowed.
get_item = nil,
---The name of a valid registered command or a callback function to execute
---when the item is clicked.
command = nil,
---The position in which to insert the given item on the internal table,
---a value of -1 inserts the item at the end which is the default. A value
---of 1 will insert the item at the beggining.
position = nil,
---Displayed when mouse hovers the item
tooltip = nil,
separator = nil,
}
StatusViewItem.options = StatusViewItemOptions
---Flag to tell the item should me aligned on left side of status bar. ---Flag to tell the item should me aligned on left side of status bar.
---@type integer ---@type number
StatusViewItem.LEFT = 1 StatusViewItem.LEFT = 1
---Flag to tell the item should me aligned on right side of status bar. ---Flag to tell the item should me aligned on right side of status bar.
---@type integer ---@type number
StatusViewItem.RIGHT = 2 StatusViewItem.RIGHT = 2
---@alias core.statusview.item.alignment ---@alias core.statusview.item.alignment
---|>`StatusView.Item.LEFT` ---|>'core.statusview.item.LEFT'
---| `StatusView.Item.RIGHT` ---| 'core.statusview.item.RIGHT'
---Constructor ---Constructor
---@param options core.statusview.item.options ---@param options core.statusview.item.options
@ -203,7 +210,7 @@ function StatusView:register_docview_items()
return { return {
dv.doc:is_dirty() and style.accent or style.text, style.icon_font, "f", dv.doc:is_dirty() and style.accent or style.text, style.icon_font, "f",
style.dim, style.font, self.separator2, style.text, style.dim, style.font, self.separator2, style.text,
dv.doc.filename and style.text or style.dim, common.home_encode(dv.doc:get_name()) dv.doc.filename and style.text or style.dim, dv.doc:get_name()
} }
end end
}) })
@ -463,7 +470,7 @@ end
---Hides the given items from the status view or all if no names given. ---Hides the given items from the status view or all if no names given.
---@param names? table<integer, string> | string ---@param names table<integer, string> | string | nil
function StatusView:hide_items(names) function StatusView:hide_items(names)
if type(names) == "string" then if type(names) == "string" then
names = {names} names = {names}
@ -482,7 +489,7 @@ end
---Shows the given items from the status view or all if no names given. ---Shows the given items from the status view or all if no names given.
---@param names? table<integer, string> | string ---@param names table<integer, string> | string | nil
function StatusView:show_items(names) function StatusView:show_items(names)
if type(names) == "string" then if type(names) == "string" then
names = {names} names = {names}
@ -600,8 +607,8 @@ function StatusView:draw_item_tooltip(item)
local x = self.pointer.x - (w / 2) - (style.padding.x * 2) local x = self.pointer.x - (w / 2) - (style.padding.x * 2)
if x < 0 then x = 0 end if x < 0 then x = 0 end
if (x + w + (style.padding.x * 3)) > self.size.x then if x + w + (style.padding.x * 2) > self.size.x then
x = self.size.x - w - (style.padding.x * 3) x = self.size.x - w - (style.padding.x * 2)
end end
renderer.draw_rect( renderer.draw_rect(
@ -776,7 +783,7 @@ function StatusView:update_active_items()
item.cached_item = {} item.cached_item = {}
if item.visible and item:predicate() then if item.visible and item:predicate() then
local styled_text = type(item.get_item) == "function" local styled_text = type(item.get_item) == "function"
and item.get_item(item) or item.get_item and item.get_item(self) or item.get_item
if #styled_text > 0 then if #styled_text > 0 then
remove_spacing(self, styled_text) remove_spacing(self, styled_text)
@ -874,7 +881,7 @@ end
---Drag the given panel if possible. ---Drag the given panel if possible.
---@param panel core.statusview.position ---@param panel '"left"' | '"right"'
---@param dx number ---@param dx number
function StatusView:drag_panel(panel, dx) function StatusView:drag_panel(panel, dx)
if panel == "left" and self.r_left_width > self.left_width then if panel == "left" and self.r_left_width > self.left_width then
@ -908,8 +915,10 @@ end
function StatusView:get_hovered_panel(x, y) function StatusView:get_hovered_panel(x, y)
if y >= self.position.y and x <= self.left_width + style.padding.x then if y >= self.position.y and x <= self.left_width + style.padding.x then
return "left" return "left"
else
return "right"
end end
return "right" return ""
end end
@ -1044,13 +1053,9 @@ function StatusView:on_mouse_released(button, x, y)
end end
function StatusView:on_mouse_wheel(y, x) function StatusView:on_mouse_wheel(y)
if not self.visible or self.hovered_panel == "" then return end if not self.visible then return end
if x ~= 0 then self:drag_panel(self.hovered_panel, y * self.left_width / 10)
self:drag_panel(self.hovered_panel, x * self.left_width / 10)
else
self:drag_panel(self.hovered_panel, y * self.left_width / 10)
end
end end

View File

@ -40,3 +40,4 @@ style.syntax_fonts = {}
style.log = {} style.log = {}
return style return style

View File

@ -30,21 +30,16 @@ end
local function find(string, field) local function find(string, field)
local best_match = 0
local best_syntax
for i = #syntax.items, 1, -1 do for i = #syntax.items, 1, -1 do
local t = syntax.items[i] local t = syntax.items[i]
local s, e = common.match_pattern(string, t[field] or {}) if common.match_pattern(string, t[field] or {}) then
if s and e - s > best_match then return t
best_match = e - s
best_syntax = t
end end
end end
return best_syntax
end end
function syntax.get(filename, header) function syntax.get(filename, header)
return (filename and find(filename, "files")) return find(filename, "files")
or (header and find(header, "headers")) or (header and find(header, "headers"))
or plain_text_syntax or plain_text_syntax
end end

View File

@ -3,14 +3,6 @@ local common = require "core.common"
local style = require "core.style" local style = require "core.style"
local View = require "core.view" local View = require "core.view"
local icon_colors = {
bg = { common.color "#2e2e32ff" },
color6 = { common.color "#e1e1e6ff" },
color7 = { common.color "#ffa94dff" },
color8 = { common.color "#93ddfaff" },
color9 = { common.color "#f7c95cff" }
};
local restore_command = { local restore_command = {
symbol = "w", action = function() system.set_window_mode("normal") end symbol = "w", action = function() system.set_window_mode("normal") end
} }
@ -51,10 +43,6 @@ function TitleView:configure_hit_test(borderless)
end end
end end
function TitleView:on_scale_change()
self:configure_hit_test(self.visible)
end
function TitleView:update() function TitleView:update()
self.size.y = self.visible and title_view_height() or 0 self.size.y = self.visible and title_view_height() or 0
title_commands[2] = core.window_mode == "maximized" and restore_command or maximize_command title_commands[2] = core.window_mode == "maximized" and restore_command or maximize_command
@ -67,11 +55,7 @@ function TitleView:draw_window_title()
local ox, oy = self:get_content_offset() local ox, oy = self:get_content_offset()
local color = style.text local color = style.text
local x, y = ox + style.padding.x, oy + style.padding.y local x, y = ox + style.padding.x, oy + style.padding.y
common.draw_text(style.icon_font, icon_colors.bg, "5", nil, x, y, 0, h) x = common.draw_text(style.icon_font, color, "M ", nil, x, y, 0, h)
common.draw_text(style.icon_font, icon_colors.color6, "6", nil, x, y, 0, h)
common.draw_text(style.icon_font, icon_colors.color7, "7", nil, x, y, 0, h)
common.draw_text(style.icon_font, icon_colors.color8, "8", nil, x, y, 0, h)
x = common.draw_text(style.icon_font, icon_colors.color9, "9 ", nil, x, y, 0, h)
local title = core.compose_window_title(core.window_title) local title = core.compose_window_title(core.window_title)
common.draw_text(style.font, color, title, nil, x, y, 0, h) common.draw_text(style.font, color, title, nil, x, y, 0, h)
end end

View File

@ -1,16 +1,15 @@
local core = require "core" local core = require "core"
local syntax = require "core.syntax" local syntax = require "core.syntax"
local config = require "core.config" local common = require "core.common"
local tokenizer = {} local tokenizer = {}
local bad_patterns = {} local bad_patterns = {}
local function push_token(t, type, text) local function push_token(t, type, text)
if not text or #text == 0 then return end
type = type or "normal" 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*$") and type ~= "incomplete")) then if prev_type and (prev_type == type or prev_text:ufind("^%s*$")) then
t[#t-1] = type t[#t-1] = type
t[#t] = prev_text .. text t[#t] = prev_text .. text
else else
@ -52,37 +51,31 @@ local function push_tokens(t, syn, pattern, full_text, find_results)
end end
end end
-- State is a string of bytes, where the count of bytes represents the depth
-- of the subsyntax we are currently in. Each individual byte represents the
-- index of the pattern for the current subsyntax in relation to its parent
-- syntax. Using a string of bytes allows us to have as many subsyntaxes as
-- bytes can be stored on a string while keeping some level of performance in
-- comparison to a Lua table. The only limitation is that a syntax would not
-- be able to contain more than 255 patterns.
--
-- Lets say a state contains 2 bytes byte #1 with value `3` and byte #2 with
-- a value of `5`. This would mean that on the parent syntax at index `3` a
-- pattern subsyntax that matched current text was found, then inside that
-- subsyntax another subsyntax pattern at index `5` that matched current text
-- was also found.
-- Calling `push_subsyntax` appends the current subsyntax pattern index to the -- State is a 32-bit number that is four separate bytes, illustrating how many
-- state and increases the stack depth. Calling `pop_subsyntax` clears the -- differnet delimiters we have open, and which subsyntaxes we have active.
-- last appended subsyntax and decreases the stack. -- At most, there are 3 subsyntaxes active at the same time. Beyond that,
-- does not support further highlighting.
-- You can think of it as a maximum 4 integer (0-255) stack. It always has
-- 1 integer in it. Calling `push_subsyntax` increases the stack depth. Calling
-- `pop_subsyntax` decreases it. The integers represent the index of a pattern
-- that we're following in the syntax. The top of the stack can be any valid
-- pattern index, any integer lower in the stack must represent a pattern that
-- specifies a subsyntax.
-- If you do not have subsyntaxes in your syntax, the three most
-- singificant numbers will always be 0, the stack will only ever be length 1
-- and the state variable will only ever range from 0-255.
local function retrieve_syntax_state(incoming_syntax, state) local function retrieve_syntax_state(incoming_syntax, state)
local current_syntax, subsyntax_info, current_pattern_idx, current_level = local current_syntax, subsyntax_info, current_pattern_idx, current_level =
incoming_syntax, nil, state:byte(1) or 0, 1 incoming_syntax, nil, state, 0
if if state > 0 and (state > 255 or current_syntax.patterns[state].syntax) then
current_pattern_idx > 0 -- If we have higher bits, then decode them one at a time, and find which
and
current_syntax.patterns[current_pattern_idx]
then
-- If the state is not empty we iterate over each byte, and find which
-- syntax we're using. Rather than walking the bytes, and calling into -- syntax we're using. Rather than walking the bytes, and calling into
-- `syntax` each time, we could probably cache this in a single table. -- `syntax` each time, we could probably cache this in a single table.
for i = 1, #state do for i = 0, 2 do
local target = state:byte(i) local target = bit32.extract(state, i*8, 8)
if target ~= 0 then if target ~= 0 then
if current_syntax.patterns[target].syntax then if current_syntax.patterns[target].syntax then
subsyntax_info = current_syntax.patterns[target] subsyntax_info = current_syntax.patterns[target]
@ -102,61 +95,30 @@ 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
---Return the list of syntaxes used in the specified state.
---@param base_syntax table @The initial base syntax (the syntax of the file)
---@param state string @The state of the tokenizer to extract from
---@return table @Array of syntaxes starting from the innermost one
function tokenizer.extract_subsyntaxes(base_syntax, state)
local current_syntax
local t = {}
repeat
current_syntax = retrieve_syntax_state(base_syntax, state)
table.insert(t, current_syntax)
state = string.sub(state, 2)
until #state == 0
return t
end
local function report_bad_pattern(log_fn, syntax, pattern_idx, msg, ...) local function report_bad_pattern(log_fn, syntax, pattern_idx, msg, ...)
if not bad_patterns[syntax] then if not bad_patterns[syntax] then
bad_patterns[syntax] = { } bad_patterns[syntax] = { }
end end
if bad_patterns[syntax][pattern_idx] then return end if bad_patterns[syntax][pattern_idx] then return end
bad_patterns[syntax][pattern_idx] = true bad_patterns[syntax][pattern_idx] = true
log_fn("Malformed pattern #%d <%s> in %s language plugin.\n" .. msg, log_fn("Malformed pattern #%d in %s language plugin. " .. msg,
pattern_idx, pattern_idx, syntax.name or "unnamed", ...)
syntax.patterns[pattern_idx].pattern or syntax.patterns[pattern_idx].regex,
syntax.name or "unnamed", ...)
end end
---@param incoming_syntax table ---@param incoming_syntax table
---@param text string ---@param text string
---@param state string ---@param state integer
function tokenizer.tokenize(incoming_syntax, text, state, resume) function tokenizer.tokenize(incoming_syntax, text, state)
local res local res = {}
local i = 1 local i = 1
if #incoming_syntax.patterns == 0 then if #incoming_syntax.patterns == 0 then
return { "normal", text } return { "normal", text }
end end
state = state or string.char(0) state = state or 0
if resume then
res = resume.res
-- Remove "incomplete" tokens
while res[#res-1] == "incomplete" do
table.remove(res, #res)
table.remove(res, #res)
end
i = resume.i
state = resume.state
end
res = res or {}
-- incoming_syntax : the parent syntax of the file. -- incoming_syntax : the parent syntax of the file.
-- state : a string of bytes representing syntax state (see above) -- state : a 32-bit number representing syntax state (see above)
-- current_syntax : the syntax we're currently in. -- 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.
@ -168,18 +130,7 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
-- 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
local state_len = #state state = bit32.replace(state, pattern_idx, current_level*8, 8)
if current_level > state_len then
state = state .. string.char(pattern_idx)
elseif state_len == 1 then
state = string.char(pattern_idx)
else
state = ("%s%s%s"):format(
state:sub(1,current_level-1),
string.char(pattern_idx),
state:sub(current_level+1)
)
end
end end
@ -193,8 +144,8 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
end end
local function pop_subsyntax() local function pop_subsyntax()
set_subsyntax_pattern_idx(0)
current_level = current_level - 1 current_level = current_level - 1
state = string.sub(state, 1, current_level)
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)
@ -213,11 +164,9 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
-- Remove '^' from the beginning of the pattern -- Remove '^' from the beginning of the pattern
if type(target) == "table" then if type(target) == "table" then
target[p_idx] = code:usub(2) target[p_idx] = code:usub(2)
code = target[p_idx]
else else
p.pattern = p.pattern and code:usub(2) p.pattern = p.pattern and code:usub(2)
p.regex = p.regex and code:usub(2) p.regex = p.regex and code:usub(2)
code = p.pattern or p.regex
end end
end end
end end
@ -234,12 +183,23 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
return return
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:ufind((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) }
or { regex.find(code, text, text:ucharpos(next), (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) } or { regex.match(code, text, text:ucharpos(next), (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) }
if p.regex and #res > 0 then -- set correct utf8 len for regex result if p.regex and #res > 0 then -- set correct utf8 len for regex result
local char_pos_1 = res[1] > next and string.ulen(text:sub(1, res[1])) or next local char_pos_1 = string.ulen(text:sub(1, res[1]))
local char_pos_2 = string.ulen(text:sub(1, res[2])) local char_pos_2 = char_pos_1 + string.ulen(text:sub(res[1], res[2])) - 1
for i=3,#res do -- `regex.match` returns group results as a series of `begin, end`
res[i] = string.ulen(text:sub(1, res[i] - 1)) + 1 -- 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 end
res[1] = char_pos_1 res[1] = char_pos_1
res[2] = char_pos_2 res[2] = char_pos_2
@ -265,124 +225,99 @@ function tokenizer.tokenize(incoming_syntax, text, state, resume)
end end
local text_len = text:ulen() local text_len = text:ulen()
local start_time = system.get_time() if text_len ~= nil then
local starting_i = i while i <= text_len do
while text_len ~= nil and i <= text_len do -- continue trying to match the end pattern of a pair if we have a state set
-- Every 200 chars, check if we're out of time if current_pattern_idx > 0 then
if i - starting_i > 200 then local p = current_syntax.patterns[current_pattern_idx]
starting_i = i local s, e = find_text(text, p, i, false, true)
if system.get_time() - start_time > 0.5 / config.fps then
-- We're out of time
push_token(res, "incomplete", string.usub(text, i))
return res, string.char(0), {
res = res,
i = i,
state = state
}
end
end
-- 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 cont = true local cont = true
-- If we're in subsyntax mode, always check to see if we end our syntax -- 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 -- first, before the found delimeter, as ending the subsyntax takes
-- precedence over ending the delimiter in the subsyntax. -- precedence over ending the delimiter in the subsyntax.
if subsyntax_info then if subsyntax_info then
local ss, se = find_text(text, subsyntax_info, i, false, true) local ss, se = find_text(text, subsyntax_info, i, false, true)
-- If we find that we end the subsyntax before the -- If we find that we end the subsyntax before the
-- delimiter, push the token, and signal we shouldn't -- delimiter, push the token, and signal we shouldn't
-- treat the bit after as a token to be normally parsed -- treat the bit after as a token to be normally parsed
-- (as it's the syntax delimiter). -- (as it's the syntax delimiter).
if ss and (s == nil or ss < s) then if ss and (s == nil or ss < s) then
push_token(res, p.type, text:usub(i, ss - 1)) push_token(res, p.type, text:usub(i, ss - 1))
i = ss i = ss
cont = false 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
end end
-- If we don't have any concerns about syntax delimiters, -- General end of syntax check. Applies in the case where
-- continue on as normal. -- we're ending early in the middle of a delimiter, or
if cont then -- just normally, upon finding a token.
if subsyntax_info then
local s, e = find_text(text, subsyntax_info, i, true, true)
if s then if s then
push_token(res, p.type, text:usub(i, e)) push_token(res, subsyntax_info.type, text:usub(i, e))
set_subsyntax_pattern_idx(0) -- On finding unescaped delimiter, pop it.
pop_subsyntax()
i = e + 1 i = e + 1
else end
push_token(res, p.type, text:usub(i)) 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
local type_is_table = type(p.type) == "table"
local n_types = type_is_table and #p.type or 1
if #find_results == 2 and type_is_table then
report_bad_pattern(core.warn, current_syntax, n,
"Token type is a table, but a string was expected.")
p.type = p.type[1]
elseif #find_results - 1 > n_types then
report_bad_pattern(core.error, current_syntax, n,
"Not enough token types: got %d needed %d.", n_types, #find_results - 1)
elseif #find_results - 1 < n_types then
report_bad_pattern(core.warn, current_syntax, n,
"Too many token types: got %d needed %d.", n_types, #find_results - 1)
end
-- matched pattern; make and add tokens
push_tokens(res, current_syntax, p, text, find_results)
-- update state if this was a start|end pattern pair
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
-- General end of syntax check. Applies in the case where -- consume character if we didn't match
-- we're ending early in the middle of a delimiter, or if not matched then
-- just normally, upon finding a token. push_token(res, "normal", text:usub(i, i))
while subsyntax_info do i = i + 1
local s, e = find_text(text, subsyntax_info, i, true, true)
if s then
push_token(res, subsyntax_info.type, text:usub(i, e))
-- On finding unescaped delimiter, pop it.
pop_subsyntax()
i = e + 1
else
break
end end
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
-- Check for patterns successfully matching nothing
if find_results[1] > find_results[2] then
report_bad_pattern(core.warn, current_syntax, n,
"Pattern successfully matched, but nothing was captured.")
goto continue
end
-- Check for patterns with mismatching number of `types`
local type_is_table = type(p.type) == "table"
local n_types = type_is_table and #p.type or 1
if #find_results == 2 and type_is_table then
report_bad_pattern(core.warn, current_syntax, n,
"Token type is a table, but a string was expected.")
p.type = p.type[1]
elseif #find_results - 1 > n_types then
report_bad_pattern(core.error, current_syntax, n,
"Not enough token types: got %d needed %d.", n_types, #find_results - 1)
elseif #find_results - 1 < n_types then
report_bad_pattern(core.warn, current_syntax, n,
"Too many token types: got %d needed %d.", n_types, #find_results - 1)
end
-- matched pattern; make and add tokens
push_tokens(res, current_syntax, p, text, find_results)
-- update state if this was a start|end pattern pair
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
::continue::
end
end
-- consume character if we didn't match
if not matched then
push_token(res, "normal", text:usub(i, i))
i = i + 1
end
end end
return res, state return res, state
end end

View File

@ -1,8 +1,8 @@
local core = require "core" local core = require "core"
local config = require "core.config" local config = require "core.config"
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"
local Scrollbar = require "core.scrollbar"
---@class core.view.position ---@class core.view.position
---@field x number ---@field x number
@ -28,6 +28,10 @@ local Scrollbar = require "core.scrollbar"
---@field w core.view.thumbtrackwidth ---@field w core.view.thumbtrackwidth
---@field h core.view.thumbtrack ---@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.cursor "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'"
---@alias core.view.mousebutton "'left'" | "'right'" ---@alias core.view.mousebutton "'left'" | "'right'"
@ -43,9 +47,8 @@ local Scrollbar = require "core.scrollbar"
---@field scroll core.view.scroll ---@field scroll core.view.scroll
---@field cursor core.view.cursor ---@field cursor core.view.cursor
---@field scrollable boolean ---@field scrollable boolean
---@field v_scrollbar core.scrollbar ---@field scrollbar core.view.scrollbar
---@field h_scrollbar core.scrollbar ---@field scrollbar_alpha core.view.increment
---@field current_scale number
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
@ -59,9 +62,13 @@ 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.v_scrollbar = Scrollbar({direction = "v", alignment = "e"}) self.scrollbar = {
self.h_scrollbar = Scrollbar({direction = "h", alignment = "e"}) x = { thumb = 0, track = 0 },
self.current_scale = SCALE 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, name)
@ -102,9 +109,47 @@ function View:get_scrollable_size()
return math.huge return math.huge
end end
---@return number
function View:get_h_scrollable_size() ---@return number x
return 0 ---@return number y
---@return number width
---@return number height
function View:get_scrollbar_track_rect()
local sz = self:get_scrollable_size()
if sz <= self.size.y or sz == math.huge then
return 0, 0, 0, 0
end
local width = style.scrollbar_size
if self.hovered_scrollbar_track or self.dragging_scrollbar then
width = style.expanded_scrollbar_size
end
return
self.position.x + self.size.x - width,
self.position.y,
width,
self.size.y
end
---@return number x
---@return number y
---@return number width
---@return number height
function View:get_scrollbar_rect()
local sz = self:get_scrollable_size()
if sz <= self.size.y or sz == math.huge then
return 0, 0, 0, 0
end
local h = math.max(20, self.size.y * self.size.y / sz)
local width = style.scrollbar_size
if self.hovered_scrollbar_track or self.dragging_scrollbar then
width = style.expanded_scrollbar_size
end
return
self.position.x + self.size.x - width,
self.position.y + self.scroll.y * (self.size.y - h) / (sz - self.size.y),
width,
h
end end
@ -112,19 +157,16 @@ end
---@param y number ---@param y number
---@return boolean ---@return boolean
function View:scrollbar_overlaps_point(x, y) function View:scrollbar_overlaps_point(x, y)
return not (not (self.v_scrollbar:overlaps(x, y) or self.h_scrollbar:overlaps(x, y))) local sx, sy, sw, sh = self:get_scrollbar_rect()
return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh
end end
---@param x number
---@param y number
---@return boolean ---@return boolean
function View:scrollbar_dragging() function View:scrollbar_track_overlaps_point(x, y)
return self.v_scrollbar.dragging or self.h_scrollbar.dragging local sx, sy, sw, sh = self:get_scrollbar_track_rect()
end return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh
---@return boolean
function View:scrollbar_hovering()
return self.v_scrollbar.hovering.track or self.h_scrollbar.hovering.track
end end
@ -134,18 +176,14 @@ end
---@param clicks integer ---@param clicks integer
---return boolean ---return boolean
function View:on_mouse_pressed(button, x, y, clicks) function View:on_mouse_pressed(button, x, y, clicks)
if not self.scrollable then return end if self:scrollbar_track_overlaps_point(x, y) then
local result = self.v_scrollbar:on_mouse_pressed(button, x, y, clicks) if self:scrollbar_overlaps_point(x, y) then
if result then self.dragging_scrollbar = true
if result ~= true then else
self.scroll.to.y = result * self:get_scrollable_size() local _, _, _, sh = self:get_scrollbar_rect()
end local ly = (y - self.position.y) - sh / 2
return true local pct = common.clamp(ly / self.size.y, 0, 100)
end self.scroll.to.y = self:get_scrollable_size() * pct
result = self.h_scrollbar:on_mouse_pressed(button, x, y, clicks)
if result then
if result ~= true then
self.scroll.to.x = result * self:get_h_scrollable_size()
end end
return true return true
end end
@ -156,9 +194,7 @@ end
---@param x number ---@param x number
---@param y number ---@param y number
function View:on_mouse_released(button, x, y) function View:on_mouse_released(button, x, y)
if not self.scrollable then return end self.dragging_scrollbar = false
self.v_scrollbar:on_mouse_released(button, x, y)
self.h_scrollbar:on_mouse_released(button, x, y)
end end
@ -167,41 +203,22 @@ end
---@param dx number ---@param dx number
---@param dy number ---@param dy number
function View:on_mouse_moved(x, y, dx, dy) function View:on_mouse_moved(x, y, dx, dy)
if not self.scrollable then return end if self.dragging_scrollbar then
local result local delta = self:get_scrollable_size() / self.size.y * dy
if self.h_scrollbar.dragging then goto skip_v_scrollbar end self.scroll.to.y = self.scroll.to.y + delta
result = self.v_scrollbar:on_mouse_moved(x, y, dx, dy) if not config.animate_drag_scroll then
if result then self:clamp_scroll_position()
if result ~= true then self.scroll.y = self.scroll.to.y
self.scroll.to.y = result * self:get_scrollable_size()
if not config.animate_drag_scroll then
self:clamp_scroll_position()
self.scroll.y = self.scroll.to.y
end
end end
-- hide horizontal scrollbar
self.h_scrollbar:on_mouse_left()
return true
end
::skip_v_scrollbar::
result = self.h_scrollbar:on_mouse_moved(x, y, dx, dy)
if result then
if result ~= true then
self.scroll.to.x = result * self:get_h_scrollable_size()
if not config.animate_drag_scroll then
self:clamp_scroll_position()
self.scroll.x = self.scroll.to.x
end
end
return true
end end
self.hovered_scrollbar = self:scrollbar_overlaps_point(x, y)
self.hovered_scrollbar_track = self.hovered_scrollbar or self:scrollbar_track_overlaps_point(x, y)
end end
function View:on_mouse_left() function View:on_mouse_left()
if not self.scrollable then return end self.hovered_scrollbar = false
self.v_scrollbar:on_mouse_left() self.hovered_scrollbar_track = false
self.h_scrollbar:on_mouse_left()
end end
@ -219,25 +236,12 @@ 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_ime_text_editing(text, start, length)
-- no-op
end end
---@param y number @Vertical scroll delta; positive is "up"
---@param x number @Horizontal scroll delta; positive is "left"
---@return boolean @Capture event
function View:on_mouse_wheel(y, x)
-- no-op
end
---Can be overriden to listen for scale change events to apply
---any neccesary changes in sizes, padding, etc...
---@param new_scale number
---@param prev_scale number
function View:on_scale_change(new_scale, prev_scale) end
function View:get_content_bounds() function View:get_content_bounds()
local x = self.scroll.x local x = self.scroll.x
local y = self.scroll.y local y = self.scroll.y
@ -257,35 +261,35 @@ end
function View:clamp_scroll_position() function View:clamp_scroll_position()
local max = self:get_scrollable_size() - self.size.y local max = self:get_scrollable_size() - self.size.y
self.scroll.to.y = common.clamp(self.scroll.to.y, 0, max) self.scroll.to.y = common.clamp(self.scroll.to.y, 0, max)
max = self:get_h_scrollable_size() - self.size.x
self.scroll.to.x = common.clamp(self.scroll.to.x, 0, max)
end end
function View:update_scrollbar() function View:update_scrollbar()
local v_scrollable = self:get_scrollable_size() local x, y, w, h = self:get_scrollbar_rect()
self.v_scrollbar:set_size(self.position.x, self.position.y, self.size.x, self.size.y, v_scrollable) self.scrollbar.w.to.thumb = w
self.v_scrollbar:set_percent(self.scroll.y/v_scrollable) self:move_towards(self.scrollbar.w, "thumb", self.scrollbar.w.to.thumb, 0.3, "scroll")
self.v_scrollbar:update() self.scrollbar.x.thumb = x + w - self.scrollbar.w.thumb
self.scrollbar.y.thumb = y
self.scrollbar.h.thumb = h
local h_scrollable = self:get_h_scrollable_size() local x, y, w, h = self:get_scrollbar_track_rect()
self.h_scrollbar:set_size(self.position.x, self.position.y, self.size.x, self.size.y, h_scrollable) self.scrollbar.w.to.track = w
self.h_scrollbar:set_percent(self.scroll.x/h_scrollable) self:move_towards(self.scrollbar.w, "track", self.scrollbar.w.to.track, 0.3, "scroll")
self.h_scrollbar:update() 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 end
function View:update() function View:update()
if self.current_scale ~= SCALE then
self:on_scale_change(SCALE, self.current_scale)
self.current_scale = SCALE
end
self:clamp_scroll_position() self: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, "scroll")
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, "scroll")
if not self.scrollable then return end
self:update_scrollbar() self:update_scrollbar()
end end
@ -298,9 +302,29 @@ function View:draw_background(color)
end end
function View:draw_scrollbar_track()
if not (self.hovered_scrollbar_track or self.dragging_scrollbar)
and self.scrollbar_alpha.value == 0 then
return
end
local color = { table.unpack(style.scrollbar_track) }
color[4] = color[4] * self.scrollbar_alpha.value / 100
renderer.draw_rect(self.scrollbar.x.track, self.scrollbar.y.track,
self.scrollbar.w.track, self.scrollbar.h.track, color)
end
function View:draw_scrollbar_thumb()
local highlight = self.hovered_scrollbar or self.dragging_scrollbar
local color = highlight and style.scrollbar2 or style.scrollbar
renderer.draw_rect(self.scrollbar.x.thumb, self.scrollbar.y.thumb,
self.scrollbar.w.thumb, self.scrollbar.h.thumb, color)
end
function View:draw_scrollbar() function View:draw_scrollbar()
self.v_scrollbar:draw() self:draw_scrollbar_track()
self.h_scrollbar:draw() self:draw_scrollbar_thumb()
end end

Binary file not shown.

View File

@ -212,14 +212,12 @@ end)
local partial = "" local partial = ""
local suggestions_offset = 1
local suggestions_idx = 1 local suggestions_idx = 1
local suggestions = {} local suggestions = {}
local last_line, last_col local last_line, last_col
local function reset_suggestions() local function reset_suggestions()
suggestions_offset = 1
suggestions_idx = 1 suggestions_idx = 1
suggestions = {} suggestions = {}
@ -263,7 +261,6 @@ local function update_suggestions()
end end
end end
suggestions_idx = 1 suggestions_idx = 1
suggestions_offset = 1
end end
local function get_partial_symbol() local function get_partial_symbol()
@ -279,10 +276,8 @@ local function get_active_view()
end end
end end
local last_max_width = 0
local function get_suggestions_rect(av) local function get_suggestions_rect(av)
if #suggestions == 0 then if #suggestions == 0 then
last_max_width = 0
return 0, 0, 0, 0 return 0, 0, 0, 0
end end
@ -292,47 +287,38 @@ local function get_suggestions_rect(av)
local font = av:get_font() local font = av:get_font()
local th = font:get_height() local th = font:get_height()
local ah = config.plugins.autocomplete.max_height
local max_items = math.min(ah, #suggestions)
local show_count = math.min(#suggestions, ah)
local start_index = math.max(suggestions_idx-(ah-1), 1)
local max_width = 0 local max_width = 0
for i = start_index, start_index + show_count - 1 do for _, s in ipairs(suggestions) do
local s = suggestions[i]
local w = font:get_width(s.text) local w = font:get_width(s.text)
if s.info then if s.info then
w = w + style.font:get_width(s.info) + style.padding.x w = w + style.font:get_width(s.info) + style.padding.x
end end
max_width = math.max(max_width, w) max_width = math.max(max_width, w)
end end
max_width = math.max(last_max_width, max_width)
last_max_width = max_width
max_width = max_width + style.padding.x * 2 local ah = config.plugins.autocomplete.max_height
x = x - style.padding.x
local max_items = #suggestions
if max_items > ah then
max_items = ah
end
-- additional line to display total items -- additional line to display total items
max_items = max_items + 1 max_items = max_items + 1
if max_width > core.root_view.size.x then if max_width < 150 then
max_width = core.root_view.size.x max_width = 150
end
if max_width < 150 * SCALE then
max_width = 150 * SCALE
end end
-- if portion not visiable to right, reposition to DocView right margin -- if portion not visiable to right, reposition to DocView right margin
if x + max_width > core.root_view.size.x then if (x - av.position.x) + max_width > av.size.x then
x = (av.size.x + av.position.x) - max_width x = (av.size.x + av.position.x) - max_width - (style.padding.x * 2)
end end
return return
x, x - style.padding.x,
y - style.padding.y, y - style.padding.y,
max_width, max_width + style.padding.x * 2,
max_items * (th + style.padding.y) + style.padding.y max_items * (th + style.padding.y) + style.padding.y
end end
@ -460,29 +446,16 @@ local function draw_suggestions_box(av)
local font = av:get_font() local font = av:get_font()
local lh = font:get_height() + style.padding.y local lh = font:get_height() + style.padding.y
local y = ry + style.padding.y / 2 local y = ry + style.padding.y / 2
local show_count = math.min(#suggestions, ah) local show_count = #suggestions <= ah and #suggestions or ah
local start_index = suggestions_offset local start_index = suggestions_idx > ah and (suggestions_idx-(ah-1)) or 1
for i=start_index, start_index+show_count-1, 1 do for i=start_index, start_index+show_count-1, 1 do
if not suggestions[i] then if not suggestions[i] then
break break
end end
local s = suggestions[i] local s = suggestions[i]
local info_size = s.info and (style.font:get_width(s.info) + style.padding.x) or style.padding.x
local color = (i == suggestions_idx) and style.accent or style.text local color = (i == suggestions_idx) and style.accent or style.text
-- Push clip to avoid that the suggestion text gets drawn over suggestion type/icon common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
core.push_clip_rect(rx + style.padding.x, y, rw - info_size - style.padding.x, lh)
local x_adv = common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
core.pop_clip_rect()
-- If the text wasn't fully visible, draw an ellipsis
if x_adv > rx + rw - info_size then
local ellipsis_size = font:get_width("")
local ell_x = rx + rw - info_size - ellipsis_size
renderer.draw_rect(ell_x, y, ellipsis_size, lh, style.background3)
common.draw_text(font, color, "", "left", ell_x, y, ellipsis_size, lh)
end
if s.info then if s.info then
color = (i == suggestions_idx) and style.text or style.dim color = (i == suggestions_idx) and style.text or style.dim
common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh) common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh)
@ -615,11 +588,8 @@ function autocomplete.open(on_close)
end end
local av = get_active_view() local av = get_active_view()
if av then last_line, last_col = av.doc:get_selection()
partial = get_partial_symbol() update_suggestions()
last_line, last_col = av.doc:get_selection()
update_suggestions()
end
end end
function autocomplete.close() function autocomplete.close()
@ -658,6 +628,7 @@ end
command.add(predicate, { command.add(predicate, {
["autocomplete:complete"] = function(dv) ["autocomplete:complete"] = function(dv)
local doc = dv.doc local doc = dv.doc
local line, col = doc:get_selection()
local item = suggestions[suggestions_idx] local item = suggestions[suggestions_idx]
local text = item.text local text = item.text
local inserted = false local inserted = false
@ -666,47 +637,19 @@ command.add(predicate, {
end end
if not inserted then if not inserted then
local current_partial = get_partial_symbol() local current_partial = get_partial_symbol()
local sz = #current_partial doc:insert(line, col, text)
doc:remove(line, col, line, col - #current_partial)
for _, line1, col1, line2, _ in doc:get_selections(true) do doc:set_selection(line, col + #text - #current_partial)
local n = col1 - 1
local line = doc.lines[line1]
for i = 1, sz + 1 do
local j = sz - i
local subline = line:sub(n - j, n)
local subpartial = current_partial:sub(i, -1)
if subpartial == subline then
doc:remove(line1, col1, line2, n - j)
break
end
end
end
doc:text_input(item.text)
end end
reset_suggestions() reset_suggestions()
end, end,
["autocomplete:previous"] = function() ["autocomplete:previous"] = function()
suggestions_idx = (suggestions_idx - 2) % #suggestions + 1 suggestions_idx = math.max(suggestions_idx - 1, 1)
local ah = math.min(config.plugins.autocomplete.max_height, #suggestions)
if suggestions_offset > suggestions_idx then
suggestions_offset = suggestions_idx
elseif suggestions_offset + ah < suggestions_idx + 1 then
suggestions_offset = suggestions_idx - ah + 1
end
end, end,
["autocomplete:next"] = function() ["autocomplete:next"] = function()
suggestions_idx = (suggestions_idx % #suggestions) + 1 suggestions_idx = math.min(suggestions_idx + 1, #suggestions)
local ah = math.min(config.plugins.autocomplete.max_height, #suggestions)
if suggestions_offset + ah < suggestions_idx + 1 then
suggestions_offset = suggestions_idx - ah + 1
elseif suggestions_offset > suggestions_idx then
suggestions_offset = suggestions_idx
end
end, end,
["autocomplete:cycle"] = function() ["autocomplete:cycle"] = function()

View File

@ -46,8 +46,8 @@ end
local function check_prompt_reload(doc) local function check_prompt_reload(doc)
if doc and doc.deferred_reload then if doc and doc.deferred_reload then
core.nag_view:show("File Changed", doc.filename .. " has changed. Reload this file?", { core.nag_view:show("File Changed", doc.filename .. " has changed. Reload this file?", {
{ text = "Yes", default_yes = true }, { font = style.font, text = "Yes", default_yes = true },
{ text = "No", default_no = true } { font = style.font, text = "No" , default_no = true }
}, function(item) }, function(item)
if item.text == "Yes" then reload_doc(doc) end if item.text == "Yes" then reload_doc(doc) end
doc.deferred_reload = false doc.deferred_reload = false
@ -69,7 +69,7 @@ function dirwatch:check(change_callback, ...)
for _, doc in ipairs(core.docs) do for _, doc in ipairs(core.docs) do
if doc.abs_filename and (dir == common.dirname(doc.abs_filename) or dir == doc.abs_filename) then 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 "") local info = system.get_file_info(doc.filename or "")
if info and info.type == "file" and times[doc] ~= info.modified then if info and times[doc] ~= info.modified then
if not doc:is_dirty() and not config.plugins.autoreload.always_show_nagview then if not doc:is_dirty() and not config.plugins.autoreload.always_show_nagview then
reload_doc(doc) reload_doc(doc)
else else

View File

@ -4,7 +4,6 @@ local command = require "core.command"
local keymap = require "core.keymap" local keymap = require "core.keymap"
local ContextMenu = require "core.contextmenu" local ContextMenu = require "core.contextmenu"
local RootView = require "core.rootview" local RootView = require "core.rootview"
local config = require "core.config"
local menu = ContextMenu() local menu = ContextMenu()
local on_view_mouse_pressed = RootView.on_view_mouse_pressed local on_view_mouse_pressed = RootView.on_view_mouse_pressed
@ -62,24 +61,18 @@ keymap.add { ["up"] = "context:focus-previous" }
keymap.add { ["down"] = "context:focus-next" } keymap.add { ["down"] = "context:focus-next" }
keymap.add { ["escape"] = "context:hide" } keymap.add { ["escape"] = "context:hide" }
if require("plugins.scale") then
local cmds = { menu:register("core.docview", {
{ text = "Cut", command = "doc:cut" }, { text = "Cut", command = "doc:cut" },
{ text = "Copy", command = "doc:copy" }, { text = "Copy", command = "doc:copy" },
{ text = "Paste", command = "doc:paste" }, { text = "Paste", command = "doc:paste" },
ContextMenu.DIVIDER, { text = "Font +", command = "scale:increase" },
{ text = "Find", command = "find-replace:find" }, { text = "Font -", command = "scale:decrease" },
{ text = "Replace", command = "find-replace:replace" } { text = "Font Reset", command = "scale:reset" },
} ContextMenu.DIVIDER,
{ text = "Find", command = "find-replace:find" },
if config.plugins.scale ~= false and require("plugins.scale") then { text = "Replace", command = "find-replace:replace" }
table.move(cmds, 4, 6, 7) })
cmds[4] = { text = "Font +", command = "scale:increase" }
cmds[5] = { text = "Font -", command = "scale:decrease" }
cmds[6] = { text = "Font Reset", command = "scale:reset" }
end end
menu:register("core.docview", cmds)
return menu return menu

View File

@ -75,9 +75,7 @@ local function escape_comment_tokens(token)
end end
local function get_comment_patterns(syntax, _loop) local function get_comment_patterns(syntax)
_loop = _loop or 1
if _loop > 5 then return end
if comments_cache[syntax] then if comments_cache[syntax] then
if #comments_cache[syntax] > 0 then if #comments_cache[syntax] > 0 then
return comments_cache[syntax] return comments_cache[syntax]
@ -127,7 +125,7 @@ local function get_comment_patterns(syntax, _loop)
elseif pattern.syntax then elseif pattern.syntax then
local subsyntax = type(pattern.syntax) == 'table' and pattern.syntax local subsyntax = type(pattern.syntax) == 'table' and pattern.syntax
or core_syntax.get("file"..pattern.syntax, "") or core_syntax.get("file"..pattern.syntax, "")
local sub_comments = get_comment_patterns(subsyntax, _loop + 1) local sub_comments = get_comment_patterns(subsyntax)
if sub_comments then if sub_comments then
for s=1, #sub_comments do for s=1, #sub_comments do
table.insert(comments, sub_comments[s]) table.insert(comments, sub_comments[s])
@ -192,11 +190,11 @@ local function get_non_empty_lines(syntax, lines)
end end
else else
if comment[3] then if comment[3] then
local start, ending = regex.find_offsets( local start, ending = regex.match(
comment[2], line, 1, regex.ANCHORED comment[2], line, 1, regex.ANCHORED
) )
if start then if start then
if not regex.find_offsets( if not regex.match(
comment[3], line, ending+1, regex.ANCHORED comment[3], line, ending+1, regex.ANCHORED
) )
then then
@ -206,7 +204,7 @@ local function get_non_empty_lines(syntax, lines)
end end
break break
end end
elseif regex.find_offsets(comment[2], line, 1, regex.ANCHORED) then elseif regex.match(comment[2], line, 1, regex.ANCHORED) then
is_comment = true is_comment = true
break break
end end
@ -216,7 +214,7 @@ local function get_non_empty_lines(syntax, lines)
is_comment = true is_comment = true
inside_comment = false inside_comment = false
end_pattern = nil end_pattern = nil
elseif end_regex and regex.find_offsets(end_regex, line) then elseif end_regex and regex.match(end_regex, line) then
is_comment = true is_comment = true
inside_comment = false inside_comment = false
end_regex = nil end_regex = nil
@ -243,7 +241,7 @@ local function detect_indent_stat(doc)
local max_lines = auto_detect_max_lines 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 spaces = text:match("^ +")
if spaces and #spaces > 1 then table.insert(stat, #spaces) end if spaces then table.insert(stat, spaces:len()) end
local tabs = text:match("^\t+") local tabs = text:match("^\t+")
if tabs then tab_count = tab_count + 1 end if tabs then tab_count = tab_count + 1 end
-- if nothing found for first lines try at least 4 more times -- if nothing found for first lines try at least 4 more times

View File

@ -3,12 +3,11 @@
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 command = require "core.command"
local config = require "core.config" local config = require "core.config"
local Highlighter = require "core.doc.highlighter" local Highlighter = require "core.doc.highlighter"
config.plugins.drawwhitespace = common.merge({ config.plugins.drawwhitespace = common.merge({
enabled = false, enabled = true,
show_leading = true, show_leading = true,
show_trailing = true, show_trailing = true,
show_middle = true, show_middle = true,
@ -42,7 +41,7 @@ config.plugins.drawwhitespace = common.merge({
description = "Disable or enable the drawing of white spaces.", description = "Disable or enable the drawing of white spaces.",
path = "enabled", path = "enabled",
type = "toggle", type = "toggle",
default = false default = true
}, },
{ {
label = "Show Leading", label = "Show Leading",
@ -245,7 +244,7 @@ function DocView:draw_line_text(idx, x, y)
local color = base_color local color = base_color
local draw = false local draw = false
if e >= #text - 1 then if e == #text - 1 then
draw = show_trailing draw = show_trailing
color = trailing_color color = trailing_color
elseif s == 1 then elseif s == 1 then
@ -291,31 +290,15 @@ function DocView:draw_line_text(idx, x, y)
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 cache = ws_cache[self.doc.highlighter][idx]
for i=1,#cache,4 do for i=1,#cache,4 do
local sub = cache[i]
local tx = cache[i + 1] + x local tx = cache[i + 1] + x
local tw = cache[i + 2] local tw = cache[i + 2]
if tx <= x2 then local color = cache[i + 3]
local sub = cache[i] if tx + tw >= x1 then
local color = cache[i + 3] tx = renderer.draw_text(font, sub, tx, ty, color)
if tx + tw >= x1 then
tx = renderer.draw_text(font, sub, tx, ty, color)
end
end end
if tx > x2 then break end
end end
return draw_line_text(self, idx, x, y) return draw_line_text(self, idx, x, y)
end end
command.add(nil, {
["draw-whitespace:toggle"] = function()
config.plugins.drawwhitespace.enabled = not config.plugins.drawwhitespace.enabled
end,
["draw-whitespace:disable"] = function()
config.plugins.drawwhitespace.enabled = false
end,
["draw-whitespace:enable"] = function()
config.plugins.drawwhitespace.enabled = true
end,
})

View File

@ -5,8 +5,7 @@ syntax.add {
name = "C++", name = "C++",
files = { files = {
"%.h$", "%.inl$", "%.cpp$", "%.cc$", "%.C$", "%.cxx$", "%.h$", "%.inl$", "%.cpp$", "%.cc$", "%.C$", "%.cxx$",
"%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$", "%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$"
"%.ino$"
}, },
comment = "//", comment = "//",
block_comment = { "/*", "*/" }, block_comment = { "/*", "*/" },
@ -15,9 +14,9 @@ syntax.add {
{ pattern = { "/%*", "%*/" }, type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" },
{ pattern = { '"', '"', '\\' }, type = "string" }, { pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" }, { pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = "0x%x+[%x']*", type = "number" }, { pattern = "0x%x+", type = "number" },
{ pattern = "%d+[%d%.'eE]*f?", type = "number" }, { pattern = "%d+[%d%.'eE]*f?", type = "number" },
{ pattern = "%.?%d+[%d']*f?", type = "number" }, { pattern = "%.?%d+f?", type = "number" },
{ pattern = "[%+%-=/%*%^%%<>!~|:&]", type = "operator" }, { 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"} },

View File

@ -1,75 +1,24 @@
-- mod-version:3 -- mod-version:3
local syntax = require "core.syntax" local syntax = require "core.syntax"
-- Regex pattern explanation:
-- This will match / and will look ahead for something that looks like a regex.
--
-- (?!/) Don't match empty regexes.
--
-- (?>...) this is using an atomic group to minimize backtracking, as that'd
-- cause "Catastrophic Backtracking" in some cases.
--
-- [^\\[\/]++ will match anything that's isn't an escape, a start of character
-- class or an end of pattern, without backtracking (the second +).
--
-- \\. will match anything that's escaped.
--
-- \[(?:[^\\\]++]|\\.)*+\] will match character classes.
--
-- /[gmiyuvsd]*\s*[\n,;\)\]\}\.]) will match the end of pattern delimiter, optionally
-- followed by pattern options, and anything that can
-- be after a pattern.
--
-- Demo with some unit tests (click on the Unit Tests entry): https://regex101.com/r/Vx5L5V/1
-- Note that it has a couple of changes to make it work on that platform.
local regex_pattern = {
[=[\/(?=(?!\/)(?:(?>[^\\[\/]++|\\.|\[(?:[^\\\]]++|\\.)*+\])*+)++\/[gmiyuvsd]*\s*(?:[\n,;\)\]\}\.]|\/[\/*]))()]=],
"/()[gmiyuvsd]*", "\\"
}
-- For the moment let's not actually differentiate the insides of the regex,
-- as this will need new token types...
local inner_regex_syntax = {
patterns = {
{ pattern = "%(()%?[:!=><]", type = { "string", "string" } },
{ pattern = "[.?+*%(%)|]", type = "string" },
{ pattern = "{%d*,?%d*}", type = "string" },
{ regex = { [=[\[()\^?]=], [=[(?:\]|(?=\n))()]=], "\\" },
type = { "string", "string" },
syntax = { -- Inside character class
patterns = {
{ pattern = "\\\\", type = "string" },
{ pattern = "\\%]", type = "string" },
{ pattern = "[^%]\n]", type = "string" }
},
symbols = {}
}
},
{ regex = "\\/", type = "string" },
{ regex = "[^/\n]", type = "string" },
},
symbols = {}
}
syntax.add { syntax.add {
name = "JavaScript", name = "JavaScript",
files = { "%.js$", "%.json$", "%.cson$", "%.mjs$", "%.cjs$" }, files = { "%.js$", "%.json$", "%.cson$", "%.mjs$", "%.cjs$" },
comment = "//", comment = "//",
block_comment = { "/*", "*/" }, block_comment = { "/*", "*/" },
patterns = { patterns = {
{ pattern = "//.*", type = "comment" }, { pattern = "//.*", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" },
{ regex = regex_pattern, syntax = inner_regex_syntax, type = {"string", "string"} }, { pattern = { '/[^= ]', '/', '\\' },type = "string" },
{ pattern = { '"', '"', '\\' }, type = "string" }, { pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" }, { pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = { "`", "`", '\\' }, type = "string" }, { pattern = { "`", "`", '\\' }, type = "string" },
-- Use (?:\/(?!\/|\*))? to avoid that a regex can start after a number, while also allowing // and /* comments { pattern = "0x[%da-fA-F_]+n?", type = "number" },
{ regex = [[-?0[xXbBoO][\da-fA-F_]+n?()\s*()(?:\/(?!\/|\*))?]], type = {"number", "normal", "operator"} }, { pattern = "-?%d+[%d%.eE_n]*", type = "number" },
{ regex = [[-?\d+[0-9.eE_n]*()\s*()(?:\/(?!\/|\*))?]], type = {"number", "normal", "operator"} }, { pattern = "-?%.?%d+", type = "number" },
{ regex = [[-?\.?\d+()\s*()(?:\/(?!\/|\*))?]], type = {"number", "normal", "operator"} }, { pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" }, { pattern = "[%a_][%w_]*%f[(]", type = "function" },
{ pattern = "[%a_][%w_]*%f[(]", type = "function" }, { pattern = "[%a_][%w_]*", type = "symbol" },
{ pattern = "[%a_][%w_]*", type = "symbol" },
}, },
symbols = { symbols = {
["async"] = "keyword", ["async"] = "keyword",
@ -93,7 +42,6 @@ syntax.add {
["get"] = "keyword", ["get"] = "keyword",
["if"] = "keyword", ["if"] = "keyword",
["import"] = "keyword", ["import"] = "keyword",
["from"] = "keyword",
["in"] = "keyword", ["in"] = "keyword",
["of"] = "keyword", ["of"] = "keyword",
["instanceof"] = "keyword", ["instanceof"] = "keyword",

View File

@ -3,6 +3,25 @@ local syntax = require "core.syntax"
local style = require "core.style" local style = require "core.style"
local core = require "core" 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_squares_match = "^%[%]"
local in_parenthesis_match = "^%(%)" local in_parenthesis_match = "^%(%)"
@ -23,9 +42,9 @@ syntax.add {
-- blockquote -- blockquote
{ pattern = "^%s*>+%s", type = "string" }, { pattern = "^%s*>+%s", type = "string" },
-- alternative bold italic formats -- alternative bold italic formats
{ pattern = { "%s___", "___" }, type = "markdown_bold_italic" }, { pattern = { "%s___", "___%f[%s]" }, type = "markdown_bold_italic" },
{ pattern = { "%s__", "__" }, type = "markdown_bold" }, { pattern = { "%s__", "__%f[%s]" }, type = "markdown_bold" },
{ pattern = { "%s_[%S]", "_" }, type = "markdown_italic" }, { pattern = { "%s_[%S]", "_%f[%s]" }, type = "markdown_italic" },
-- reference links -- reference links
{ {
pattern = "^%s*%[%^()["..in_squares_match.."]+()%]: ", pattern = "^%s*%[%^()["..in_squares_match.."]+()%]: ",
@ -93,7 +112,6 @@ syntax.add {
{ pattern = { "%$%$", "%$%$", "\\" }, type = "string", syntax = ".tex"}, { pattern = { "%$%$", "%$%$", "\\" }, type = "string", syntax = ".tex"},
{ regex = { "\\$", [[\$|(?=\\*\n)]], "\\" }, type = "string", syntax = ".tex"}, { regex = { "\\$", [[\$|(?=\\*\n)]], "\\" }, type = "string", syntax = ".tex"},
-- code blocks -- code blocks
{ pattern = { "```caddyfile", "```" }, type = "string", syntax = "Caddyfile" },
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" }, { pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
{ pattern = { "```cpp", "```" }, type = "string", syntax = ".cpp" }, { pattern = { "```cpp", "```" }, type = "string", syntax = ".cpp" },
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" }, { pattern = { "```python", "```" }, type = "string", syntax = ".py" },
@ -131,15 +149,14 @@ syntax.add {
{ pattern = { "```", "```" }, type = "string" }, { pattern = { "```", "```" }, type = "string" },
{ pattern = { "``", "``" }, type = "string" }, { pattern = { "``", "``" }, type = "string" },
{ pattern = { "%f[\\`]%`[%S]", "`" }, type = "string" }, { pattern = { "%f[\\`]%`[%S]", "`" }, type = "string" },
-- lines
{ pattern = "^%-%-%-+\n" , type = "comment" },
{ pattern = "^%*%*%*+\n", type = "comment" },
{ pattern = "^___+\n", type = "comment" },
{ pattern = "^===+\n", type = "comment" },
-- strike -- strike
{ pattern = { "~~", "~~" }, type = "keyword2" }, { pattern = { "~~", "~~" }, type = "keyword2" },
-- highlight -- highlight
{ pattern = { "==", "==" }, type = "literal" }, { pattern = { "==", "==" }, type = "literal" },
-- lines
{ pattern = "^%-%-%-+$" , type = "comment" },
{ pattern = "^%*%*%*+$", type = "comment" },
{ pattern = "^___+$", type = "comment" },
-- bold and italic -- bold and italic
{ pattern = { "%*%*%*%S", "%*%*%*" }, type = "markdown_bold_italic" }, { pattern = { "%*%*%*%S", "%*%*%*" }, type = "markdown_bold_italic" },
{ pattern = { "%*%*%S", "%*%*" }, type = "markdown_bold" }, { pattern = { "%*%*%S", "%*%*" }, type = "markdown_bold" },
@ -149,16 +166,16 @@ syntax.add {
type = "markdown_italic" type = "markdown_italic"
}, },
-- alternative bold italic formats -- alternative bold italic formats
{ pattern = "^___[%s%p%w]+___" , type = "markdown_bold_italic" }, { pattern = "^___[%s%p%w]+___%s" , type = "markdown_bold_italic" },
{ pattern = "^__[%s%p%w]+__" , type = "markdown_bold" }, { pattern = "^__[%s%p%w]+__%s" , type = "markdown_bold" },
{ pattern = "^_[%s%p%w]+_" , type = "markdown_italic" }, { pattern = "^_[%s%p%w]+_%s" , type = "markdown_italic" },
-- heading with custom id -- heading with custom id
{ {
pattern = "^#+%s[%w%s%p]+(){()#[%w%-]+()}", pattern = "^#+%s[%w%s%p]+(){()#[%w%-]+()}",
type = { "keyword", "function", "string", "function" } type = { "keyword", "function", "string", "function" }
}, },
-- headings -- headings
{ pattern = "^#+%s.+\n", type = "keyword" }, { pattern = "^#+%s.+$", type = "keyword" },
-- superscript and subscript -- superscript and subscript
{ {
pattern = "%^()%d+()%^", pattern = "%^()%d+()%^",
@ -205,64 +222,14 @@ syntax.add {
-- Adjust the color on theme changes -- Adjust the color on theme changes
core.add_thread(function() core.add_thread(function()
local custom_fonts = { bold = {font = nil, color = nil}, italic = {}, bold_italic = {} }
local initial_color
local last_code_font
local function set_font(attr)
local attributes = {}
if attr ~= "bold_italic" then
attributes[attr] = true
else
attributes["bold"] = true
attributes["italic"] = true
end
local font = style.code_font:copy(
style.code_font:get_size(),
attributes
)
custom_fonts[attr].font = font
style.syntax_fonts["markdown_"..attr] = font
end
local function set_color(attr)
custom_fonts[attr].color = style.syntax["keyword2"]
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
end
-- Add 3 type of font styles for use on markdown files
for attr, _ in pairs(custom_fonts) do
-- Only set it if the font wasn't manually customized
if not style.syntax_fonts["markdown_"..attr] then
set_font(attr)
end
-- Only set it if the color wasn't manually customized
if not style.syntax["markdown_"..attr] then
set_color(attr)
end
end
while true do while true do
if last_code_font ~= style.code_font then
last_code_font = style.code_font
for attr, _ in pairs(custom_fonts) do
-- Only set it if the font wasn't manually customized
if style.syntax_fonts["markdown_"..attr] == custom_fonts[attr].font then
set_font(attr)
end
end
end
if initial_color ~= style.syntax["keyword2"] then if initial_color ~= style.syntax["keyword2"] then
initial_color = style.syntax["keyword2"] for _, attr in pairs({"bold", "italic", "bold_italic"}) do
for attr, _ in pairs(custom_fonts) do style.syntax["markdown_"..attr] = style.syntax["keyword2"]
-- Only set it if the color wasn't manually customized
if style.syntax["markdown_"..attr] == custom_fonts[attr].color then
set_color(attr)
end
end end
initial_color = style.syntax["keyword2"]
end end
coroutine.yield(1) coroutine.yield(1)
end end
end) end)

View File

@ -3,7 +3,7 @@ local syntax = require "core.syntax"
syntax.add { syntax.add {
name = "Python", name = "Python",
files = { "%.py$", "%.pyw$", "%.rpy$", "%.pyi$" }, files = { "%.py$", "%.pyw$", "%.rpy$" },
headers = "^#!.*[ /]python", headers = "^#!.*[ /]python",
comment = "#", comment = "#",
block_comment = { '"""', '"""' }, block_comment = { '"""', '"""' },
@ -16,8 +16,8 @@ syntax.add {
{ 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 = "-?0[xboXBO][%da-fA-F_]+",type = "number" }, { pattern = "0x[%da-fA-F]+", type = "number" },
{ pattern = "-?%d+[%d%.eE_]*", type = "number" }, { pattern = "-?%d+[%d%.eE]*", type = "number" },
{ pattern = "-?%.?%d+", type = "number" }, { pattern = "-?%.?%d+", type = "number" },
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" }, { pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{ pattern = "[%a_][%w_]*%f[(]", type = "function" }, { pattern = "[%a_][%w_]*%f[(]", type = "function" },

View File

@ -79,12 +79,14 @@ end
local draw_overlay = DocView.draw_overlay local draw_overlay = DocView.draw_overlay
function DocView:draw_overlay(...) function DocView:draw_overlay(...)
draw_overlay(self, ...)
if if
type(config.plugins.lineguide) == "table" type(config.plugins.lineguide) == "table"
and and
config.plugins.lineguide.enabled config.plugins.lineguide.enabled
and and
self:is(DocView) not self:is(CommandView)
then then
local line_x = self:get_line_screen_position(1) local line_x = self:get_line_screen_position(1)
local character_width = self:get_font():get_width("n") local character_width = self:get_font():get_width("n")
@ -104,8 +106,6 @@ function DocView:draw_overlay(...)
end end
end end
end end
-- everything else like the cursor above the line guides
draw_overlay(self, ...)
end end
command.add(nil, { command.add(nil, {

View File

@ -219,7 +219,7 @@ function LineWrapping.draw_guide(docview)
end end
function LineWrapping.update_docview_breaks(docview) function LineWrapping.update_docview_breaks(docview)
local w = docview.v_scrollbar.expanded_size or style.expanded_scrollbar_size 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)) 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) 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 if (not docview.wrapped_settings or docview.wrapped_settings.width == nil or width ~= docview.wrapped_settings.width) then
@ -310,7 +310,7 @@ local function get_line_col_from_index_and_x(docview, idx, x)
end end
local open_files = setmetatable({ }, { __mode = "k" }) local open_files = {}
local old_doc_insert = Doc.raw_insert local old_doc_insert = Doc.raw_insert
function Doc:raw_insert(line, col, text, undo_stack, time) function Doc:raw_insert(line, col, text, undo_stack, time)
@ -355,34 +355,18 @@ function DocView:get_scrollable_size()
return self:get_line_height() * (get_total_wrapped_lines(self) - 1) + self.size.y return self:get_line_height() * (get_total_wrapped_lines(self) - 1) + self.size.y
end end
local old_get_h_scrollable_size = DocView.get_h_scrollable_size
function DocView:get_h_scrollable_size(...)
if self.wrapping_enabled then return 0 end
return old_get_h_scrollable_size(self, ...)
end
local old_new = DocView.new local old_new = DocView.new
function DocView:new(doc) function DocView:new(doc)
old_new(self, doc) old_new(self, doc)
if not open_files[doc] then open_files[doc] = {} end if not open_files[doc] then open_files[doc] = {} end
table.insert(open_files[doc], self) table.insert(open_files[doc], self)
if config.plugins.linewrapping.enable_by_default then if config.plugins.linewrapping.enable_by_default then
self.wrapping_enabled = true
LineWrapping.update_docview_breaks(self) LineWrapping.update_docview_breaks(self)
else
self.wrapping_enabled = false
end end
end end
local old_scroll_to_line = DocView.scroll_to_line
function DocView:scroll_to_line(...)
if self.wrapping_enabled then LineWrapping.update_docview_breaks(self) end
old_scroll_to_line(self, ...)
end
local old_scroll_to_make_visible = DocView.scroll_to_make_visible local old_scroll_to_make_visible = DocView.scroll_to_make_visible
function DocView:scroll_to_make_visible(line, col) function DocView:scroll_to_make_visible(line, col)
if self.wrapping_enabled then LineWrapping.update_docview_breaks(self) end
old_scroll_to_make_visible(self, line, col) old_scroll_to_make_visible(self, line, col)
if self.wrapped_settings then self.scroll.to.x = 0 end if self.wrapped_settings then self.scroll.to.x = 0 end
end end
@ -485,7 +469,7 @@ local old_draw_line_body = DocView.draw_line_body
function DocView:draw_line_body(line, x, y) function DocView:draw_line_body(line, x, y)
if not self.wrapped_settings then return old_draw_line_body(self, line, x, y) end if not self.wrapped_settings then return old_draw_line_body(self, line, x, y) end
local lh = self:get_line_height() local lh = self:get_line_height()
local idx0, _, count = get_line_idx_col_count(self, line) local idx0 = get_line_idx_col_count(self, line)
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
if line >= line1 and line <= line2 then if line >= line1 and line <= line2 then
if line1 ~= line then col1 = 1 end if line1 ~= line then col1 = 1 end
@ -493,14 +477,12 @@ function DocView:draw_line_body(line, x, y)
if col1 ~= col2 then if col1 ~= col2 then
local idx1, ncol1 = get_line_idx_col_count(self, line, col1) local idx1, ncol1 = get_line_idx_col_count(self, line, col1)
local idx2, ncol2 = get_line_idx_col_count(self, line, col2) local idx2, ncol2 = get_line_idx_col_count(self, line, col2)
local start = 0
for i = idx1, idx2 do for i = idx1, idx2 do
local x1, x2 = x + (idx1 == i and self:get_col_x_offset(line1, col1) or 0) local x1, x2 = x + (idx1 == i and self:get_col_x_offset(line1, col1) or 0)
if idx2 == i then if idx2 == i then
x2 = x + self:get_col_x_offset(line, col2) x2 = x + self:get_col_x_offset(line, col2)
else else
start = start + get_idx_line_length(self, i, line) x2 = x + self:get_col_x_offset(line, get_idx_line_length(self, i, line) + 1, true)
x2 = x + self:get_col_x_offset(line, start + 1, true)
end end
renderer.draw_rect(x1, y + (i - idx0) * lh, x2 - x1, lh, style.selection) renderer.draw_rect(x1, y + (i - idx0) * lh, x2 - x1, lh, style.selection)
end end
@ -516,6 +498,7 @@ function DocView:draw_line_body(line, x, y)
end end
end end
if draw_highlight then if draw_highlight then
local _, _, count = get_line_idx_col_count(self, line)
for i=1,count do for i=1,count do
self:draw_line_highlight(x + self.scroll.x, y + lh * (i - 1)) self:draw_line_highlight(x + self.scroll.x, y + lh * (i - 1))
end end
@ -574,13 +557,11 @@ end
command.add(nil, { command.add(nil, {
["line-wrapping:enable"] = function() ["line-wrapping:enable"] = function()
if core.active_view and core.active_view.doc then if core.active_view and core.active_view.doc then
core.active_view.wrapping_enabled = true
LineWrapping.update_docview_breaks(core.active_view) LineWrapping.update_docview_breaks(core.active_view)
end end
end, end,
["line-wrapping:disable"] = function() ["line-wrapping:disable"] = function()
if core.active_view and core.active_view.doc then if core.active_view and core.active_view.doc then
core.active_view.wrapping_enabled = false
LineWrapping.reconstruct_breaks(core.active_view, core.active_view:get_font(), math.huge) LineWrapping.reconstruct_breaks(core.active_view, core.active_view:get_font(), math.huge)
end end
end, end,

View File

@ -6,7 +6,7 @@ local command = require "core.command"
local style = require "core.style" local style = require "core.style"
local View = require "core.view" local View = require "core.view"
---@class plugins.projectsearch.resultsview : core.view
local ResultsView = View:extend() local ResultsView = View:extend()
ResultsView.context = "session" ResultsView.context = "session"
@ -219,10 +219,6 @@ function ResultsView:draw()
end end
---@param path string
---@param text string
---@param fn fun(line_text:string):...
---@return plugins.projectsearch.resultsview?
local function begin_search(path, text, fn) local function begin_search(path, text, fn)
if text == "" then if text == "" then
core.error("Expected non-empty string") core.error("Expected non-empty string")
@ -230,7 +226,6 @@ local function begin_search(path, text, fn)
end end
local rv = ResultsView(path, text, fn) local rv = ResultsView(path, text, fn)
core.root_view:get_active_node_default():add_view(rv) core.root_view:get_active_node_default():add_view(rv)
return rv
end end
@ -254,59 +249,6 @@ local function normalize_path(path)
return path return path
end end
---@class plugins.projectsearch
local projectsearch = {}
---@type plugins.projectsearch.resultsview
projectsearch.ResultsView = ResultsView
---@param text string
---@param path string
---@param insensitive? boolean
---@return plugins.projectsearch.resultsview?
function projectsearch.search_plain(text, path, insensitive)
if insensitive then text = text:lower() end
return begin_search(path, text, function(line_text)
if insensitive then
return line_text:lower():find(text, nil, true)
else
return line_text:find(text, nil, true)
end
end)
end
---@param text string
---@param path string
---@param insensitive? boolean
---@return plugins.projectsearch.resultsview?
function projectsearch.search_regex(text, path, insensitive)
local re, errmsg
if insensitive then
re, errmsg = regex.compile(text, "i")
else
re, errmsg = regex.compile(text)
end
if not re then core.log("%s", errmsg) return end
return begin_search(path, text, function(line_text)
return regex.cmatch(re, line_text)
end)
end
---@param text string
---@param path string
---@param insensitive? boolean
---@return plugins.projectsearch.resultsview?
function projectsearch.search_fuzzy(text, path, insensitive)
if insensitive then text = text:lower() end
return begin_search(path, text, function(line_text)
if insensitive then
return common.fuzzy_match(line_text:lower(), text) and 1
else
return common.fuzzy_match(line_text, text) and 1
end
end)
end
command.add(nil, { command.add(nil, {
["project-search:find"] = function(path) ["project-search:find"] = function(path)
@ -314,7 +256,10 @@ command.add(nil, {
text = get_selected_text(), text = get_selected_text(),
select_text = true, select_text = true,
submit = function(text) submit = function(text)
projectsearch.search_plain(text, path, true) text = text:lower()
begin_search(path, text, function(line_text)
return line_text:lower():find(text, nil, true)
end)
end end
}) })
end, end,
@ -322,7 +267,10 @@ command.add(nil, {
["project-search:find-regex"] = function(path) ["project-search:find-regex"] = function(path)
core.command_view:enter("Find Regex In " .. (normalize_path(path) or "Project"), { core.command_view:enter("Find Regex In " .. (normalize_path(path) or "Project"), {
submit = function(text) submit = function(text)
projectsearch.search_regex(text, path, true) local re = regex.compile(text, "i")
begin_search(path, text, function(line_text)
return regex.cmatch(re, line_text)
end)
end end
}) })
end, end,
@ -332,7 +280,9 @@ command.add(nil, {
text = get_selected_text(), text = get_selected_text(),
select_text = true, select_text = true,
submit = function(text) submit = function(text)
projectsearch.search_fuzzy(text, path, true) begin_search(path, text, function(line_text)
return common.fuzzy_match(line_text, text) and 1
end)
end end
}) })
end, end,
@ -394,6 +344,3 @@ keymap.add {
["home"] = "project-search:move-to-start-of-doc", ["home"] = "project-search:move-to-start-of-doc",
["end"] = "project-search:move-to-end-of-doc" ["end"] = "project-search:move-to-end-of-doc"
} }
return projectsearch

View File

@ -25,16 +25,11 @@ local function set_scale(scale)
scale = common.clamp(scale, 0.2, 6) scale = common.clamp(scale, 0.2, 6)
-- save scroll positions -- save scroll positions
local v_scrolls = {} local scrolls = {}
local h_scrolls = {}
for _, view in ipairs(core.root_view.root_node:get_children()) do for _, view in ipairs(core.root_view.root_node:get_children()) do
local n = view:get_scrollable_size() local n = view:get_scrollable_size()
if n ~= math.huge and n > view.size.y then if n ~= math.huge and not view:is(CommandView) and n > view.size.y then
v_scrolls[view] = view.scroll.y / (n - view.size.y) scrolls[view] = view.scroll.y / (n - view.size.y)
end
local hn = view:get_h_scrollable_size()
if hn ~= math.huge and hn > view.size.x then
h_scrolls[view] = view.scroll.x / (hn - view.size.x)
end end
end end
@ -44,13 +39,12 @@ local function set_scale(scale)
if config.plugins.scale.mode == "ui" then if config.plugins.scale.mode == "ui" then
SCALE = scale SCALE = scale
style.padding.x = style.padding.x * s style.padding.x = style.padding.x * s
style.padding.y = style.padding.y * s style.padding.y = style.padding.y * s
style.divider_size = style.divider_size * s style.divider_size = style.divider_size * s
style.scrollbar_size = style.scrollbar_size * s style.scrollbar_size = style.scrollbar_size * s
style.expanded_scrollbar_size = style.expanded_scrollbar_size * s style.caret_width = style.caret_width * s
style.caret_width = style.caret_width * s 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]:set_size(s * style[name]:get_size())
@ -64,14 +58,10 @@ local function set_scale(scale)
end end
-- restore scroll positions -- restore scroll positions
for view, n in pairs(v_scrolls) do for view, n in pairs(scrolls) do
view.scroll.y = n * (view:get_scrollable_size() - view.size.y) view.scroll.y = n * (view:get_scrollable_size() - view.size.y)
view.scroll.to.y = view.scroll.y view.scroll.to.y = view.scroll.y
end end
for view, hn in pairs(h_scrolls) do
view.scroll.x = hn * (view:get_h_scrollable_size() - view.size.x)
view.scroll.to.x = view.scroll.x
end
core.redraw = true core.redraw = true
end end

View File

@ -39,11 +39,6 @@ end
function ToolbarView:toggle_visible() function ToolbarView:toggle_visible()
self.visible = not self.visible self.visible = not self.visible
if self.tooltip then
core.status_view:remove_tooltip()
self.tooltip = false
end
self.hovered_item = nil
end end
function ToolbarView:get_icon_width() function ToolbarView:get_icon_width()
@ -78,7 +73,6 @@ end
function ToolbarView:draw() function ToolbarView:draw()
if not self.visible then return end
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
@ -89,7 +83,6 @@ end
function ToolbarView:on_mouse_pressed(button, x, y, clicks) function ToolbarView:on_mouse_pressed(button, x, y, clicks)
if not self.visible then return end
local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks) local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks)
if caught then return caught end if caught then return caught end
core.set_active_view(core.last_active_view) core.set_active_view(core.last_active_view)
@ -101,7 +94,6 @@ end
function ToolbarView:on_mouse_moved(px, py, ...) function ToolbarView:on_mouse_moved(px, py, ...)
if not self.visible then return end
ToolbarView.super.on_mouse_moved(self, px, py, ...) ToolbarView.super.on_mouse_moved(self, px, py, ...)
self.hovered_item = nil self.hovered_item = nil
local x_min, x_max, y_min, y_max = self.size.x, 0, self.size.y, 0 local x_min, x_max, y_min, y_max = self.size.x, 0, self.size.y, 0

View File

@ -24,7 +24,7 @@ local tooltip_alpha_rate = 1
local function get_depth(filename) local function get_depth(filename)
local n = 1 local n = 1
for _ in filename:gmatch(PATHSEP) do for sep in filename:gmatch("[\\/]") do
n = n + 1 n = n + 1
end end
return n return n
@ -50,6 +50,20 @@ function TreeView:new()
self.item_icon_width = 0 self.item_icon_width = 0
self.item_text_spacing = 0 self.item_text_spacing = 0
self:add_core_hooks()
end
function TreeView:add_core_hooks()
-- When a file or directory is deleted we delete the corresponding cache entry
-- because if the entry is recreated we may use wrong information from cache.
local on_delete = core.on_dirmonitor_delete
core.on_dirmonitor_delete = function(dir, filepath)
local cache = self.cache[dir.name]
if cache then cache[filepath] = nil end
on_delete(dir, filepath)
end
end end
@ -72,7 +86,7 @@ function TreeView:get_cached(dir, item, dirname)
-- used only to identify the entry into the cache. -- used only to identify the entry into the cache.
local cache_name = item.filename .. (item.topdir and ":" or "") local cache_name = item.filename .. (item.topdir and ":" or "")
local t = dir_cache[cache_name] local t = dir_cache[cache_name]
if not t or t.type ~= item.type then if not t then
t = {} t = {}
local basename = common.basename(item.filename) local basename = common.basename(item.filename)
if item.topdir then if item.topdir then
@ -83,7 +97,7 @@ function TreeView:get_cached(dir, item, dirname)
else else
t.filename = item.filename t.filename = item.filename
t.depth = get_depth(item.filename) t.depth = get_depth(item.filename)
t.abs_filename = common.basepath(dirname) .. item.filename t.abs_filename = dirname .. PATHSEP .. item.filename
end end
t.name = basename t.name = basename
t.type = item.type t.type = item.type
@ -195,10 +209,10 @@ end
function TreeView:on_mouse_moved(px, py, ...) function TreeView:on_mouse_moved(px, py, ...)
if not self.visible then return end if not self.visible then return end
TreeView.super.on_mouse_moved(self, px, py, ...)
self.cursor_pos.x = px self.cursor_pos.x = px
self.cursor_pos.y = py self.cursor_pos.y = py
if TreeView.super.on_mouse_moved(self, px, py, ...) then if self.dragging_scrollbar then
-- mouse movement handled by the View (scrollbar)
self.hovered_item = nil self.hovered_item = nil
return return
end end
@ -518,19 +532,15 @@ local function is_primary_project_folder(path)
return core.project_dir == path return core.project_dir == path
end end
menu:register(function() return view.hovered_item end, {
local function treeitem() return view.hovered_item or view.selected_item end
menu:register(function() return core.active_view:is(TreeView) and treeitem() end, {
{ text = "Open in System", command = "treeview:open-in-system" }, { text = "Open in System", command = "treeview:open-in-system" },
ContextMenu.DIVIDER ContextMenu.DIVIDER
}) })
menu:register( menu:register(
function() function()
local item = treeitem() return view.hovered_item
return core.active_view:is(TreeView) and item and not is_project_folder(item.abs_filename) and not is_project_folder(view.hovered_item.abs_filename)
end, end,
{ {
{ text = "Rename", command = "treeview:rename" }, { text = "Rename", command = "treeview:rename" },
@ -540,8 +550,7 @@ menu:register(
menu:register( menu:register(
function() function()
local item = treeitem() return view.hovered_item and view.hovered_item.type == "dir"
return core.active_view:is(TreeView) and item and item.type == "dir"
end, end,
{ {
{ text = "New File", command = "treeview:new-file" }, { text = "New File", command = "treeview:new-file" },
@ -551,10 +560,9 @@ menu:register(
menu:register( menu:register(
function() function()
local item = treeitem() return view.hovered_item
return core.active_view:is(TreeView) and item and not is_primary_project_folder(view.hovered_item.abs_filename)
and not is_primary_project_folder(item.abs_filename) and is_project_folder(view.hovered_item.abs_filename)
and is_project_folder(item.abs_filename)
end, end,
{ {
{ text = "Remove directory", command = "treeview:remove-project-directory" }, { text = "Remove directory", command = "treeview:remove-project-directory" },
@ -595,10 +603,7 @@ command.add(nil, {
end end
}) })
command.add( command.add(TreeView, {
function()
return not menu.show_context_menu and core.active_view:extends(TreeView), TreeView
end, {
["treeview:next"] = function() ["treeview:next"] = function()
local item, _, item_y = view:get_next(view.selected_item) local item, _, item_y = view:get_next(view.selected_item)
view:set_selection(item, item_y) view:set_selection(item, item_y)
@ -669,24 +674,33 @@ command.add(
}) })
local function treeitem() return view.hovered_item or view.selected_item end
command.add( command.add(
function() function()
local item = treeitem() local item = treeitem()
return item ~= nil and (core.active_view == view or menu.show_context_menu), item 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, { end, {
["treeview:delete"] = function(item) ["treeview:delete"] = function(item)
local filename = item.abs_filename local filename = item.abs_filename
local relfilename = item.filename local relfilename = item.filename
if item.dir_name ~= core.project_dir then if item.dir_name ~= core.project_dir then
-- add secondary project dirs names to the file path to show -- add secondary project dirs names to the file path to show
relfilename = common.basepath(common.basename(item.dir_name)) .. PATHSEP .. relfilename relfilename = common.basename(item.dir_name) .. PATHSEP .. relfilename
end 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
local opt = { local opt = {
{ text = "Yes", default_yes = true }, { font = style.font, text = "Yes", default_yes = true },
{ text = "No", default_no = true } { font = style.font, text = "No" , default_no = true }
} }
core.nag_view:show( core.nag_view:show(
string.format("Delete %s", file_type), string.format("Delete %s", file_type),
@ -714,8 +728,16 @@ command.add(
end 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) ["treeview:rename"] = function(item)
local old_filename = item.filename local old_filename = item.filename
local old_abs_filename = item.abs_filename local old_abs_filename = item.abs_filename
@ -724,7 +746,7 @@ command.add(
submit = function(filename) submit = function(filename)
local abs_filename = filename local abs_filename = filename
if not common.is_absolute_path(filename) then if not common.is_absolute_path(filename) then
abs_filename = common.basepath(item.dir_name) .. filename abs_filename = item.dir_name .. PATHSEP .. filename
end end
local res, err = os.rename(old_abs_filename, abs_filename) local res, err = os.rename(old_abs_filename, abs_filename)
if res then -- successfully renamed if res then -- successfully renamed
@ -754,7 +776,7 @@ command.add(
core.command_view:enter("Filename", { core.command_view:enter("Filename", {
text = text, text = text,
submit = function(filename) submit = function(filename)
local doc_filename = common.basepath(item.dir_name) .. filename local doc_filename = item.dir_name .. PATHSEP .. filename
core.log(doc_filename) core.log(doc_filename)
local file = io.open(doc_filename, "a+") local file = io.open(doc_filename, "a+")
file:write("") file:write("")
@ -776,7 +798,7 @@ command.add(
core.command_view:enter("Folder Name", { core.command_view:enter("Folder Name", {
text = text, text = text,
submit = function(filename) submit = function(filename)
local dir_path = common.basepath(item.dir_name) .. filename local dir_path = item.dir_name .. PATHSEP .. filename
common.mkdirp(dir_path) common.mkdirp(dir_path)
core.log("Created %s", dir_path) core.log("Created %s", dir_path)
end, end,
@ -802,10 +824,9 @@ command.add(
local projectsearch = pcall(require, "plugins.projectsearch") local projectsearch = pcall(require, "plugins.projectsearch")
if projectsearch then if projectsearch then
menu:register(function() menu:register(function()
local item = treeitem() return view.hovered_item and view.hovered_item.type == "dir"
return item and item.type == "dir" end, {
end, { { text = "Find in directory", command = "treeview:search-in-directory" }
{ text = "Find in directory", command = "treeview:search-in-directory" }
}) })
command.add(function() command.add(function()
return view.hovered_item and view.hovered_item.type == "dir" return view.hovered_item and view.hovered_item.type == "dir"

View File

@ -1,53 +1,10 @@
-- mod-version:3 -- mod-version:3
local common = require "core.common" local core = require "core"
local config = require "core.config"
local command = require "core.command" local command = require "core.command"
local Doc = require "core.doc" local Doc = require "core.doc"
---@class config.plugins.trimwhitespace
---@field enabled boolean
---@field trim_empty_end_lines boolean
config.plugins.trimwhitespace = common.merge({
enabled = false,
trim_empty_end_lines = false,
config_spec = {
name = "Trim Whitespace",
{
label = "Enabled",
description = "Disable or enable the trimming of white spaces by default.",
path = "enabled",
type = "toggle",
default = false
},
{
label = "Trim Empty End Lines",
description = "Remove any empty new lines at the end of documents.",
path = "trim_empty_end_lines",
type = "toggle",
default = false
}
}
}, config.plugins.trimwhitespace)
---@class plugins.trimwhitespace local function trim_trailing_whitespace(doc)
local trimwhitespace = {}
---Disable whitespace trimming for a specific document.
---@param doc core.doc
function trimwhitespace.disable(doc)
doc.disable_trim_whitespace = true
end
---Re-enable whitespace trimming if previously disabled.
---@param doc core.doc
function trimwhitespace.enable(doc)
doc.disable_trim_whitespace = nil
end
---Perform whitespace trimming in all lines of a document except the
---line where the caret is currently positioned.
---@param doc core.doc
function trimwhitespace.trim(doc)
local cline, ccol = doc:get_selection() local cline, ccol = doc:get_selection()
for i = 1, #doc.lines do for i = 1, #doc.lines do
local old_text = doc:get_text(i, 1, i, math.huge) local old_text = doc:get_text(i, 1, i, math.huge)
@ -65,54 +22,16 @@ function trimwhitespace.trim(doc)
end end
end end
---Removes all empty new lines at the end of the document.
---@param doc core.doc
---@param raw_remove? boolean Perform the removal not registering to undo stack
function trimwhitespace.trim_empty_end_lines(doc, raw_remove)
for _=#doc.lines, 1, -1 do
local l = #doc.lines
if l > 1 and doc.lines[l] == "\n" then
local current_line = doc:get_selection()
if current_line == l then
doc:set_selection(l-1, math.huge, l-1, math.huge)
end
if not raw_remove then
doc:remove(l-1, math.huge, l, math.huge)
else
table.remove(doc.lines, l)
end
else
break
end
end
end
command.add("core.docview", { command.add("core.docview", {
["trim-whitespace:trim-trailing-whitespace"] = function(dv) ["trim-whitespace:trim-trailing-whitespace"] = function(dv)
trimwhitespace.trim(dv.doc) trim_trailing_whitespace(dv.doc)
end,
["trim-whitespace:trim-empty-end-lines"] = function(dv)
trimwhitespace.trim_empty_end_lines(dv.doc)
end, end,
}) })
local doc_save = Doc.save local save = Doc.save
Doc.save = function(self, ...) Doc.save = function(self, ...)
if trim_trailing_whitespace(self)
config.plugins.trimwhitespace.enabled save(self, ...)
and
not self.disable_trim_whitespace
then
trimwhitespace.trim(self)
if config.plugins.trimwhitespace.trim_empty_end_lines then
trimwhitespace.trim_empty_end_lines(self)
end
end
doc_save(self, ...)
end end
return trimwhitespace

View File

@ -7,7 +7,7 @@ local LogView = require "core.logview"
local function workspace_files_for(project_dir) local function workspace_files_for(project_dir)
local basename = common.basename(project_dir) local basename = common.basename(project_dir)
local workspace_dir = common.basepath(USERDIR) .. "ws" local workspace_dir = USERDIR .. PATHSEP .. "ws"
local info_wsdir = system.get_file_info(workspace_dir) local info_wsdir = system.get_file_info(workspace_dir)
if not info_wsdir then if not info_wsdir then
local ok, err = system.mkdir(workspace_dir) local ok, err = system.mkdir(workspace_dir)
@ -22,7 +22,7 @@ local function workspace_files_for(project_dir)
if file:sub(1, n) == basename then if file:sub(1, n) == basename then
local id = tonumber(file:sub(n + 1):match("^-(%d+)$")) local id = tonumber(file:sub(n + 1):match("^-(%d+)$"))
if id then if id then
coroutine.yield(common.basepath(workspace_dir) .. file, id) coroutine.yield(workspace_dir .. PATHSEP .. file, id)
end end
end end
end end
@ -52,7 +52,7 @@ local function get_workspace_filename(project_dir)
id = id + 1 id = id + 1
end end
local basename = common.basename(project_dir) local basename = common.basename(project_dir)
return common.basepath(USERDIR) .. "ws" .. PATHSEP .. basename .. "-" .. tostring(id) return USERDIR .. PATHSEP .. "ws" .. PATHSEP .. basename .. "-" .. tostring(id)
end end
@ -83,8 +83,7 @@ local function save_view(view)
filename = view.doc.filename, filename = view.doc.filename,
selection = { view.doc:get_selection() }, selection = { view.doc:get_selection() },
scroll = { x = view.scroll.to.x, y = view.scroll.to.y }, scroll = { x = view.scroll.to.x, y = view.scroll.to.y },
crlf = view.doc.crlf, text = not view.doc.filename and view.doc:get_text(1, 1, math.huge, math.huge)
text = view.doc.new_file and view.doc:get_text(1, 1, math.huge, math.huge)
} }
end end
if mt == LogView then return end if mt == LogView then return end
@ -93,8 +92,7 @@ local function save_view(view)
return { return {
type = "view", type = "view",
active = (core.active_view == view), active = (core.active_view == view),
module = name, module = name
scroll = { x = view.scroll.to.x, y = view.scroll.to.y, to = { x = view.scroll.to.x, y = view.scroll.to.y } },
} }
end end
end end
@ -107,6 +105,7 @@ local function load_view(t)
if not t.filename then if not t.filename then
-- document not associated to a file -- document not associated to a file
dv = DocView(core.open_doc()) dv = DocView(core.open_doc())
if t.text then dv.doc:insert(1, 1, t.text) end
else else
-- we have a filename, try to read the file -- we have a filename, try to read the file
local ok, doc = pcall(core.open_doc, t.filename) local ok, doc = pcall(core.open_doc, t.filename)
@ -114,11 +113,9 @@ local function load_view(t)
dv = DocView(doc) dv = DocView(doc)
end end
end end
-- doc view "dv" can be nil here if the filename associated to the document
-- cannot be read.
if dv and dv.doc then if dv and dv.doc then
if dv.doc.new_file and t.text then
dv.doc:insert(1, 1, t.text)
dv.doc.crlf = t.crlf
end
dv.doc:set_selection(table.unpack(t.selection)) dv.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_line1, dv.last_col1, dv.last_line2, dv.last_col2 = 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
@ -165,9 +162,6 @@ local function load_node(node, t)
if t.active_view == i then if t.active_view == i then
active_view = view active_view = view
end end
if not view:is(DocView) then
view.scroll = v.scroll
end
end end
end end
if active_view then if active_view then

View File

@ -1,70 +0,0 @@
---@meta
---
---Functionality that allows to monitor a directory or file for changes
---using the native facilities provided by the current operating system
---for better efficiency and performance.
---@class dirmonitor
dirmonitor = {}
---@alias dirmonitor.callback fun(fd_or_path:integer|string)
---
---Creates a new dirmonitor object.
---
---@return dirmonitor
function dirmonitor.new() end
---
---Monitors a directory or file for changes.
---
---In "multiple" mode you will need to call this method more than once to
---recursively monitor directories and files.
---
---In "single" mode you will only need to call this method for the parent
---directory and every sub directory and files will get automatically monitored.
---
---@param path string
---
---@return integer fd The file descriptor id assigned to the monitored path when
---the mode is "multiple", in "single" mode: 1 for success or -1 on failure.
function dirmonitor:watch(path) end
---
---Stops monitoring a file descriptor in "multiple" mode
---or in "single" mode a directory path.
---
---@param fd_or_path integer | string A file descriptor or path.
function dirmonitor:unwatch(fd_or_path) end
---
---Verify if the resources registered for monitoring have changed, should
---be called periodically to check for changes.
---
---The callback will be called for each file or directory that was:
---edited, removed or added. A file descriptor will be passed to the
---callback in "multiple" mode or a path in "single" mode.
---
---If an error occurred during the callback execution, the error callback will be called with the error object.
---This callback should not manipulate coroutines to avoid deadlocks.
---
---@param callback dirmonitor.callback
---@param error_callback fun(error: any): nil
---
---@return boolean? changes True when changes were detected.
function dirmonitor:check(callback, error_callback) end
---
---Get the working mode for the current file system monitoring backend.
---
---"multiple": various file descriptors are needed to recursively monitor a
---directory contents, backends: inotify and kqueue.
---
---"single": a single process takes care of monitoring a path recursively
---so no individual file descriptors are used, backends: win32 and fsevents.
---
---@return "single" | "multiple"
function dirmonitor:mode() end
return dirmonitor

View File

@ -10,7 +10,7 @@ ARGS = {}
ARCH = "Architecture-OperatingSystem" 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"
---The current text or ui scale. ---The current text or ui scale.

View File

@ -81,27 +81,27 @@ process.REDIRECT_DISCARD = 3
process.REDIRECT_STDOUT = 4 process.REDIRECT_STDOUT = 4
---@alias process.errortype ---@alias process.errortype
---| `process.ERROR_PIPE` ---|>'process.ERROR_PIPE'
---| `process.ERROR_WOULDBLOCK` ---| 'process.ERROR_WOULDBLOCK'
---| `process.ERROR_TIMEDOUT` ---| 'process.ERROR_TIMEDOUT'
---| `process.ERROR_INVAL` ---| 'process.ERROR_INVAL'
---| `process.ERROR_NOMEM` ---| 'process.ERROR_NOMEM'
---@alias process.streamtype ---@alias process.streamtype
---| `process.STREAM_STDIN` ---|>'process.STREAM_STDIN'
---| `process.STREAM_STDOUT` ---| 'process.STREAM_STDOUT'
---| `process.STREAM_STDERR` ---| 'process.STREAM_STDERR'
---@alias process.waittype ---@alias process.waittype
---| `process.WAIT_INFINITE` ---|>'process.WAIT_INFINITE'
---| `process.WAIT_DEADLINE` ---| 'process.WAIT_DEADLINE'
---@alias process.redirecttype ---@alias process.redirecttype
---| `process.REDIRECT_DEFAULT` ---|>'process.REDIRECT_DEFAULT'
---| `process.REDIRECT_PIPE` ---| 'process.REDIRECT_PIPE'
---| `process.REDIRECT_PARENT` ---| 'process.REDIRECT_PARENT'
---| `process.REDIRECT_DISCARD` ---| 'process.REDIRECT_DISCARD'
---| `process.REDIRECT_STDOUT` ---| 'process.REDIRECT_STDOUT'
--- ---
--- Options that can be passed to process.start() --- Options that can be passed to process.start()
@ -112,6 +112,7 @@ process.REDIRECT_STDOUT = 4
---@field public stdout process.redirecttype ---@field public stdout process.redirecttype
---@field public stderr process.redirecttype ---@field public stderr process.redirecttype
---@field public env table<string, string> ---@field public env table<string, string>
process.options = {}
--- ---
---Create and start a new process ---Create and start a new process
@ -232,6 +233,3 @@ function process:returncode() end
--- ---
---@return boolean ---@return boolean
function process:running() end function process:running() end
return process

View File

@ -31,9 +31,9 @@ regex.NOTEMPTY = 0x00000004
regex.NOTEMPTY_ATSTART = 0x00000008 regex.NOTEMPTY_ATSTART = 0x00000008
---@alias regex.modifiers ---@alias regex.modifiers
---| "i" # Case insesitive matching ---|>'"i"' # Case insesitive matching
---| "m" # Multiline matching ---| '"m"' # Multiline matching
---| "s" # Match all characters with dot (.) metacharacter even new lines ---| '"s"' # Match all characters with dot (.) metacharacter even new lines
--- ---
---Compiles a regular expression pattern that can be used to search in strings. ---Compiles a regular expression pattern that can be used to search in strings.
@ -41,8 +41,8 @@ regex.NOTEMPTY_ATSTART = 0x00000008
---@param pattern string ---@param pattern string
---@param options? regex.modifiers A string of one or more pattern modifiers. ---@param options? regex.modifiers A string of one or more pattern modifiers.
--- ---
---@return regex? regex Ready to use regular expression object or nil on error. ---@return regex|string regex Ready to use regular expression object or error
---@return string? error The error message if compiling the pattern failed. ---message if compiling the pattern failed.
function regex.compile(pattern, options) end function regex.compile(pattern, options) end
--- ---
@ -53,42 +53,5 @@ function regex.compile(pattern, options) end
---@param options? integer A bit field of matching options, eg: ---@param options? integer A bit field of matching options, eg:
---regex.NOTBOL | regex.NOTEMPTY ---regex.NOTBOL | regex.NOTEMPTY
--- ---
---@return integer? ... List of offsets where a match was found. ---@return table<integer, integer> list List of offsets where a match was found.
function regex:cmatch(subject, offset, options) end function regex:cmatch(subject, offset, options) end
---
---Returns an iterator function that, each time it is called, returns the
---next captures from `pattern` over the string subject.
---
---Example:
---```lua
--- s = "hello world hello world"
--- for hello, world in regex.gmatch("(hello)\\s+(world)", s) do
--- print(hello .. " " .. world)
--- end
---```
---
---@param pattern string
---@param subject string
---@param offset? integer
---
---@return fun():string, ...
function regex.gmatch(pattern, subject, offset) end
---
---Replaces the matched pattern globally on the subject with the given
---replacement, supports named captures ((?'name'<pattern>), ${name}) and
---$[1-9][0-9]* substitutions. Raises an error when failing to compile the
---pattern or by a substitution mistake.
---
---@param pattern regex|string
---@param subject string
---@param replacement string
---@param limit? integer Limits the number of substitutions that will be done.
---
---@return string? replaced_subject
---@return integer? total_replacements
function regex.gsub(pattern, subject, replacement, limit) end
return regex

View File

@ -14,17 +14,19 @@ renderer = {}
---@field public g number Green ---@field public g number Green
---@field public b number Blue ---@field public b number Blue
---@field public a number Alpha ---@field public a number Alpha
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 "'none'" | "'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 smoothing boolean
---@field public strikethrough boolean -- @field public strikethrough boolean
renderer.fontoptions = {}
--- ---
---@class renderer.font ---@class renderer.font
@ -152,6 +154,3 @@ function renderer.draw_rect(x, y, width, height, color) end
--- ---
---@return number x ---@return number x
function renderer.draw_text(font, text, x, y, color) end function renderer.draw_text(font, text, x, y, color) end
return renderer

View File

@ -101,14 +101,14 @@ function string.unext(s, charpos, index) end
---@param s string ---@param s string
---@param idx? integer ---@param idx? integer
---@param substring string ---@param substring string
---@return string new_string ---return string new_string
function string.uinsert(s, idx, substring) end function string.uinsert(s, idx, substring) end
---Equivalent to utf8.remove() ---Equivalent to utf8.remove()
---@param s string ---@param s string
---@param start? integer ---@param start? integer
---@param stop? integer ---@param stop? integer
---@return string new_string ---return string new_string
function string.uremove(s, start, stop) end function string.uremove(s, start, stop) end
---Equivalent to utf8.width() ---Equivalent to utf8.width()
@ -130,12 +130,12 @@ function string.uwidthindex(s, location, ambi_is_double, default_width) end
---Equivalent to utf8.title() ---Equivalent to utf8.title()
---@param s string ---@param s string
---@return string new_string ---return string new_string
function string.utitle(s) end function string.utitle(s) end
---Equivalent to utf8.fold() ---Equivalent to utf8.fold()
---@param s string ---@param s string
---@return string new_string ---return string new_string
function string.ufold(s) end function string.ufold(s) end
---Equivalent to utf8.ncasecmp() ---Equivalent to utf8.ncasecmp()

View File

@ -6,15 +6,15 @@
system = {} system = {}
---@alias system.fileinfotype ---@alias system.fileinfotype
---| "file" # It is a file. ---|>'"file"' # It is a file.
---| "dir" # It is a directory. ---| '"dir"' # It is a directory.
--- ---
---@class system.fileinfo ---@class system.fileinfo
---@field public modified number A timestamp in seconds. ---@field public modified number A timestamp in seconds.
---@field public size number Size in bytes. ---@field public size number Size in bytes.
---@field public type system.fileinfotype Type of file ---@field public type system.fileinfotype Type of file
---@field public symlink boolean The directory is a symlink. This field is only set on Linux and on directories. system.fileinfo = {}
--- ---
---Core function used to retrieve the current event been triggered by SDL. ---Core function used to retrieve the current event been triggered by SDL.
@ -24,7 +24,7 @@ system = {}
--- ---
---Window events: ---Window events:
--- * "quit" --- * "quit"
--- * "resized" -> width, height (in points) --- * "resized" -> width, height
--- * "exposed" --- * "exposed"
--- * "minimized" --- * "minimized"
--- * "maximized" --- * "maximized"
@ -38,18 +38,12 @@ system = {}
--- * "keypressed" -> key_name --- * "keypressed" -> key_name
--- * "keyreleased" -> key_name --- * "keyreleased" -> key_name
--- * "textinput" -> text --- * "textinput" -> text
--- * "textediting" -> text, start, length
--- ---
---Mouse events: ---Mouse events:
--- * "mousepressed" -> button_name, x, y, amount_of_clicks --- * "mousepressed" -> button_name, x, y, amount_of_clicks
--- * "mousereleased" -> button_name, x, y --- * "mousereleased" -> button_name, x, y
--- * "mousemoved" -> x, y, relative_x, relative_y --- * "mousemoved" -> x, y, relative_x, relative_y
--- * "mousewheel" -> y, x --- * "mousewheel" -> y
---
---Touch events:
--- * "touchpressed" -> x, y, finger_id
--- * "touchreleased" -> x, y, finger_id
--- * "touchmoved" -> x, y, distance_x, distance_y, finger_id
--- ---
---@return string type ---@return string type
---@return any? arg1 ---@return any? arg1
@ -61,16 +55,16 @@ function system.poll_event() end
--- ---
---Wait until an event is triggered. ---Wait until an event is triggered.
--- ---
---@param timeout? number Amount of seconds, also supports fractions ---@param timeout number Amount of seconds, also supports fractions
---of a second, eg: 0.01. If not provided, waits forever. ---of a second, eg: 0.01
--- ---
---@return boolean status True on success or false if there was an error or if no event was received. ---@return boolean status True on success or false if there was an error.
function system.wait_event(timeout) end function system.wait_event(timeout) end
--- ---
---Change the cursor type displayed on screen. ---Change the cursor type displayed on screen.
--- ---
---@param type string | "arrow" | "ibeam" | "sizeh" | "sizev" | "hand" ---@param type string | "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'"
function system.set_cursor(type) end function system.set_cursor(type) end
--- ---
@ -80,10 +74,10 @@ function system.set_cursor(type) end
function system.set_window_title(title) end function system.set_window_title(title) end
---@alias system.windowmode ---@alias system.windowmode
---| "normal" ---|>'"normal"'
---| "minimized" ---| '"minimized"'
---| "maximized" ---| '"maximized"'
---| "fullscreen" ---| '"fullscreen"'
--- ---
---Change the window mode. ---Change the window mode.
@ -107,12 +101,10 @@ function system.set_window_bordered(bordered) end
---When then window is run borderless (without system decorations), this ---When then window is run borderless (without system decorations), this
---function allows to set the size of the different regions that allow ---function allows to set the size of the different regions that allow
---for custom window management. ---for custom window management.
---To disable custom window management, call this function without any
---arguments
--- ---
---@param title_height? number Height of the window decoration ---@param title_height number
---@param controls_width? number Width of window controls (maximize,minimize and close buttons, etc). ---@param controls_width number This is for minimize, maximize, close, etc...
---@param resize_border? number The amount of pixels reserved for resizing ---@param resize_border number The amount of pixels reserved for resizing
function system.set_window_hit_test(title_height, controls_width, resize_border) end function system.set_window_hit_test(title_height, controls_width, resize_border) end
--- ---
@ -139,30 +131,6 @@ function system.set_window_size(width, height, x, y) end
---@return boolean ---@return boolean
function system.window_has_focus() end function system.window_has_focus() end
---
---Gets the mode of the window.
---
---@return system.windowmode
function system.get_window_mode() end
---
---Sets the position of the IME composition window.
---
---@param x number
---@param y number
---@param width number
---@param height number
function system.set_text_input_rect(x, y, width, height) end
---
---Clears any ongoing composition on the IME
function system.clear_ime() end
---
---Raise the main window and give it input focus.
---Note: may not always be obeyed by the users window manager.
function system.raise_window() end
--- ---
---Opens a message box to display an error message. ---Opens a message box to display an error message.
--- ---
@ -170,14 +138,6 @@ function system.raise_window() end
---@param message string ---@param message string
function system.show_fatal_error(title, message) end function system.show_fatal_error(title, message) end
---
---Deletes an empty directory.
---
---@param path string
---@return boolean success True if the operation suceeded, false otherwise
---@return string? message An error message if the operation failed
function system.rmdir(path) end
--- ---
---Change the current directory path which affects relative file operations. ---Change the current directory path which affects relative file operations.
---This function raises an error if the path doesn't exists. ---This function raises an error if the path doesn't exists.
@ -192,7 +152,6 @@ function system.chdir(path) end
---@param directory_path string ---@param directory_path string
--- ---
---@return boolean created True on success or false on failure. ---@return boolean created True on success or false on failure.
---@return string? message The error message if the operation failed.
function system.mkdir(directory_path) end function system.mkdir(directory_path) end
--- ---
@ -209,7 +168,7 @@ function system.list_dir(path) end
--- ---
---@param path string ---@param path string
--- ---
---@return string? abspath ---@return string
function system.absolute_path(path) end function system.absolute_path(path) end
--- ---
@ -221,28 +180,6 @@ function system.absolute_path(path) end
---@return string? message Error message in case of error. ---@return string? message Error message in case of error.
function system.get_file_info(path) end function system.get_file_info(path) end
---@alias system.fstype
---| "ext2/ext3"
---| "nfs"
---| "fuse"
---| "smb"
---| "smb2"
---| "reiserfs"
---| "tmpfs"
---| "ramfs"
---| "ntfs"
---
---Gets the filesystem type of a path.
---Note: This only works on Linux.
---
---@param path string Can be path to a directory or a file
---
---@return system.fstype
function system.get_fs_type(path) end
--- ---
---Retrieve the text currently stored on the clipboard. ---Retrieve the text currently stored on the clipboard.
--- ---
@ -256,7 +193,7 @@ function system.get_clipboard() end
function system.set_clipboard(text) end function system.set_clipboard(text) end
--- ---
---Get the process id of lite-xl itself. ---Get the process id of lite-xl it self.
--- ---
---@return integer ---@return integer
function system.get_process_id() end function system.get_process_id() end
@ -278,9 +215,7 @@ function system.sleep(seconds) end
---Similar to os.execute() but does not return the exit status of the ---Similar to os.execute() but does not return the exit status of the
---executed command and executes the process in a non blocking way by ---executed command and executes the process in a non blocking way by
---forking it to the background. ---forking it to the background.
---Note: Do not use this function, use the Process API instead.
--- ---
---@deprecated
---@param command string The command to execute. ---@param command string The command to execute.
function system.exec(command) end function system.exec(command) end
@ -302,27 +237,4 @@ function system.fuzzy_match(haystack, needle, file) end
--- ---
---@param opacity number A value from 0.0 to 1.0, the lower the value ---@param opacity number A value from 0.0 to 1.0, the lower the value
---the less visible the window will be. ---the less visible the window will be.
---@return boolean success True if the operation suceeded.
function system.set_window_opacity(opacity) end function system.set_window_opacity(opacity) end
---
---Loads a lua native module using the default Lua API or lite-xl native plugin API.
---Note: Never use this function directly.
---
---@param name string the name of the module
---@param path string the path to the shared library file
---@return number nargs the return value of the entrypoint
function system.load_native_plugin(name, path) end
---
---Compares two paths in the order used by TreeView.
---
---@param path1 string
---@param type1 system.fileinfotype
---@param path2 string
---@param type2 system.fileinfotype
---@return boolean compare_result True if path1 < path2
function system.path_compare(path1, type1, path2, type2) end
return system

View File

@ -131,7 +131,7 @@ function utf8extra.next(s, charpos, index) end
---@param s string ---@param s string
---@param idx? integer ---@param idx? integer
---@param substring string ---@param substring string
---@return string new_string ---return string new_string
function utf8extra.insert(s, idx, substring) end function utf8extra.insert(s, idx, substring) end
---Delete a substring in s. If neither start nor stop is given, delete the last ---Delete a substring in s. If neither start nor stop is given, delete the last
@ -141,7 +141,7 @@ function utf8extra.insert(s, idx, substring) end
---@param s string ---@param s string
---@param start? integer ---@param start? integer
---@param stop? integer ---@param stop? integer
---@return string new_string ---return string new_string
function utf8extra.remove(s, start, stop) end function utf8extra.remove(s, start, stop) end
---Calculate the width of UTF-8 string s. if ambi_is_double is given, the ---Calculate the width of UTF-8 string s. if ambi_is_double is given, the
@ -174,14 +174,14 @@ function utf8extra.widthindex(s, location, ambi_is_double, default_width) end
---is a number, it's treat as a code point and return a convert code point ---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. ---(number). utf8.lower/utf8.pper has the same extension.
---@param s string ---@param s string
---@return string new_string ---return string new_string
function utf8extra.title(s) end function utf8extra.title(s) end
---Convert UTF-8 string s to folded case, used to compare by ignore case. if s ---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 ---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. ---(number). utf8.lower/utf8.pper has the same extension.
---@param s string ---@param s string
---@return string new_string ---return string new_string
function utf8extra.fold(s) end function utf8extra.fold(s) end
---Compare a and b without case, -1 means a < b, 0 means a == b and 1 means a > b. ---Compare a and b without case, -1 means a < b, 0 means a == b and 1 means a > b.
@ -189,6 +189,3 @@ function utf8extra.fold(s) end
---@param b string ---@param b string
---@return integer result ---@return integer result
function utf8extra.ncasecmp(a, b) end function utf8extra.ncasecmp(a, b) end
return utf8extra

View File

@ -1,10 +1,11 @@
project('lite-xl', project('lite-xl',
['c'], ['c'],
version : '2.1.4', version : '2.1.0',
license : 'MIT', license : 'MIT',
meson_version : '>= 0.56', meson_version : '>= 0.47',
default_options : [ default_options : [
'c_std=gnu11' 'c_std=gnu11',
'wrap_mode=nofallback'
] ]
) )
@ -35,7 +36,6 @@ 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', version)
conf_data.set('PROJECT_ASSEMBLY_VERSION', meson.project_version() + '.0')
#=============================================================================== #===============================================================================
# Compiler Settings # Compiler Settings
@ -52,17 +52,14 @@ lite_cargs = ['-DSDL_MAIN_HANDLED', '-DPCRE2_STATIC']
if get_option('renderer') or host_machine.system() == 'darwin' if get_option('renderer') or host_machine.system() == 'darwin'
lite_cargs += '-DLITE_USE_SDL_RENDERER' lite_cargs += '-DLITE_USE_SDL_RENDERER'
endif endif
if get_option('arch_tuple') != ''
arch_tuple = get_option('arch_tuple')
else
arch_tuple = '@0@-@1@'.format(target_machine.cpu_family(), target_machine.system())
endif
lite_cargs += '-DLITE_ARCH_TUPLE="@0@"'.format(arch_tuple)
#=============================================================================== #===============================================================================
# Linker Settings # Linker Settings
#=============================================================================== #===============================================================================
lite_link_args = [] lite_link_args = []
if cc.get_id() == 'gcc' and get_option('buildtype') == 'release'
lite_link_args += ['-static-libgcc']
endif
if host_machine.system() == 'darwin' if host_machine.system() == 'darwin'
lite_link_args += ['-framework', 'CoreServices', '-framework', 'Foundation'] lite_link_args += ['-framework', 'CoreServices', '-framework', 'Foundation']
endif endif
@ -83,27 +80,16 @@ if not get_option('source-only')
'lua', # Fedora 'lua', # Fedora
] ]
if get_option('use_system_lua') foreach lua : lua_names
foreach lua : lua_names last_lua = (lua == lua_names[-1] or get_option('wrap_mode') == 'forcefallback')
last_lua = (lua == lua_names[-1] or get_option('wrap_mode') == 'forcefallback') lua_dep = dependency(lua, fallback: last_lua ? ['lua', 'lua_dep'] : [], required : last_lua,
lua_dep = dependency(lua, required : false, version: '>= 5.4',
)
if lua_dep.found()
break
endif
if last_lua
# If we could not find lua on the system and fallbacks are disabled
# try the compiler as a last ditch effort, since Lua has no official
# pkg-config support.
lua_dep = cc.find_library('lua', required : true)
endif
endforeach
else
lua_dep = dependency('', fallback: ['lua', 'lua_dep'], required : true,
default_options: default_fallback_options + ['default_library=static', 'line_editing=false', 'interpreter=false'] default_options: default_fallback_options + ['default_library=static', 'line_editing=false', 'interpreter=false']
) )
endif if lua_dep.found()
break
endif
endforeach
pcre2_dep = dependency('libpcre2-8', fallback: ['pcre2', 'libpcre2_8'], pcre2_dep = dependency('libpcre2-8', fallback: ['pcre2', 'libpcre2_8'],
default_options: default_fallback_options + ['default_library=static', 'grep=false', 'test=false'] default_options: default_fallback_options + ['default_library=static', 'grep=false', 'test=false']
@ -123,7 +109,6 @@ if not get_option('source-only')
sdl_options += 'use_atomic=enabled' sdl_options += 'use_atomic=enabled'
sdl_options += 'use_threads=enabled' sdl_options += 'use_threads=enabled'
sdl_options += 'use_timers=enabled' sdl_options += 'use_timers=enabled'
sdl_options += 'with_main=true'
# investigate if this is truly needed # investigate if this is truly needed
# Do not remove before https://github.com/libsdl-org/SDL/issues/5413 is released # Do not remove before https://github.com/libsdl-org/SDL/issues/5413 is released
sdl_options += 'use_events=enabled' sdl_options += 'use_events=enabled'
@ -156,24 +141,12 @@ if not get_option('source-only')
sdl_options += 'use_video_vulkan=disabled' sdl_options += 'use_video_vulkan=disabled'
sdl_options += 'use_video_offscreen=disabled' sdl_options += 'use_video_offscreen=disabled'
sdl_options += 'use_power=disabled' sdl_options += 'use_power=disabled'
sdl_options += 'system_iconv=disabled'
sdl_dep = dependency('sdl2', fallback: ['sdl2', 'sdl2_dep'], sdl_dep = dependency('sdl2', fallback: ['sdl2', 'sdl2_dep'],
default_options: default_fallback_options + sdl_options default_options: default_fallback_options + sdl_options
) )
if host_machine.system() == 'windows' lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl]
if sdl_dep.type_name() == 'internal'
sdlmain_dep = dependency('sdl2main', fallback: ['sdl2main_dep'])
else
sdlmain_dep = cc.find_library('SDL2main')
endif
else
sdlmain_dep = dependency('', required: false)
assert(not sdlmain_dep.found(), 'checking if fake dependency has been found')
endif
lite_deps = [lua_dep, sdl_dep, sdlmain_dep, freetype_dep, pcre2_dep, libm, libdl]
endif endif
#=============================================================================== #===============================================================================
# Install Configuration # Install Configuration
@ -182,11 +155,6 @@ if get_option('portable') or host_machine.system() == 'windows'
lite_bindir = '/' lite_bindir = '/'
lite_docdir = '/doc' lite_docdir = '/doc'
lite_datadir = '/data' lite_datadir = '/data'
configure_file(
input: 'resources/windows/lite-xl.exe.manifest.in',
output: 'lite-xl.exe.manifest',
configuration: conf_data
)
elif get_option('bundle') and host_machine.system() == 'darwin' elif get_option('bundle') and host_machine.system() == 'darwin'
lite_cargs += '-DMACOS_USE_BUNDLE' lite_cargs += '-DMACOS_USE_BUNDLE'
lite_bindir = 'Contents/MacOS' lite_bindir = 'Contents/MacOS'

View File

@ -2,6 +2,4 @@ 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', 'fsevents', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use') option('dirmonitor_backend', type : 'combo', value : '', choices : ['', 'inotify', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use')
option('arch_tuple', type : 'string', value : '', description: 'Specify a custom architecture tuple')
option('use_system_lua', type : 'boolean', value : false, description: 'Prefer System Lua over a the meson wrap')

View File

@ -1,24 +0,0 @@
# Resources
This folder contains resources that is used for building or packaging the project.
### Build
- `cross/*.txt`: Meson [cross files][1] for cross-compiling lite-xl on other platforms.
### Packaging
- `icons/icon.{icns,ico,inl,rc,svg}`: lite-xl icon in various formats.
- `linux/com.lite_xl.LiteXL.appdata.xml`: AppStream metadata.
- `linux/com.lite_xl.LiteXL.desktop`: Desktop file for Linux desktops.
- `macos/dmg-cover.png`: Background image for packaging macOS DMGs.
- `macos/Info.plist.in`: Template for generating `info.plist` on macOS. See `macos/macos-retina-display.md` for details.
- `macos/lite-xl-dmg.py`: Configuration options for dmgbuild for packaging macOS DMGs.
- `windows/001-lua-unicode.diff`: Patch for allowing Lua to load files with UTF-8 filenames on Windows.
### Development
- `include/lite_xl_plugin_api.h`: Native plugin API header. See the contents of `lite_xl_plugin_api.h` for more details.
[1]: https://mesonbuild.com/Cross-compilation.html

View File

@ -1,10 +0,0 @@
; Lite-XL AutoInstall
; $VER: Lite-XL AutoInstall 1.0 (15.02.2024)
; Get the path to the executable from the ENV variable
Set litexlPath `GetEnv AppDir/lite-xl`
copy LiteXL2/#? "$litexlPath" CLONE ALL
; Free the variable
UnSet litexlPath

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,29 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#02103d" }
style.background2 = { common.color "#02103d" }
style.background3 = { common.color "#02103d" }
style.text = { common.color "#0f6773" }
style.caret = { common.color "#6a8ca8" }
style.accent = { common.color "#6a8ca8" }
style.dim = { common.color "#303030" }
style.divider = { common.color "#151515" }
style.selection = { common.color "#242424" }
style.line_number = { common.color "#252525" }
style.line_number2 = { common.color "#444444" }
style.line_highlight = { common.color "#101010" }
style.scrollbar = { common.color "#252525" }
style.scrollbar2 = { common.color "#444444" }
style.syntax = {}
style.syntax["normal"] = { common.color "#a0a0a0" }
style.syntax["symbol"] = { common.color "#a0a0a0" }
style.syntax["comment"] = { common.color "#404040" }
style.syntax["keyword"] = { common.color "#dfdfdf" }
style.syntax["keyword2"] = { common.color "#dfdfdf" }
style.syntax["number"] = { common.color "#dfdfdf" }
style.syntax["literal"] = { common.color "#dfdfdf" }
style.syntax["string"] = { common.color "#132a52" }
style.syntax["operator"] = { common.color "#01A870" }
style.syntax["function"] = { common.color "#01A870" }

View File

@ -1,48 +0,0 @@
local style = require "core.style"
local common = require "core.common"
-- App --
style.background = { common.color "#222831" }
style.background2 = { common.color "#1e232b" }
style.background3 = { common.color "#1e232b" }
style.text = { common.color "#dfe2e7" }
style.caret = { common.color "#dfe2e7" }
style.accent = { common.color "#e2e4e9" }
style.dim = { common.color "#8893a5" }
style.divider = { common.color "#1e232b" }
style.selection = { common.color "#2c3440" }
style.line_number = { common.color "#8893a5" }
style.line_number2 = { common.color "#8893a5" }
style.line_highlight = { common.color "#242a34" }
style.scrollbar = { common.color "#2c3440" }
style.scrollbar2 = { common.color "#f5ad44" }
style.scrollbar_track = { common.color "#00000000" }
style.nagbar = { common.color "#db504a" }
style.nagbar_text = { common.color "#dfe2e7" }
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
style.drag_overlay = { common.color "#dfe2e733" }
style.drag_overlay_tab = { common.color "#f5ad44" }
style.good = { common.color "#47e2b1" }
style.warn = { common.color "#f5ad44" }
style.error = { common.color "#db504a" }
style.modified = { common.color "#448bf5" }
-- Syntax --
style.syntax = {}
style.syntax["normal"] = { common.color "#dfe2e7" }
style.syntax["symbol"] = { common.color "#dfe2e7" }
style.syntax["comment"] = { common.color "#8893a5" }
style.syntax["keyword"] = { common.color "#448bf5" }
style.syntax["keyword2"] = { common.color "#f5ad44" }
style.syntax["number"] = { common.color "#f5ad44" }
style.syntax["literal"] = { common.color "#45e1df" }
style.syntax["string"] = { common.color "#f5ad44" }
style.syntax["operator"] = { common.color "#dfe2e7" }
style.syntax["function"] = { common.color "#f786aa" }
-- Lint+ --
style.lint = {}
style.lint["info"] = { common.color "#448bf5" }
style.lint["hint"] = { common.color "#47e2b1" }
style.lint["warning"] = { common.color "#f5ad44" }
style.lint["error"] = { common.color "#db504a" }

View File

@ -1,29 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#03071e" }
style.background2 = { common.color "#03071e" }
style.background3 = { common.color "#03071e" }
style.text = { common.color "#ffa848" }
style.caret = { common.color "#ffa848" }
style.accent = { common.color "#ffb86c" }
style.dim = { common.color "#4f526b" }
style.divider = { common.color "#22242e" }
style.selection = { common.color "#4c5163" }
style.line_number = { common.color "#44475a" }
style.line_number2 = { common.color "#717796" }
style.line_highlight = { common.color "#2d303d" }
style.scrollbar = { common.color "#44475a" }
style.scrollbar2 = { common.color "#4c5163" }
style.syntax = {}
style.syntax["normal"] = { common.color "#f5faff" }
style.syntax["symbol"] = { common.color "#f5faff" }
style.syntax["comment"] = { common.color "#081355" }
style.syntax["keyword"] = { common.color "#fc0fc0" }
style.syntax["keyword2"] = { common.color "#05e6fa" }
style.syntax["number"] = { common.color "#7612c5" }
style.syntax["literal"] = { common.color "#7612c5" }
style.syntax["string"] = { common.color "#fdd017" }
style.syntax["operator"] = { common.color "#fc0fc0" }
style.syntax["function"] = { common.color "#05e6fa" }

View File

@ -1,28 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#073642" }
style.background2 = { common.color "#073642" }
style.background3 = { common.color "#073642" }
style.text = { common.color "#00d1d1" }
style.caret = { common.color "#f053f3" }
style.accent = { common.color "#f053f3" }
style.dim = { common.color "#586e75" }
style.divider = { common.color "#6c71c4" }
style.selection = { common.color "#415256" }
style.line_number = { common.color "#586e75" }
style.line_number2 = { common.color "#f053f3" }
style.line_highlight = { common.color "#415256" }
style.scrollbar = { common.color "#6c71c4" }
style.scrollbar2 = { common.color "#6c71c4" }
style.syntax["normal"] = { common.color "#00d1d1" }
style.syntax["symbol"] = { common.color "#00ff7f" }
style.syntax["comment"] = { common.color "#6c71c4" }
style.syntax["keyword"] = { common.color "#6c71c4" }
style.syntax["keyword2"] = { common.color "#6c71c4" }
style.syntax["number"] = { common.color "#00ff7f" }
style.syntax["literal"] = { common.color "#1586d2" }
style.syntax["string"] = { common.color "#f7f97d" }
style.syntax["operator"] = { common.color "#00ff7f" }
style.syntax["function"] = { common.color "#55ffff" }

View File

@ -1,28 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#282a36" }
style.background2 = { common.color "#21222b" }
style.background3 = { common.color "#21222b" }
style.text = { common.color "#7b81a6" }
style.caret = { common.color "#f8f8f0" }
style.accent = { common.color "#8be9fd" }
style.dim = { common.color "#4f5873" }
style.divider = { common.color "#1f2029" }
style.selection = { common.color "#44475a" }
style.line_number = { common.color "#53576e" }
style.line_number2 = { common.color "#f8f8f0" }
style.line_highlight = { common.color "#313442" }
style.scrollbar = { common.color "#44475a" }
style.scrollbar2 = { common.color "#ff79c6" }
style.syntax["normal"] = { common.color "#f8f8f2" }
style.syntax["symbol"] = { common.color "#f8f8f2" }
style.syntax["comment"] = { common.color "#6272a4" }
style.syntax["keyword"] = { common.color "#ff79c6" }
style.syntax["keyword2"] = { common.color "#ff79c6" }
style.syntax["number"] = { common.color "#bd93f9" }
style.syntax["literal"] = { common.color "#f1fa8c" }
style.syntax["string"] = { common.color "#f1fa8c" }
style.syntax["operator"] = { common.color "#ff79c6" }
style.syntax["function"] = { common.color "#50fa7b" }

View File

@ -1,38 +0,0 @@
local style = require "core.style"
local common = require "core.common"
math.randomseed(os.time())
local color = {
math.random(90, 255),
math.random(90, 255),
math.random(90, 255),
255
}
style.background = { common.color "#151515" }
style.background2 = { common.color "#151515" }
style.background3 = { common.color "#151515" }
style.text = { common.color "#707070" }
style.caret = { common.color "#dfdfdf" }
style.accent = { common.color "#d0d0d0" }
style.dim = { common.color "#303030" }
style.divider = { common.color "#151515" }
style.selection = { common.color "#303030" }
style.line_number = { common.color "#252525" }
style.line_number2 = { common.color "#444444" }
style.line_highlight = { common.color "#101010" }
style.scrollbar = { common.color "#252525" }
style.scrollbar2 = { common.color "#444444" }
style.syntax = {}
style.syntax["normal"] = { common.color "#a0a0a0" }
style.syntax["symbol"] = { common.color "#a0a0a0" }
style.syntax["comment"] = { common.color "#404040" }
style.syntax["keyword"] = { common.color "#dfdfdf" }
style.syntax["keyword2"] = { common.color "#dfdfdf" }
style.syntax["number"] = { common.color "#dfdfdf" }
style.syntax["literal"] = { common.color "#dfdfdf" }
style.syntax["string"] = { common.color "#dfdfdf" }
style.syntax["operator"] = color
style.syntax["function"] = color

View File

@ -1,29 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#151515" }
style.background2 = { common.color "#151515" }
style.background3 = { common.color "#151515" }
style.text = { common.color "#707070" }
style.caret = { common.color "#dfdfdf" }
style.accent = { common.color "#d0d0d0" }
style.dim = { common.color "#303030" }
style.divider = { common.color "#151515" }
style.selection = { common.color "#242424" }
style.line_number = { common.color "#252525" }
style.line_number2 = { common.color "#444444" }
style.line_highlight = { common.color "#101010" }
style.scrollbar = { common.color "#252525" }
style.scrollbar2 = { common.color "#444444" }
style.syntax = {}
style.syntax["normal"] = { common.color "#a0a0a0" }
style.syntax["symbol"] = { common.color "#a0a0a0" }
style.syntax["comment"] = { common.color "#404040" }
style.syntax["keyword"] = { common.color "#dfdfdf" }
style.syntax["keyword2"] = { common.color "#dfdfdf" }
style.syntax["number"] = { common.color "#dfdfdf" }
style.syntax["literal"] = { common.color "#dfdfdf" }
style.syntax["string"] = { common.color "#dfdfdf" }
style.syntax["operator"] = { common.color "#01A870" }
style.syntax["function"] = { common.color "#01A870" }

View File

@ -1,38 +0,0 @@
local style = require "core.style"
local common = require "core.common"
-- GitHub color palette
-- Ported by Andrey Proskurin (proskur1n)
local bg = { common.color "#22272e" }
local bg2 = { common.color "#2d333b" }
local fg = { common.color "#adbac7" }
local fgdim = { common.color "#768390" }
local red = { common.color "#f47067" }
local blue = { common.color "#6cb6ff" }
local purple = { common.color "#dcbdfb" }
style.background = bg
style.background2 = bg
style.background3 = bg
style.text = fg
style.caret = red
style.accent = blue
style.dim = fgdim
style.divider = { common.color "#444c56" }
style.selection = { common.color "#2e4c77" }
style.line_number = fgdim
style.line_number2 = fg
style.line_highlight = bg2
style.scrol = fgdim
style.scrollbar2 = fg
style.syntax["normal"] = fg
style.syntax["symbol"] = fg
style.syntax["comment"] = fgdim
style.syntax["keyword"] = red
style.syntax["keyword2"] = red
style.syntax["number"] = blue
style.syntax["literal"] = blue
style.syntax["string"] = { common.color "#96d0ff" }
style.syntax["operator"] = fg
style.syntax["function"] = blue

View File

@ -1,41 +0,0 @@
local style = require "core.style"
local common = require "core.common"
-- GitHub color palette
-- Ported by Andrey Proskurin (proskur1n)
local bg = { common.color "#0d1117" }
local bg2 = { common.color "#161925" }
local fg = { common.color "#adbac7" }
local fgdim = { common.color "#768390" }
local red = { common.color "#f47067" }
local blue = { common.color "#6cb6ff" }
local purple = { common.color "#dcbdfb" }
style.background = bg
style.background2 = bg
style.background3 = bg2
style.text = fg
style.caret = red
style.accent = blue
style.dim = fgdim
style.divider = { common.color "#444c56" }
style.selection = { common.color "#2e4c77" }
style.line_number = fgdim
style.line_number2 = fg
style.line_highlight = {common.color "#1e202e"}
style.scrollbar = fgdim
style.scrollbar2 = fg
style.syntax["normal"] = fg
style.syntax["symbol"] = fg
style.syntax["comment"] = fgdim
style.syntax["keyword"] = red
style.syntax["keyword2"] = red
style.syntax["number"] = blue
style.syntax["literal"] = blue
style.syntax["string"] = { common.color "#96d0ff" }
style.syntax["operator"] = fg
style.syntax["function"] = blue
style.guide = { common.color "#404040" } -- indentguide

View File

@ -1,28 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#282828" }
style.background2 = { common.color "#1d2021" }
style.background3 = { common.color "#1d2021" }
style.text = { common.color "#928374" }
style.caret = { common.color "#fbf1c7" }
style.accent = { common.color "#ebdbb2" }
style.dim = { common.color "#928374" }
style.divider = { common.color "#1d2021" }
style.selection = { common.color "#3c3836" }
style.line_number = { common.color "#928374" }
style.line_number2 = { common.color "#ebdbb2" }
style.line_highlight = { common.color "#32302f" }
style.scrollbar = { common.color "#928374" }
style.scrollbar2 = { common.color "#fbf1c7" }
style.syntax["normal"] = { common.color "#ebdbb2" }
style.syntax["symbol"] = { common.color "#ebdbb2" }
style.syntax["comment"] = { common.color "#928374" }
style.syntax["keyword"] = { common.color "#fb4934" }
style.syntax["keyword2"] = { common.color "#83a598" }
style.syntax["number"] = { common.color "#d3869b" }
style.syntax["literal"] = { common.color "#d3869b" }
style.syntax["string"] = { common.color "#b8bb26" }
style.syntax["operator"] = { common.color "#ebdbb2" }
style.syntax["function"] = { common.color "#8ec07c" }

View File

@ -1,37 +0,0 @@
-- Colors from: https://github.com/nanotech/jellybeans.vim
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#151515" }
style.background2 = { common.color "#212121" }
style.background3 = { common.color "#212121" }
style.text = { common.color "#e8e8d3" }
style.caret = { common.color "#e8e8d3" }
style.accent = { common.color "#597bc5" } -- Text in autocomplete and command, col(>80) in satusbar
style.dim = { common.color "#888888" } -- Text of nonactive tabs, prefix in log
style.divider = { common.color "#151515" }
style.selection = { common.color "#404040" }
style.line_number = { common.color "#3b3b3b" }
style.line_number2 = { common.color "#888888" } -- Number on line with caret
style.line_highlight = { common.color "#191919"}
style.scrollbar = { common.color "#2e2e2e" }
style.scrollbar2 = { common.color "#3b3b3b" } -- Hovered
style.syntax["normal"] = { common.color "#6b8b9b" }
style.syntax["symbol"] = { common.color "#e8e8d3" }
style.syntax["comment"] = { common.color "#888888" }
style.syntax["keyword"] = { common.color "#8197bf" } -- local function end, if case
style.syntax["keyword2"] = { common.color "#FFB964" } -- self, int float
style.syntax["number"] = { common.color "#cf6a4c" }
style.syntax["literal"] = { common.color "#8FBFDC" }
style.syntax["string"] = { common.color "#99ad6a" }
style.syntax["operator"] = { common.color "#8FBFDC"} -- = + - / < >
style.syntax["function"] = { common.color "#FAD07A" }
-- PLUGINS
style.linter_warning = { common.color "#d8ad4c" } -- linter
style.bracketmatch_color = { common.color "#8197bf" } -- bracketmatch
style.guide = { common.color "#3b3b3b" }
style.guide_highlight = { common.color "#5b5b5b" } -- indentguide
style.guide_width = 1 -- indentguide

View File

@ -1,32 +0,0 @@
-- Liqube Dark Code for Lite <liqube.com>
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#13171e" }
style.background2 = { common.color "#21252b" }
style.background3 = { common.color "#21252b" }
style.text = { common.color "#abb2bf" }
style.caret = { common.color "#abb2bf" }
style.accent = { common.color "#ffffff" }
style.dim = { common.color "#545e70" }
style.divider = { common.color "#242223" }
style.selection = { common.color "#3e4451" }
style.line_number = { common.color "#323641" }
style.line_number2 = { common.color "#596275" }
style.line_highlight = { common.color "#1c1f25" }
style.scrollbar = { common.color "#3d3f43" }
style.scrollbar2 = { common.color "#595b5f" }
style.guide = { common.color "#1c1f25" } -- indentguide
style.syntax["normal"] = { common.color "#abb2bf" }
style.syntax["symbol"] = { common.color "#71a9d7" }
style.syntax["comment"] = { common.color "#5c6370" }
style.syntax["keyword"] = { common.color "#98c875" }
style.syntax["keyword2"] = { common.color "#ffffff" }
style.syntax["number"] = { common.color "#ffffff" }
style.syntax["literal"] = { common.color "#ea5964" }
style.syntax["string"] = { common.color "#ea5964" }
style.syntax["operator"] = { common.color "#657085" }
style.syntax["function"] = { common.color "#ffffff" }
style.syntax["preprocessor"] = { common.color "#98c875" } -- thinking ahead

View File

@ -1,28 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#303841" }
style.background2 = { common.color "#1d2227" }
style.background3 = { common.color "#1d2227" }
style.text = { common.color "#9ea191" }
style.caret = { common.color "#61efce" }
style.accent = { common.color "#ffd152" }
style.dim = { common.color "#4c5863" }
style.divider = { common.color "#242223" }
style.selection = { common.color "#4c5863" }
style.line_number = { common.color "#bfc5d0" }
style.line_number2 = { common.color "#848b95" }
style.line_highlight = { common.color "#303841" }
style.scrollbar = { common.color "#696f75" }
style.scrollbar2 = { common.color "#444b53" }
style.syntax["normal"] = { common.color "#d7dde9" }
style.syntax["symbol"] = { common.color "#d8dee9" }
style.syntax["comment"] = { common.color "#a6acb9" }
style.syntax["keyword"] = { common.color "#e55e66" }
style.syntax["keyword2"] = { common.color "#ef6179" }
style.syntax["number"] = { common.color "#ffd152" }
style.syntax["literal"] = { common.color "#e75550" }
style.syntax["string"] = { common.color "#939d5d" }
style.syntax["operator"] = { common.color "#c2674f" }
style.syntax["function"] = { common.color "#6699ca" }

View File

@ -1,29 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#080808" }
style.background2 = { common.color "#080808" }
style.background3 = { common.color "#101010" }
style.text = { common.color "#707070" }
style.caret = { common.color "#ffffff" }
style.accent = { common.color "#d0d0d0" }
style.dim = { common.color "#303030" }
style.divider = { common.color "#080808" }
style.selection = { common.color "#242424" }
style.line_number = { common.color "#202020" }
style.line_number2 = { common.color "#707070" }
style.line_highlight = { common.color "#101010" }
style.scrollbar = { common.color "#252525" }
style.scrollbar2 = { common.color "#303030" }
style.syntax = {}
style.syntax["normal"] = { common.color "#a0a0a0" }
style.syntax["symbol"] = { common.color "#a0a0a0" }
style.syntax["comment"] = { common.color "#404040" }
style.syntax["keyword"] = { common.color "#f0f0f0" }
style.syntax["keyword2"] = { common.color "#f0f0f0" }
style.syntax["number"] = { common.color "#f0f0f0" }
style.syntax["literal"] = { common.color "#f0f0f0" }
style.syntax["string"] = { common.color "#f0f0f0" }
style.syntax["operator"] = { common.color "#f0f0f0" }
style.syntax["function"] = { common.color "#a0a0a0" }

View File

@ -1,28 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#272822" }
style.background2 = { common.color "#22231C" }
style.background3 = { common.color "#22231C" }
style.text = { common.color "#9ea191" }
style.caret = { common.color "#F8F8F0" }
style.accent = { common.color "#F8F8F2" }
style.dim = { common.color "#5e6052" }
style.divider = { common.color "#1b1c17" }
style.selection = { common.color "#49483E" }
style.line_number = { common.color "#75715E" }
style.line_number2 = { common.color "#d2d0c6" }
style.line_highlight = { common.color "#36372f" }
style.scrollbar = { common.color "#49483E" }
style.scrollbar2 = { common.color "#636254" }
style.syntax["normal"] = { common.color "#F8F8F2" }
style.syntax["symbol"] = { common.color "#F8F8F2" }
style.syntax["comment"] = { common.color "#75715E" }
style.syntax["keyword"] = { common.color "#F92672" }
style.syntax["keyword2"] = { common.color "#66DAEF" }
style.syntax["number"] = { common.color "#AE81FF" }
style.syntax["literal"] = { common.color "#AE81FF" }
style.syntax["string"] = { common.color "#E6DB74" }
style.syntax["operator"] = { common.color "#F8F8F2" }
style.syntax["function"] = { common.color "#A6E22E" }

View File

@ -1,28 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#282923" }
style.background2 = { common.color "#181915" }
style.background3 = { common.color "#181915" }
style.text = { common.color "#9ea191" }
style.caret = { common.color "#f8f8f2" }
style.accent = { common.color "#f8f8f2" }
style.dim = { common.color "#5e6052" }
style.divider = { common.color "#1b1c17" }
style.selection = { common.color "#3a3a32" }
style.line_number = { common.color "#90918b" }
style.line_number2 = { common.color "#d2d0c6" }
style.line_highlight = { common.color "#282923" }
style.scrollbar = { common.color "#63635f" }
style.scrollbar2 = { common.color "#3d3d38" }
style.syntax["normal"] = { common.color "#f8f8f2" }
style.syntax["symbol"] = { common.color "#f8f8f2" }
style.syntax["comment"] = { common.color "#75715E" }
style.syntax["keyword"] = { common.color "#f92472" }
style.syntax["keyword2"] = { common.color "#f92472" }
style.syntax["number"] = { common.color "#ac80ff" }
style.syntax["literal"] = { common.color "#e7db74" }
style.syntax["string"] = { common.color "#e7db74" }
style.syntax["operator"] = { common.color "#f92472" }
style.syntax["function"] = { common.color "#5cd5ef" }

View File

@ -1,39 +0,0 @@
local style = require "core.style"
local common = require "core.common"
local config = require "core.config"
style.background = { common.color "#2E3440" }
style.background2 = { common.color "#2E3440" }
style.background3 = { common.color "#3B4252" }
style.text = { common.color "#D8DEE9" }
style.caret = { common.color "#D8DEE9" }
style.accent = { common.color "#88C0D0" }
style.dim = { common.color "#d8dee966" }
style.divider = { common.color "#3B4252" }
style.selection = { common.color "#434C5ECC" }
style.line_number = { common.color "#4C566A" }
style.line_number2 = { common.color "#D8DEE9" }
style.line_highlight = { common.color "#3B4252" }
style.scrollbar = { common.color "#434c5eaa" }
style.scrollbar2 = { common.color "#434c5e" }
style.good = { common.color "#72b886cc" }
style.warn = { common.color "#d08770" }
style.error = { common.color "#bf616a" }
style.modified = { common.color "#ebcb8b" }
style.syntax["normal"] = { common.color "#ECEFF4" }
style.syntax["symbol"] = { common.color "#D8DEE9" }
style.syntax["comment"] = { common.color "#616E88" }
style.syntax["keyword"] = { common.color "#81A1C1" }
style.syntax["keyword2"] = { common.color "#81A1C1" }
style.syntax["number"] = { common.color "#B48EAD" }
style.syntax["literal"] = { common.color "#81A1C1" }
style.syntax["string"] = { common.color "#A3BE8C" }
style.syntax["operator"] = { common.color "#81A1C1" }
style.syntax["function"] = { common.color "#88C0D0" }
config.highlight_current_line = "no_selection"
style.guide = { common.color "#434c5eb3" }
style.bracketmatch_color = { common.color "#8fbcbb" }

View File

@ -1,29 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#282c34" }
style.background2 = { common.color "#21252B" }
style.background3 = { common.color "#21252B" }
style.text = { common.color "#abb2bf" }
style.caret = { common.color "#528bff" }
style.accent = { common.color "#ffffff" }
style.dim = { common.color "#4f5873" }
style.divider = { common.color "#181A1F" }
style.selection = { common.color "#383D49" }
style.line_number = { common.color "#53576e" }
style.line_number2 = { common.color "#666B76" }
style.line_highlight = { common.color "#2C333E" }
style.scrollbar = { common.color "#4f5873" }
style.scrollbar2 = { common.color "#3060C1" }
style.syntax["normal"] = { common.color "#abb2bf" }
style.syntax["symbol"] = { common.color "#abb2bf" }
style.syntax["comment"] = { common.color "#5f697a" }
style.syntax["keyword"] = { common.color "#cd74e8" }
style.syntax["keyword2"] = { common.color "#eb6772" }
style.syntax["number"] = { common.color "#db9d63" }
style.syntax["literal"] = { common.color "#e6c07b" }
style.syntax["string"] = { common.color "#9acc76" }
style.syntax["operator"] = { common.color "#56B6C2" }
style.syntax["function"] = { common.color "#5cb3fa" }

View File

@ -1,31 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#242424" }
style.background2 = { common.color "#252528" }
style.background3 = { common.color "#44475A" }
style.text = { common.color "#fffff0" }
style.caret = { common.color "#69FF94" }
style.accent = { common.color "#ff0fff" }
style.dim = { common.color "#0fffff" }
style.divider = { common.color "#7b7f8b" }
style.selection = { common.color "#48484f" }
style.selectionhighlight = { common.color "#dddeee" }
style.line_number = { common.color "#525259" }
style.line_number2 = { common.color "#f6f6e0" }
style.line_highlight = { common.color "#343438" }
style.scrollbar = { common.color "#414146" }
style.scrollbar2 = { common.color "#4b4bff" }
style.syntax["normal"] = { common.color "#e1e1e6" }
style.syntax["symbol"] = { common.color "#97e1f1" }
style.syntax["comment"] = { common.color "#676b6f" }
style.syntax["keyword"] = { common.color "#E58AC9" }
style.syntax["keyword2"] = { common.color "#F77483" }
style.syntax["number"] = { common.color "#FFA94D" }
style.syntax["literal"] = { common.color "#ee6666" }
style.syntax["string"] = { common.color "#f7c95c" }
style.syntax["operator"] = { common.color "#93DDFA" }
style.syntax["function"] = { common.color "#bf9eee" }

View File

@ -1,29 +0,0 @@
local style = require "core.style"
local common = require "core.common"
style.background = { common.color "#222226" }
style.background2 = { common.color "#252528" }
style.background3 = { common.color "#1e1e21" }
style.text = { common.color "#dddddd" }
style.caret = { common.color "#aeafad" }
style.accent = { common.color "#0097fb" }
style.dim = { common.color "#9393a5" }
style.divider = { common.color "#1E1E1E" }
style.selection = { common.color "#264f78" }
style.line_number = { common.color "#858585" }
style.line_number2 = { common.color "#c6c6c6" }
style.line_highlight = { common.color "#2b2b2f"}
style.scrollbar = { common.color "#313136" }
style.scrollbar2 = { common.color "#bfbfbf" }
style.syntax["normal"] = { common.color "#dddddd" }
style.syntax["symbol"] = { common.color "#e06c75" }
style.syntax["comment"] = { common.color "#c5c5c5" }
style.syntax["keyword"] = { common.color "#61afef" }
style.syntax["keyword2"] = { common.color "#56B6C2" }
style.syntax["number"] = { common.color "#d19a66" }
style.syntax["literal"] = { common.color "#61AFEF" }
style.syntax["string"] = { common.color "#98C379" }
style.syntax["operator"] = { common.color "#dddddd" }
style.syntax["function"] = { common.color "#c678dd" }

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