Compare commits
32 Commits
amiga-2.0
...
os4-1.16.1
Author | SHA1 | Date |
---|---|---|
George Sokianos | 1279fde93b | |
George Sokianos | 7a347ca436 | |
George Sokianos | 7930499e6e | |
George Sokianos | 9908e129a6 | |
George Sokianos | fb3d36da43 | |
George Sokianos | 94807d505c | |
George Sokianos | 44cd036b7a | |
George Sokianos | 3c7da82ad2 | |
George Sokianos | d30a9622a5 | |
George Sokianos | 2fdf19ec49 | |
George Sokianos | 9f656f8ab4 | |
George Sokianos | 32a3d4b933 | |
George Sokianos | c5309e04d6 | |
George Sokianos | 1c3f766e6b | |
George Sokianos | de6c0fd575 | |
George Sokianos | 7bd164b17e | |
George Sokianos | de3e4815ee | |
George Sokianos | b5d4f3f0f8 | |
George Sokianos | a788ac871b | |
George Sokianos | 1f27d6f923 | |
George Sokianos | 5af782b884 | |
George Sokianos | 95ece12d74 | |
George Sokianos | 03f2818657 | |
George Sokianos | 689901daca | |
George Sokianos | 4499f1f111 | |
George Sokianos | 14d813cea1 | |
George Sokianos | 2c711138d7 | |
George Sokianos | ff535843e8 | |
George Sokianos | fdd2f3af33 | |
George Sokianos | 7c85530e92 | |
George Sokianos | e13efe91b4 | |
George Sokianos | c155f797cf |
|
@ -1,11 +0,0 @@
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
indent_size = 2
|
|
||||||
indent_style = space
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[meson.build]
|
|
||||||
indent_size = 4
|
|
|
@ -1,7 +1,3 @@
|
||||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
|
||||||
# See https://help.github.com/en/articles/dealing-with-line-endings
|
|
||||||
* text=auto
|
|
||||||
|
|
||||||
winlib/* linguist-vendored
|
winlib/* linguist-vendored
|
||||||
src/lib/* linguist-vendored
|
src/lib/* linguist-vendored
|
||||||
resources/icons/icon.inl linguist-vendored
|
icon.inl linguist-vendored
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
"Category: CI":
|
|
||||||
- .github/workflows/*
|
|
||||||
|
|
||||||
"Category: Meta":
|
|
||||||
- ./*
|
|
||||||
- .github/*
|
|
||||||
- .github/ISSUE_TEMPLATE/*
|
|
||||||
- .github/PULL_REQUEST_TEMPLATE/*
|
|
||||||
- .gitignore
|
|
||||||
|
|
||||||
"Category: Build System":
|
|
||||||
- meson.build
|
|
||||||
- meson_options.txt
|
|
||||||
- subprojects/*
|
|
||||||
|
|
||||||
"Category: Documentation":
|
|
||||||
- docs/**/*
|
|
||||||
|
|
||||||
"Category: Resources":
|
|
||||||
- resources/**/*
|
|
||||||
|
|
||||||
"Category: Themes":
|
|
||||||
- data/colors/*
|
|
||||||
|
|
||||||
"Category: Lua Core":
|
|
||||||
- data/core/**/*
|
|
||||||
|
|
||||||
"Category: Fonts":
|
|
||||||
- data/fonts/*
|
|
||||||
|
|
||||||
"Category: Plugins":
|
|
||||||
- data/plugins/*
|
|
||||||
|
|
||||||
"Category: C Core":
|
|
||||||
- src/**/*
|
|
||||||
|
|
||||||
"Category: Libraries":
|
|
||||||
- lib/**/*
|
|
|
@ -1,16 +0,0 @@
|
||||||
name: "Pull Request Labeler"
|
|
||||||
on:
|
|
||||||
- pull_request_target
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Apply Type Label
|
|
||||||
uses: actions/labeler@v3
|
|
||||||
with:
|
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
sync-labels: "" # works around actions/labeler#104
|
|
|
@ -1,20 +1,12 @@
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_linux:
|
build-linux:
|
||||||
name: Linux
|
name: Build Linux
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-18.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
config:
|
config:
|
||||||
|
@ -24,110 +16,48 @@ jobs:
|
||||||
CC: ${{ matrix.config.cc }}
|
CC: ${{ matrix.config.cc }}
|
||||||
CXX: ${{ matrix.config.cxx }}
|
CXX: ${{ matrix.config.cxx }}
|
||||||
steps:
|
steps:
|
||||||
- name: Set Environment Variables
|
|
||||||
if: ${{ matrix.config.cc == 'gcc' }}
|
|
||||||
run: |
|
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
||||||
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
|
||||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-linux-$(uname -m)-portable" >> "$GITHUB_ENV"
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Python Setup
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.6
|
||||||
- name: Update Packages
|
- name: Install dependencies
|
||||||
run: sudo apt-get update
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: bash scripts/install-dependencies.sh --debug
|
|
||||||
- name: Build
|
|
||||||
run: |
|
run: |
|
||||||
bash --version
|
sudo apt-get install -qq libsdl2-dev libfreetype6 ninja-build
|
||||||
bash scripts/build.sh --debug --forcefallback --portable
|
pip3 install meson
|
||||||
- name: Package
|
- name: Build package
|
||||||
if: ${{ matrix.config.cc == 'gcc' }}
|
run: bash build-packages.sh x86-64
|
||||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --binary
|
- name: upload packages
|
||||||
- name: Upload Artifacts
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.config.cc == 'gcc' }}
|
|
||||||
with:
|
|
||||||
name: Linux Artifacts
|
|
||||||
path: ${{ env.INSTALL_NAME }}.tar.gz
|
|
||||||
|
|
||||||
build_macos:
|
|
||||||
name: macOS (x86_64)
|
|
||||||
runs-on: macos-11
|
|
||||||
env:
|
|
||||||
CC: clang
|
|
||||||
CXX: clang++
|
|
||||||
steps:
|
|
||||||
- name: System Information
|
|
||||||
run: |
|
|
||||||
system_profiler SPSoftwareDataType
|
|
||||||
bash --version
|
|
||||||
gcc -v
|
|
||||||
xcodebuild -version
|
|
||||||
- name: Set Environment Variables
|
|
||||||
run: |
|
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
||||||
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
|
||||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-macos-$(uname -m)" >> "$GITHUB_ENV"
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Python Setup
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: bash scripts/install-dependencies.sh --debug
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
bash --version
|
|
||||||
bash scripts/build.sh --bundle --debug --forcefallback
|
|
||||||
- name: Create DMG Image
|
|
||||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --dmg
|
|
||||||
- name: Upload DMG Image
|
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: macOS DMG Image
|
name: Ubuntu Package
|
||||||
path: ${{ env.INSTALL_NAME }}.dmg
|
path: lite-xl-linux-*.tar.gz
|
||||||
|
|
||||||
build_windows_msys2:
|
build-macox:
|
||||||
name: Windows
|
name: Build Mac OS X
|
||||||
runs-on: windows-2019
|
runs-on: macos-10.15
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
msystem: [MINGW32, MINGW64]
|
config:
|
||||||
defaults:
|
# - { name: "GCC", cc: gcc-10, cxx: g++-10 }
|
||||||
run:
|
- { name: "clang", cc: clang, cxx: clang++ }
|
||||||
shell: msys2 {0}
|
env:
|
||||||
|
CC: ${{ matrix.config.cc }}
|
||||||
|
CXX: ${{ matrix.config.cxx }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: msys2/setup-msys2@v2
|
- name: Set up Python
|
||||||
with:
|
uses: actions/setup-python@v2
|
||||||
msystem: ${{ matrix.msystem }}
|
with:
|
||||||
update: true
|
python-version: 3.9
|
||||||
install: >-
|
- name: Install dependencies
|
||||||
base-devel
|
run: |
|
||||||
git
|
pip3 install meson
|
||||||
zip
|
brew install ninja sdl2
|
||||||
- name: Set Environment Variables
|
- name: Build package
|
||||||
run: |
|
run: bash build-packages.sh x86-64
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
- name: upload packages
|
||||||
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
uses: actions/upload-artifact@v2
|
||||||
if [[ "${MSYSTEM}" == "MINGW64" ]]; then
|
with:
|
||||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-windows-x86_64" >> "$GITHUB_ENV"
|
name: Mac OS X Package
|
||||||
else
|
path: lite-xl-macosx-*.zip
|
||||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-windows-i686" >> "$GITHUB_ENV"
|
|
||||||
fi
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: bash scripts/install-dependencies.sh --debug
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
bash --version
|
|
||||||
bash scripts/build.sh -U --debug --forcefallback
|
|
||||||
- name: Package
|
|
||||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --binary
|
|
||||||
- name: Upload Artifacts
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: Windows Artifacts
|
|
||||||
path: ${{ env.INSTALL_NAME }}.zip
|
|
||||||
|
|
|
@ -1,183 +0,0 @@
|
||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: Release Version
|
|
||||||
default: v2.1.0
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Create Release
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
outputs:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
version: ${{ steps.tag.outputs.version }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Fetch Version
|
|
||||||
id: tag
|
|
||||||
run: |
|
|
||||||
if [[ "${{ github.event.inputs.version }}" != "" ]]; then
|
|
||||||
echo ::set-output name=version::${{ github.event.inputs.version }}
|
|
||||||
else
|
|
||||||
echo ::set-output name=version::${GITHUB_REF/refs\/tags\//}
|
|
||||||
fi
|
|
||||||
- name: Create Release
|
|
||||||
id: create_release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
tag_name: ${{ steps.tag.outputs.version }}
|
|
||||||
name: Lite XL ${{ steps.tag.outputs.version }}
|
|
||||||
draft: true
|
|
||||||
prerelease: false
|
|
||||||
body_path: changelog.md
|
|
||||||
generate_release_notes: true
|
|
||||||
|
|
||||||
build_linux:
|
|
||||||
name: Linux
|
|
||||||
needs: release
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
env:
|
|
||||||
CC: gcc
|
|
||||||
CXX: g++
|
|
||||||
steps:
|
|
||||||
- name: Set Environment Variables
|
|
||||||
run: |
|
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
||||||
echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Python Setup
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
- name: Update Packages
|
|
||||||
run: sudo apt-get update
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
bash scripts/install-dependencies.sh --debug
|
|
||||||
sudo apt-get install -y ccache
|
|
||||||
- name: Build Portable
|
|
||||||
run: |
|
|
||||||
bash --version
|
|
||||||
bash scripts/build.sh --debug --forcefallback --portable --release
|
|
||||||
- name: Package Portables
|
|
||||||
run: |
|
|
||||||
bash scripts/package.sh --version ${INSTALL_REF} --debug --binary --release
|
|
||||||
bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --binary --release
|
|
||||||
- name: Build AppImages
|
|
||||||
run: |
|
|
||||||
bash scripts/appimage.sh --debug --static --version ${INSTALL_REF} --release
|
|
||||||
bash scripts/appimage.sh --debug --nobuild --addons --version ${INSTALL_REF}
|
|
||||||
- name: Upload Files
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
tag_name: ${{ needs.release.outputs.version }}
|
|
||||||
files: |
|
|
||||||
lite-xl-${{ env.INSTALL_REF }}-linux-x86_64-portable.tar.gz
|
|
||||||
lite-xl-${{ env.INSTALL_REF }}-addons-linux-x86_64-portable.tar.gz
|
|
||||||
LiteXL-${{ env.INSTALL_REF }}-x86_64.AppImage
|
|
||||||
LiteXL-${{ env.INSTALL_REF }}-addons-x86_64.AppImage
|
|
||||||
|
|
||||||
build_macos:
|
|
||||||
name: macOS (x86_64)
|
|
||||||
needs: release
|
|
||||||
runs-on: macos-11
|
|
||||||
env:
|
|
||||||
CC: clang
|
|
||||||
CXX: clang++
|
|
||||||
steps:
|
|
||||||
- name: System Information
|
|
||||||
run: |
|
|
||||||
system_profiler SPSoftwareDataType
|
|
||||||
bash --version
|
|
||||||
gcc -v
|
|
||||||
xcodebuild -version
|
|
||||||
- name: Set Environment Variables
|
|
||||||
run: |
|
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
||||||
echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
|
|
||||||
echo "INSTALL_NAME=lite-xl-${{ needs.release.outputs.version }}-macos-$(uname -m)" >> "$GITHUB_ENV"
|
|
||||||
echo "INSTALL_NAME_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-macos-$(uname -m)" >> "$GITHUB_ENV"
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Python Setup
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: bash scripts/install-dependencies.sh --debug
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
bash --version
|
|
||||||
bash scripts/build.sh --bundle --debug --forcefallback --release
|
|
||||||
- name: Create DMG Image
|
|
||||||
run: |
|
|
||||||
bash scripts/package.sh --version ${INSTALL_REF} --debug --dmg --release
|
|
||||||
bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --dmg --release
|
|
||||||
- name: Upload Files
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
tag_name: ${{ needs.release.outputs.version }}
|
|
||||||
files: |
|
|
||||||
${{ env.INSTALL_NAME }}.dmg
|
|
||||||
${{ env.INSTALL_NAME_ADDONS }}.dmg
|
|
||||||
|
|
||||||
build_windows_msys2:
|
|
||||||
name: Windows
|
|
||||||
needs: release
|
|
||||||
runs-on: windows-2019
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
msystem: [MINGW32, MINGW64]
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: msys2 {0}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: msys2/setup-msys2@v2
|
|
||||||
with:
|
|
||||||
msystem: ${{ matrix.msystem }}
|
|
||||||
update: true
|
|
||||||
install: >-
|
|
||||||
base-devel
|
|
||||||
git
|
|
||||||
zip
|
|
||||||
- name: Set Environment Variables
|
|
||||||
run: |
|
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
||||||
echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
|
|
||||||
if [[ "${MSYSTEM}" == "MINGW64" ]]; then
|
|
||||||
echo "BUILD_ARCH=x86_64" >> "$GITHUB_ENV"
|
|
||||||
echo "INSTALL_NAME=lite-xl-${{ needs.release.outputs.version }}-windows-x86_64" >> "$GITHUB_ENV"
|
|
||||||
echo "INSTALL_NAME_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-windows-x86_64" >> "$GITHUB_ENV"
|
|
||||||
else
|
|
||||||
echo "BUILD_ARCH=i686" >> "$GITHUB_ENV"
|
|
||||||
echo "INSTALL_NAME=lite-xl-${{ needs.release.outputs.version }}-windows-i686" >> "$GITHUB_ENV"
|
|
||||||
echo "INSTALL_NAME_ADDONS=lite-xl-${{ needs.release.outputs.version }}-addons-windows-i686" >> "$GITHUB_ENV"
|
|
||||||
fi
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: bash scripts/install-dependencies.sh --debug
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
bash --version
|
|
||||||
bash scripts/build.sh -U --debug --forcefallback --release
|
|
||||||
- name: Package
|
|
||||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --binary --release
|
|
||||||
- name: Build Installer
|
|
||||||
run: bash scripts/innosetup/innosetup.sh --debug --version ${INSTALL_REF}
|
|
||||||
- name: Package With Addons
|
|
||||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --binary --release
|
|
||||||
- name: Build Installer With Addons
|
|
||||||
run: bash scripts/innosetup/innosetup.sh --debug --version ${INSTALL_REF} --addons
|
|
||||||
- name: Upload Files
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
tag_name: ${{ needs.release.outputs.version }}
|
|
||||||
files: |
|
|
||||||
${{ env.INSTALL_NAME }}.zip
|
|
||||||
${{ env.INSTALL_NAME_ADDONS }}.zip
|
|
||||||
LiteXL-${{ env.INSTALL_REF }}-${{ env.BUILD_ARCH }}-setup.exe
|
|
||||||
LiteXL-${{ env.INSTALL_REF }}-addons-${{ env.BUILD_ARCH }}-setup.exe
|
|
|
@ -1,30 +1,11 @@
|
||||||
build*/
|
build*
|
||||||
.build*/
|
.build*
|
||||||
lhelper/
|
|
||||||
submodules/
|
|
||||||
subprojects/*/
|
|
||||||
/appimage*
|
|
||||||
.vscode
|
|
||||||
.cache
|
|
||||||
.ccls-cache
|
|
||||||
.lite-debug.log
|
|
||||||
.run*
|
.run*
|
||||||
*.diff
|
|
||||||
*.exe
|
|
||||||
*.tar.gz
|
|
||||||
*.zip
|
*.zip
|
||||||
*.DS_Store
|
*.tar.gz
|
||||||
*App*
|
.lite-debug.log
|
||||||
compile_commands.json
|
subprojects/lua
|
||||||
error.txt
|
subprojects/libagg
|
||||||
lite-xl*
|
sybprojects/lua
|
||||||
LiteXL*
|
|
||||||
lite
|
lite
|
||||||
.config/
|
.config/
|
||||||
*.lha
|
|
||||||
release_files
|
|
||||||
*.o
|
|
||||||
*.snalyzerinfo
|
|
||||||
|
|
||||||
|
|
||||||
!resources/windows/*.diff
|
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2020-2021 Lite XL Team
|
Copyright (c) 2020-2021 Francesco Abbate
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|
86
Makefile.mos
86
Makefile.mos
|
@ -1,86 +0,0 @@
|
||||||
#
|
|
||||||
# Project: Lite XL
|
|
||||||
#
|
|
||||||
# Created on: 26-12-2021
|
|
||||||
#
|
|
||||||
|
|
||||||
LiteXL_OBJ := \
|
|
||||||
src/main.o src/rencache.o src/renderer.o src/renwindow.o \
|
|
||||||
src/api/api.o src/api/dirmonitor.o \
|
|
||||||
src/api/regex.o src/api/renderer.o src/api/system.o \
|
|
||||||
src/api/utf8.o src/platform/morphos.o \
|
|
||||||
src/api/dirmonitor/mos.o
|
|
||||||
|
|
||||||
|
|
||||||
outfile := lite
|
|
||||||
compiler := gcc
|
|
||||||
cxxcompiler := g++
|
|
||||||
|
|
||||||
INCPATH := -Isrc -Ilib/dmon -I/sdk/gg/usr/local/include/SDL2 -I/sdk/gg/usr/include/freetype -I/sdk/gg/usr/include/lua5.4
|
|
||||||
DFLAGS := -D__USE_INLINE__
|
|
||||||
CFLAGS := -Wall -Wwrite-strings -O2 -noixemul -g -std=gnu11 -fno-strict-aliasing
|
|
||||||
LFLAGS := -noixemul -lpcre2 -lSDL2 -llua54 -lagg -lfreetype -lm -lc -L/usr/local/lib
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: LiteXL clean release
|
|
||||||
|
|
||||||
default: LiteXL
|
|
||||||
|
|
||||||
clean:
|
|
||||||
@echo "Cleaning compiler objects..."
|
|
||||||
@rm -f $(LiteXL_OBJ)
|
|
||||||
|
|
||||||
LiteXL: $(LiteXL_OBJ)
|
|
||||||
@echo "Linking LiteXL"
|
|
||||||
@$(cxxcompiler) -o $(outfile) $(LiteXL_OBJ) $(LFLAGS)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.c.o:
|
|
||||||
@echo "Compiling $<"
|
|
||||||
@$(compiler) -c $< -o $*.o $(CFLAGS) $(INCPATH) $(DFLAGS)
|
|
||||||
|
|
||||||
src/main.o: src/main.c src/api/api.h src/rencache.h \
|
|
||||||
src/renderer.h src/platform/morphos.h
|
|
||||||
|
|
||||||
src/rencache.o: src/rencache.c
|
|
||||||
|
|
||||||
src/renderer.o: src/renderer.c
|
|
||||||
|
|
||||||
src/renwindow.o: src/renwindow.c
|
|
||||||
|
|
||||||
src/api/api.o: src/api/api.c
|
|
||||||
|
|
||||||
src/api/regex.o: src/api/regex.c
|
|
||||||
|
|
||||||
src/api/renderer.o: src/api/renderer.c
|
|
||||||
|
|
||||||
src/api/system.o: src/api/system.c
|
|
||||||
|
|
||||||
src/platform/morphos.o: src/platform/morphos.c
|
|
||||||
|
|
||||||
src/api/dirmonitor.o: src/api/dirmonitor.c src/api/dirmonitor/mos.c
|
|
||||||
|
|
||||||
src/api/utf8.o: src/api/utf8.c
|
|
||||||
|
|
||||||
src/api/dirmonitor/mos.o: src/api/dirmonitor/mos.c
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
release: clean LiteXL
|
|
||||||
@echo "Creating release files..."
|
|
||||||
@mkdir -p release/LiteXL2
|
|
||||||
@cp release_files/* release/LiteXL2/ -r
|
|
||||||
@mv release/LiteXL2/LiteXL2.info release/
|
|
||||||
@cp data release/LiteXL2/ -r
|
|
||||||
@cp changelog.md release/LiteXL2/
|
|
||||||
@cp lite release/LiteXL2/
|
|
||||||
@strip release/LiteXL2/lite
|
|
||||||
@cp README.md release/LiteXL2/
|
|
||||||
@cp README_Amiga.md release/LiteXL2/
|
|
||||||
@cp LICENSE release/LiteXL2/
|
|
||||||
@echo "Creating release archive..."
|
|
||||||
@lha -aeqr3 a LiteXL2_MOS.lha release/
|
|
||||||
@echo "Clean release files..."
|
|
||||||
@delete release ALL QUIET FORCE
|
|
||||||
|
|
107
Makefile.os4
107
Makefile.os4
|
@ -3,98 +3,23 @@
|
||||||
#
|
#
|
||||||
# Created on: 26-12-2021
|
# Created on: 26-12-2021
|
||||||
#
|
#
|
||||||
|
.PHONY: build release
|
||||||
|
|
||||||
LiteXL_OBJ := \
|
default: build
|
||||||
src/main.o src/rencache.o src/renderer.o src/renwindow.o \
|
|
||||||
src/api/api.o src/api/dirmonitor.o \
|
|
||||||
src/api/regex.o src/api/renderer.o src/api/system.o \
|
|
||||||
src/api/utf8.o src/platform/amigaos4.o \
|
|
||||||
src/api/dirmonitor/os4.o
|
|
||||||
|
|
||||||
|
build:
|
||||||
|
@sh os4build.sh
|
||||||
|
|
||||||
outfile := lite
|
release:
|
||||||
compiler := gcc
|
mkdir -p release/LiteXL
|
||||||
cxxcompiler := g++
|
cp release_files/* release/LiteXL/ -r
|
||||||
|
mv release/LiteXL/LiteXL.info release/
|
||||||
INCPATH := -Isrc -Ilib/dmon -I/sdk/local/newlib/include/SDL2 \
|
cp data release/LiteXL/ -r
|
||||||
-I/sdk/local/common/include/lua54 -I/sdk/local/common/include/freetype2
|
cp doc release/LiteXL/ -r
|
||||||
|
cp lite release/LiteXL/
|
||||||
DFLAGS += -D__USE_INLINE__ -DLITE_XL_DATA_USE_EXEDIR
|
strip release/LiteXL/lite
|
||||||
|
cp README.md release/LiteXL/
|
||||||
CFLAGS += -Werror -Wwrite-strings -O2 -g -std=gnu11 -fno-strict-aliasing
|
cp README_OS4.md release/LiteXL/
|
||||||
|
cp LICENSE release/LiteXL/
|
||||||
LFLAGS += -mcrt=newlib -lauto \
|
lha -aeqr3 a LiteXL.lha release/
|
||||||
-lpcre2 -lSDL2 -llua54 -lfreetype -lz -lm -lpthread -athread=native
|
|
||||||
|
|
||||||
ifeq ($(DEBUG),1)
|
|
||||||
CFLAGS += -gstabs
|
|
||||||
LFLAGS += -gstabs
|
|
||||||
|
|
||||||
ifeq ($(PROFYLER),1)
|
|
||||||
CFLAGS += -finstrument-functions -fno-inline -DPROFILING
|
|
||||||
LFLAGS += -lprofyle
|
|
||||||
endif
|
|
||||||
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: LiteXL clean release
|
|
||||||
|
|
||||||
default: LiteXL
|
|
||||||
|
|
||||||
clean:
|
|
||||||
@echo "Cleaning compiler objects..."
|
|
||||||
@rm -f $(LiteXL_OBJ)
|
|
||||||
|
|
||||||
LiteXL: $(LiteXL_OBJ)
|
|
||||||
@echo "Linking LiteXL"
|
|
||||||
@$(compiler) -o $(outfile) $(LiteXL_OBJ) $(LFLAGS)
|
|
||||||
|
|
||||||
|
|
||||||
.c.o:
|
|
||||||
@echo "Compiling $<"
|
|
||||||
@$(compiler) -c $< -o $*.o $(CFLAGS) $(INCPATH) $(DFLAGS)
|
|
||||||
|
|
||||||
|
|
||||||
src/main.o: src/main.c src/api/api.h src/rencache.h \
|
|
||||||
src/renderer.h src/platform/amigaos4.h
|
|
||||||
|
|
||||||
src/rencache.o: src/rencache.c
|
|
||||||
|
|
||||||
src/renderer.o: src/renderer.c
|
|
||||||
|
|
||||||
src/renwindow.o: src/renwindow.c
|
|
||||||
|
|
||||||
src/api/api.o: src/api/api.c
|
|
||||||
|
|
||||||
src/api/regex.o: src/api/regex.c
|
|
||||||
|
|
||||||
src/api/renderer.o: src/api/renderer.c
|
|
||||||
|
|
||||||
src/api/system.o: src/api/system.c
|
|
||||||
|
|
||||||
src/platform/amigaos4.o: src/platform/amigaos4.c
|
|
||||||
|
|
||||||
src/api/dirmonitor.o: src/api/dirmonitor.c src/api/dirmonitor/os4.c
|
|
||||||
|
|
||||||
src/api/utf8.o: src/api/utf8.c
|
|
||||||
|
|
||||||
src/api/dirmonitor/os4.o: src/api/dirmonitor/os4.c
|
|
||||||
|
|
||||||
release:
|
|
||||||
@echo "Creating release files..."
|
|
||||||
@mkdir -p release/LiteXL2
|
|
||||||
@cp -r release_files/* release/LiteXL2/
|
|
||||||
@mv release/LiteXL2/LiteXL2.info release/
|
|
||||||
@cp -r data release/LiteXL2/
|
|
||||||
@cp changelog.md release/LiteXL2/
|
|
||||||
@cp lite release/LiteXL2/
|
|
||||||
@strip release/LiteXL2/lite
|
|
||||||
@cp README.md release/LiteXL2/
|
|
||||||
@cp README_Amiga.md release/LiteXL2/
|
|
||||||
@cp LICENSE release/LiteXL2/
|
|
||||||
@echo "Creating release archive..."
|
|
||||||
@lha -aeqr3 a LiteXL2_OS4.lha release/
|
|
||||||
@echo "Clean release files..."
|
|
||||||
@delete release ALL QUIET FORCE
|
|
||||||
|
|
||||||
|
|
190
README.md
190
README.md
|
@ -1,160 +1,126 @@
|
||||||
# Lite XL
|
# Lite XL
|
||||||
|
|
||||||
[![CI]](https://github.com/lite-xl/lite-xl/actions/workflows/build.yml)
|
![screenshot-dark](https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png)
|
||||||
[![Discord Badge Image]](https://discord.gg/RWzqC3nx7K)
|
|
||||||
|
|
||||||
![screenshot-dark]
|
A lightweight text editor written in Lua, adapted from [lite](https://github.com/rxi/lite)
|
||||||
|
|
||||||
A lightweight text editor written in Lua, adapted from [lite].
|
* **[Get Lite XL](https://github.com/franko/lite-xl/releases/latest)** — Download
|
||||||
|
for Windows, Linux and Mac OS (notarized app).
|
||||||
|
* **[Get started](doc/usage.md)** — A quick overview on how to get started
|
||||||
|
* **[Get plugins](https://github.com/franko/lite-plugins)** — Add additional
|
||||||
|
functionality, adapted for Lite XL
|
||||||
|
* **[Get color themes](https://github.com/rxi/lite-colors)** — Add additional colors
|
||||||
|
themes
|
||||||
|
|
||||||
* **[Get Lite XL]** — Download for Windows, Linux and Mac OS.
|
Lite XL has support for high DPI display on Windows and Linux and, since 1.16.7 release, it supports **retina displays** on Mac OS.
|
||||||
* **[Get plugins]** — Add additional functionality, adapted for Lite XL.
|
|
||||||
* **[Get color themes]** — Add additional colors themes.
|
|
||||||
|
|
||||||
Please refer to our [website] for the user and developer documentation,
|
|
||||||
including [build] instructions details. A quick build guide is described below.
|
|
||||||
|
|
||||||
Lite XL has support for high DPI display on Windows and Linux and,
|
|
||||||
since 1.16.7 release, it supports **retina displays** on macOS.
|
|
||||||
|
|
||||||
Please note that Lite XL is compatible with lite for most plugins and all color themes.
|
Please note that Lite XL is compatible with lite for most plugins and all color themes.
|
||||||
We provide a separate lite-xl-plugins repository for Lite XL, because in some cases
|
We provide a separate lite-plugins repository for Lite XL, because in some cases some adaptations may be needed to make them work better with Lite XL.
|
||||||
some adaptations may be needed to make them work better with Lite XL.
|
The repository with modified plugins is http://github.com/franko/lite-plugins.
|
||||||
The repository with modified plugins is https://github.com/lite-xl/lite-xl-plugins.
|
|
||||||
|
|
||||||
The changes and differences between Lite XL and rxi/lite are listed in the
|
The changes and differences between Lite XL and rxi/lite are listed in the [changelog](https://github.com/franko/lite-xl/blob/master/changelog.md).
|
||||||
[changelog].
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Lite XL is derived from [lite].
|
Lite XL is derived from lite. It is a lightweight text editor written mostly in Lua — it aims to provide
|
||||||
It is a lightweight text editor written mostly in Lua — it aims to provide
|
something practical, pretty, *small* and fast easy to modify and extend, or to use without doing either.
|
||||||
something practical, pretty, *small* and fast easy to modify and extend,
|
|
||||||
or to use without doing either.
|
|
||||||
|
|
||||||
The aim of Lite XL compared to lite is to be more user friendly,
|
The aim of Lite XL compared to lite is to be more user friendly, improve the quality of font rendering, and reduce CPU usage.
|
||||||
improve the quality of font rendering, and reduce CPU usage.
|
|
||||||
|
|
||||||
## Customization
|
## Customization
|
||||||
|
|
||||||
Additional functionality can be added through plugins which are available in
|
Additional functionality can be added through plugins which are available in
|
||||||
the [plugins repository] or in the [Lite XL plugins repository].
|
the [plugins repository](https://github.com/rxi/lite-plugins) or in the [Lite XL-specific plugins repository](https://github.com/franko/lite-plugins).
|
||||||
|
|
||||||
Additional color themes can be found in the [colors repository].
|
Additional color themes can be found in the [colors repository](https://github.com/rxi/lite-colors).
|
||||||
These color themes are bundled with all releases of Lite XL by default.
|
These color themes are bundled with all releases of Lite XL by default.
|
||||||
|
|
||||||
## Quick Build Guide
|
The editor can be customized by making changes to the [user module](data/user/init.lua).
|
||||||
|
|
||||||
If you compile Lite XL yourself, it is recommended to use the script
|
## Building
|
||||||
`build-packages.sh`:
|
|
||||||
|
You can build Lite XL yourself using Meson.
|
||||||
|
|
||||||
|
In addition, the `build-packages.sh` script can be used to compile Lite XL and create an OS-specific package for Linux, Windows or Mac OS.
|
||||||
|
|
||||||
|
The following libraries are required:
|
||||||
|
|
||||||
|
- freetype2
|
||||||
|
- SDL2
|
||||||
|
|
||||||
|
The following libraries are **optional**:
|
||||||
|
|
||||||
|
- libagg
|
||||||
|
- Lua 5.2
|
||||||
|
|
||||||
|
If they are not found, they will be downloaded and compiled by Meson.
|
||||||
|
Otherwise, if they are present, they will be used to compile Lite XL.
|
||||||
|
|
||||||
|
On Debian-based systems the required libraries and Meson can be installed using the following commands:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bash build-packages.sh -h
|
# To install the required libraries:
|
||||||
|
sudo apt install libfreetype6-dev libsdl2-dev
|
||||||
|
|
||||||
|
# To install Meson:
|
||||||
|
sudo apt install meson
|
||||||
|
# or pip3 install --user meson
|
||||||
```
|
```
|
||||||
|
|
||||||
The script will run Meson and create a tar compressed archive with the application or,
|
To build Lite XL with Meson the commands below can be used:
|
||||||
for Windows, a zip file. Lite XL can be easily installed
|
|
||||||
by unpacking the archive in any directory of your choice.
|
|
||||||
|
|
||||||
Otherwise the following is an example of basic commands if you want to customize
|
|
||||||
the build:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
meson setup --buildtype=release --prefix <prefix> build
|
meson setup --buildtype=release build
|
||||||
meson compile -C build
|
meson compile -C build
|
||||||
DESTDIR="$(pwd)/lite-xl" meson install --skip-subprojects -C build
|
meson install -C build
|
||||||
```
|
```
|
||||||
|
|
||||||
where `<prefix>` might be one of `/`, `/usr` or `/opt`, the default is `/`.
|
If you are using a version of Meson below 0.54 you need to use diffent commands to compile and install:
|
||||||
To build a bundle application on macOS:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
meson setup --buildtype=release --Dbundle=true --prefix / build
|
meson setup --buildtype=release build
|
||||||
meson compile -C build
|
ninja -C build
|
||||||
DESTDIR="$(pwd)/Lite XL.app" meson install --skip-subprojects -C build
|
ninja -C build install
|
||||||
```
|
```
|
||||||
|
|
||||||
Please note that the package is relocatable to any prefix and the option prefix
|
When performing the `meson setup` command you may enable the `-Dportable=true` option to specify whether files should be installed as in a portable application.
|
||||||
affects only the place where the application is actually installed.
|
|
||||||
|
|
||||||
## Installing Prebuilt
|
If `portable` is enabled, Lite XL is built to use a `data` directory placed next to the executable.
|
||||||
|
Otherwise, Lite XL will use unix-like directory locations.
|
||||||
|
In this case, the `data` directory will be `$prefix/share/lite-xl` and the executable will be located in `$prefix/bin`.
|
||||||
|
`$prefix` is determined when the application starts as a directory such that `$prefix/bin` corresponds to the location of the executable.
|
||||||
|
|
||||||
Head over to [releases](https://github.com/lite-xl/lite-xl/releases) and download the version for your operating system.
|
The `user` directory does not depend on the `portable` option and will always be `$HOME/.config/lite-xl`.
|
||||||
|
`$HOME` is determined from the corresponding environment variable.
|
||||||
|
As a special case on Windows the variable `$USERPROFILE` will be used instead.
|
||||||
|
|
||||||
### Linux
|
If you compile Lite XL yourself, it is recommended to use the script `build-packages.sh`:
|
||||||
|
|
||||||
Unzip the file and `cd` into the `lite-xl` directory:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
tar -xzf <file>
|
bash build-packages.sh <arch>
|
||||||
cd lite-xl
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To run lite-xl without installing:
|
The script will run Meson and create a zip file with the application or, for linux, a tar compressed archive.
|
||||||
```sh
|
Lite XL can be easily installed by unpacking the archive in any directory of your choice.
|
||||||
cd bin
|
|
||||||
./lite-xl
|
|
||||||
```
|
|
||||||
|
|
||||||
To install lite-xl copy files over into appropriate directories:
|
On Windows two packages will be created, one called "portable" using the "data" folder next to the executable and
|
||||||
|
the other one using a unix-like file layout. Both packages works correctly. The one with unix-like file layout
|
||||||
|
is meant for people using a unix-like shell and the command line.
|
||||||
|
|
||||||
```sh
|
Please note that there aren't any hard-coded directories in the executable, so that the
|
||||||
mkdir -p $HOME/.local/bin && cp bin/lite-xl $HOME/.local/bin
|
package can be extracted and used in any directory.
|
||||||
cp -r share $HOME/.local
|
|
||||||
```
|
|
||||||
|
|
||||||
If `$HOME/.local/bin` is not in PATH:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
echo -e 'export PATH=$PATH:$HOME/.local/bin' >> $HOME/.bashrc
|
|
||||||
```
|
|
||||||
|
|
||||||
To get the icon to show up in app launcher:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
xdg-desktop-menu forceupdate
|
|
||||||
```
|
|
||||||
|
|
||||||
You may need to logout and login again to see icon in app launcher.
|
|
||||||
|
|
||||||
To uninstall just run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rm -f $HOME/.local/bin/lite-xl
|
|
||||||
rm -rf $HOME/.local/share/icons/hicolor/scalable/apps/lite-xl.svg \
|
|
||||||
$HOME/.local/share/applications/org.lite_xl.lite_xl.desktop \
|
|
||||||
$HOME/.local/share/metainfo/org.lite_xl.lite_xl.appdata.xml \
|
|
||||||
$HOME/.local/share/lite-xl
|
|
||||||
```
|
|
||||||
|
|
||||||
|
Mac OS X is fully supported and a notarized app disk image is provided in the [release page](https://github.com/franko/lite-xl/releases).
|
||||||
|
In addition the application can be compiled using the generic instructions given above.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Any additional functionality that can be added through a plugin should be done
|
Any additional functionality that can be added through a plugin should be done
|
||||||
as a plugin, after which a pull request to the [Lite XL plugins repository] can be made.
|
as a plugin, after which a pull request to the
|
||||||
|
[plugins repository](https://github.com/rxi/lite-plugins) can be made.
|
||||||
|
|
||||||
|
If the plugin uses any Lite XL-specific functionality, please open a pull request to the
|
||||||
|
[Lite XL plugins repository](https://github.com/franko/lite-plugins).
|
||||||
|
|
||||||
Pull requests to improve or modify the editor itself are welcome.
|
Pull requests to improve or modify the editor itself are welcome.
|
||||||
|
|
||||||
## Licenses
|
## License
|
||||||
|
|
||||||
This project is free software; you can redistribute it and/or modify it under
|
This project is free software; you can redistribute it and/or modify it under
|
||||||
the terms of the MIT license. See [LICENSE] for details.
|
the terms of the MIT license. See [LICENSE](LICENSE) for details.
|
||||||
|
|
||||||
See the [licenses] file for details on licenses used by the required dependencies.
|
|
||||||
|
|
||||||
|
|
||||||
[CI]: https://github.com/lite-xl/lite-xl/actions/workflows/build.yml/badge.svg
|
|
||||||
[Discord Badge Image]: https://img.shields.io/discord/847122429742809208?label=discord&logo=discord
|
|
||||||
[screenshot-dark]: https://user-images.githubusercontent.com/433545/111063905-66943980-84b1-11eb-9040-3876f1133b20.png
|
|
||||||
[lite]: https://github.com/rxi/lite
|
|
||||||
[website]: https://lite-xl.com
|
|
||||||
[build]: https://lite-xl.com/en/documentation/build
|
|
||||||
[Get Lite XL]: https://github.com/lite-xl/lite-xl/releases/latest
|
|
||||||
[Get plugins]: https://github.com/lite-xl/lite-xl-plugins
|
|
||||||
[Get color themes]: https://github.com/lite-xl/lite-xl-colors
|
|
||||||
[changelog]: https://github.com/lite-xl/lite-xl/blob/master/changelog.md
|
|
||||||
[Lite XL plugins repository]: https://github.com/lite-xl/lite-xl-plugins
|
|
||||||
[plugins repository]: https://github.com/rxi/lite-plugins
|
|
||||||
[colors repository]: https://github.com/lite-xl/lite-xl-colors
|
|
||||||
[LICENSE]: LICENSE
|
|
||||||
[licenses]: licenses/licenses.md
|
|
||||||
|
|
280
README_Amiga.md
280
README_Amiga.md
|
@ -1,280 +0,0 @@
|
||||||
# Lite XL v2 for AmigaOS 4.1 FE & MorphOS 3
|
|
||||||
|
|
||||||
Lite XL is a lightweight text editor written in Lua.
|
|
||||||
|
|
||||||
The port is not perfect and it might have issues here and there. For example
|
|
||||||
the filesystem notifications are not working yet. So when you make changes
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
You can extract the Lite XL archive wherever you want and run the *lite*
|
|
||||||
editor.
|
|
||||||
|
|
||||||
## Configuration folder
|
|
||||||
This editor creates a `.config` folder where the configuration is saved, as
|
|
||||||
well as plugins, themes etc.. By default this version uses the
|
|
||||||
executable folder, but if you want to override it, you can create an ENV
|
|
||||||
variable named `HOME` and set there your prefferable path.
|
|
||||||
|
|
||||||
You can check if there is one already set by executing the following command
|
|
||||||
in a shell
|
|
||||||
```
|
|
||||||
GetEnv HOME
|
|
||||||
```
|
|
||||||
If there is one set, then you will see the path at the output.
|
|
||||||
|
|
||||||
Otherwise, you can set your home path be executing the following command.
|
|
||||||
Change the path to the one of your preference.
|
|
||||||
```
|
|
||||||
SetEnv SAVE HOME "Sys:home/"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Addons
|
|
||||||
### Colors
|
|
||||||
Colors are lua files that set the color scheme of the editor. There are
|
|
||||||
light and dark themes for you to choose.
|
|
||||||
|
|
||||||
To install and use them you have to copy the ones you would like from
|
|
||||||
`addons/colors/light` or `addons/colors/dark` into the folder
|
|
||||||
`.config/lite-xl/colors/`. Don't add light or dark folders. Just copy the
|
|
||||||
.lua files in there.
|
|
||||||
|
|
||||||
Then you have to start Lite XL and open your configuration by clicking
|
|
||||||
at the cog icon at the toolbar (bottom left sixth icon). Go at the line
|
|
||||||
that looks like below
|
|
||||||
```
|
|
||||||
-- core.reload_module("colors.summer")
|
|
||||||
```
|
|
||||||
and change the `summer` with the name of your color theme. Also, remove
|
|
||||||
the two dashes `--` at the start of the line and save the file. If you
|
|
||||||
did everything right, the color schema should change instantly.
|
|
||||||
|
|
||||||
The themes can also be found at
|
|
||||||
https://github.com/lite-xl/lite-xl-colors
|
|
||||||
|
|
||||||
### Plugins
|
|
||||||
This Lite XL release is based on version 2.0.3 of the application as
|
|
||||||
released on other systems, by the original development team.
|
|
||||||
This not the latest version. This means that some of the latest
|
|
||||||
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
|
|
||||||
well, and I included them under `addons/plugins`. For you to install the
|
|
||||||
ones you would like to use, you have to copy the `.lua` files into the
|
|
||||||
folder `.config/lite-xl/plugins/` and restart the editor.
|
|
||||||
|
|
||||||
Please, choose wisely, because adding all the plugins might make the editor
|
|
||||||
slower on your system. I would recommend you add only those that you really
|
|
||||||
need.
|
|
||||||
|
|
||||||
The included plugins are the following:
|
|
||||||
|
|
||||||
**autoinsert**
|
|
||||||
Automatically inserts closing brackets and quotes. Also allows selected
|
|
||||||
text to be wrapped with brackets or quotes.
|
|
||||||
|
|
||||||
**autosaveonfocuslost**
|
|
||||||
Automatically saves files that were changed when the main window loses
|
|
||||||
focus by switching to another application
|
|
||||||
|
|
||||||
**autowrap**
|
|
||||||
Automatically hardwraps lines when typing
|
|
||||||
|
|
||||||
**bigclock**
|
|
||||||
Shows the current time and date in a view with large text
|
|
||||||
|
|
||||||
**bracketmatch**
|
|
||||||
Underlines matching pair for bracket under the caret
|
|
||||||
|
|
||||||
**colorpreview**
|
|
||||||
Underlays color values (eg. `#ff00ff` or `rgb(255, 0, 255)`) with their
|
|
||||||
resultant color.
|
|
||||||
|
|
||||||
**ephemeral_tabs**
|
|
||||||
Preview tabs. Opening a doc will replace the contents of the preview tab.
|
|
||||||
Marks tabs as non-preview on any change or tab double clicking.
|
|
||||||
|
|
||||||
**ghmarkdown**
|
|
||||||
Opens a preview of the current markdown file in a browser window.
|
|
||||||
On AmigaOS 4 it uses *urlopen* and on MorphOS it uses *openurl* to load
|
|
||||||
the generated html in the browser.
|
|
||||||
|
|
||||||
**indentguide**
|
|
||||||
Adds indent guides
|
|
||||||
|
|
||||||
**language_guide**
|
|
||||||
Syntax for the AmigaGuide scripting language
|
|
||||||
|
|
||||||
**language_hws**
|
|
||||||
Syntax for the Hollywood language
|
|
||||||
|
|
||||||
**language_make**
|
|
||||||
Syntax for the Make build system language
|
|
||||||
|
|
||||||
**language_sh**
|
|
||||||
Syntax for shell scripting language
|
|
||||||
|
|
||||||
**lfautoinsert**
|
|
||||||
Automatically inserts indentation and closing bracket/text after newline
|
|
||||||
|
|
||||||
**markers**
|
|
||||||
Add markers to docs and jump between them quickly
|
|
||||||
|
|
||||||
**minimap**
|
|
||||||
Shows a minimap on the right-hand side of the docview. Please note that
|
|
||||||
this plugin will make the editor slower on file loading and scrolling.
|
|
||||||
|
|
||||||
**navigate**
|
|
||||||
Allows moving back and forward between document positions, reducing the
|
|
||||||
amount of scrolling
|
|
||||||
|
|
||||||
**nonicons**
|
|
||||||
File icons set for TreeView. Download TTF font to your config/fonts
|
|
||||||
folder from https://github.com/yamatsum/nonicons/tree/master/dist
|
|
||||||
|
|
||||||
**opacity**
|
|
||||||
Change the opaqueness/transparency of lite-xl using shift+mousewheel
|
|
||||||
or a command.
|
|
||||||
|
|
||||||
**openfilelocation**
|
|
||||||
Opens the parent directory of the current file in the file manager
|
|
||||||
|
|
||||||
**rainbowparen**
|
|
||||||
Show nesting of parentheses with rainbow colours
|
|
||||||
|
|
||||||
**restoretabs**
|
|
||||||
Keep a list of recently closed tabs, and restore the tab in order on
|
|
||||||
cntrl+shift+t.
|
|
||||||
|
|
||||||
**select_colorscheme**
|
|
||||||
Select a color theme, like VScode, Sublime Text.
|
|
||||||
(plugin saves changes)
|
|
||||||
|
|
||||||
**selectionhighlight**
|
|
||||||
Highlights regions of code that match the current selection
|
|
||||||
|
|
||||||
**smallclock**
|
|
||||||
It adds a small clock at the bottom right corner.
|
|
||||||
|
|
||||||
## Tips and tricks
|
|
||||||
|
|
||||||
### Transitions
|
|
||||||
|
|
||||||
If you want to disable the transitions and make the editor faster,
|
|
||||||
open your configuration file by clicking at the cog icon at the toolbar
|
|
||||||
(bottom left, 6th icon) and add the following line at the end of the file,
|
|
||||||
and then save it. You might need to restart your editor (CTRL+SHIFT+R)
|
|
||||||
|
|
||||||
```
|
|
||||||
config.transitions = false
|
|
||||||
```
|
|
||||||
|
|
||||||
### Hide files from the file list
|
|
||||||
|
|
||||||
If you would like to hide files or whole folder from the left side bar list,
|
|
||||||
open your configuration by clicking at the cog icon at the toolbar
|
|
||||||
(bottom left sixth icon) and add the followline at the end of the file and
|
|
||||||
save it. This hides all the files that start with a dot, and all the `.info`
|
|
||||||
files. You might need to restart your editor (CTRL+SHIFT+R)
|
|
||||||
|
|
||||||
```
|
|
||||||
config.ignore_files = {"^%.", "%.info$"}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can add as many rules as you want in there, to hide files or
|
|
||||||
folders, as you like.
|
|
||||||
|
|
||||||
## I would like to thank
|
|
||||||
|
|
||||||
- IconDesigner for the proper glow icons that are included in the release
|
|
||||||
- Capehill for his tireless work on SDL port for AmigaOS 4.1 FE
|
|
||||||
- Michael Trebilcock for his port on liblua
|
|
||||||
- Bruno "BeWorld" Peloille for his great work on porting SDL to MorphOS
|
|
||||||
and for his valuable help
|
|
||||||
- Lite XL original team for being helpful and providing info
|
|
||||||
|
|
||||||
Without all the above Lite XL would not be possible
|
|
||||||
|
|
||||||
## Support
|
|
||||||
If you enjoy what I am doing and would like to keep me up during the night,
|
|
||||||
please consider to buy me a coffee at:
|
|
||||||
https://ko-fi.com/walkero
|
|
||||||
|
|
||||||
## Known issues
|
|
||||||
You can find the known issues at
|
|
||||||
https://git.walkero.gr/walkero/lite-xl/issues
|
|
||||||
|
|
||||||
# Changelog
|
|
||||||
|
|
||||||
## [2.1.0r1] - 2022-10-10
|
|
||||||
### Added
|
|
||||||
- This version of LiteXL recognises changes that are done outside the editor
|
|
||||||
in files and folders, and updates the items when it gets focus again.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Synced the code with the latest upstream master branch, which means that
|
|
||||||
this version is based on the latest available source
|
|
||||||
- Now the plugins need to be version 3. The older versions will not work.
|
|
||||||
All the included plugins are updated to the latest available version.
|
|
||||||
- Compiled with SDL 2.24
|
|
||||||
- Compiled with Lua 5.4
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Fixed regex issues with some plugins
|
|
||||||
- Fixed "Open in System" on AmigaOS 4 and MorphOS. When you right click
|
|
||||||
at a file or a folder at the treeview at the left side, then depending
|
|
||||||
the type of the item opens on Workbench. A folder opens in a list view
|
|
||||||
and a file opens with its default tool
|
|
||||||
- Fixed markdown preview on MorphOS. Now, it calls openurl with the html
|
|
||||||
file (#20)
|
|
||||||
- Fixed locale issues on MorphOS (again), since the previous fix didn't
|
|
||||||
actually fixed the problem
|
|
||||||
|
|
||||||
## [2.0.3r3] - 2022-09-26
|
|
||||||
### Added
|
|
||||||
- Added plugin for AmigaGuide files
|
|
||||||
- Added plugin for Hollywood files
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Fixed non existing path crashes on OS4 and MorphOS
|
|
||||||
- Fixed editor refresh whenever init.lua is changed, no matter the working
|
|
||||||
folder
|
|
||||||
- Fixed an issue when the user added a directory in the project that
|
|
||||||
already existed
|
|
||||||
- Fixed locale issue on start for MorphOS. Now it should start just fine
|
|
||||||
no matter what locale the user has on his system.
|
|
||||||
- Fixed "Find" on MorphOS that was not working (shortcut CTRL+F)
|
|
||||||
- If the user selects to change the project folder and inserts Sys: or any
|
|
||||||
partition name, the included folders will be listed as suggestions
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Removed linking with unix on OS4 build
|
|
||||||
- Makefiles updated
|
|
||||||
|
|
||||||
## [2.0.3r2] - 2022-06-18
|
|
||||||
### Added
|
|
||||||
- First public MorphOS version released
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Merged source code for both AmigaOS 4 and MorphOS
|
|
||||||
- Moved the declaration of the $VER and $STACK for the AmigaOS 4 version,
|
|
||||||
so to happen only once (reported by capehill)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Fixed the usage of NumPad (reported by root)
|
|
||||||
|
|
||||||
|
|
||||||
## [2.0.3r1] - 2022-03-30
|
|
||||||
### Changed
|
|
||||||
- Applied all the necessary changes to make it run under AmigaOS 4.1 FE
|
|
||||||
- Fixes and changes
|
|
||||||
|
|
||||||
# Disclaimer
|
|
||||||
YOU MAY USE IT AT YOUR OWN RISK!
|
|
||||||
I will not be held responsible for any data loss or problems you might get
|
|
||||||
by using this software.
|
|
||||||
|
|
|
@ -0,0 +1,231 @@
|
||||||
|
# Lite XL for AmigaOS 4.1 FE
|
||||||
|
|
||||||
|
Lite XL is a lightweight text editor written in Lua.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
You can extract the Lite XL archive wherever you want and run the *lite*
|
||||||
|
editor.
|
||||||
|
|
||||||
|
## Configuration folder
|
||||||
|
This editor creates a `.config` folder where the configuration is saved, as
|
||||||
|
well as plugins, themes etc.. By default this AmigaOS 4.1 FE version uses the
|
||||||
|
executable folder, but if you want to ovveride it, create an ENV variable
|
||||||
|
named `HOME` and set there your path.
|
||||||
|
|
||||||
|
You can check if there is one already set by executing the following command
|
||||||
|
in a shell
|
||||||
|
```
|
||||||
|
GetEnv HOME
|
||||||
|
```
|
||||||
|
If there is one set, then you will see the path at the output.
|
||||||
|
|
||||||
|
Otherwise, you can set your home path be executing the following command.
|
||||||
|
Change the path to the one of your preference.
|
||||||
|
```
|
||||||
|
SetEnv SAVE HOME "Sys:home/"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Addons
|
||||||
|
### Colors
|
||||||
|
Colors are lua files that set the color scheme of the editor. There are
|
||||||
|
light and dark themes for you to choose.
|
||||||
|
|
||||||
|
To install and use them you have to copy the ones you would like from
|
||||||
|
`addons/colors/light` or `addons/colors/dark` into the folder
|
||||||
|
`.config/lite-xl/colors/`. Don't add light or dark folders. Just copy the
|
||||||
|
.lua files in there.
|
||||||
|
|
||||||
|
Then you have to start Lite XL and open your configuration by clicking
|
||||||
|
at the cog icon at the toolbar (bottom left sixth icon). Go at the line
|
||||||
|
that looks like below
|
||||||
|
```
|
||||||
|
-- core.reload_module("colors.summer")
|
||||||
|
```
|
||||||
|
and change the `summer` with the name of your color theme. Also, remove
|
||||||
|
the two dashes `--` at the start of the line and save the file. If you
|
||||||
|
did everything right, the color schema should change instantly.
|
||||||
|
|
||||||
|
The themes can also be found at
|
||||||
|
https://github.com/lite-xl/lite-xl-colors
|
||||||
|
|
||||||
|
### Plugins
|
||||||
|
The Lite XL that you are using on AmigaOS 4 is based on version 1.16.12
|
||||||
|
and not the latest version that is available by the development team.
|
||||||
|
This means that the latest plugins are not working at all or need some
|
||||||
|
modifications to work.
|
||||||
|
|
||||||
|
To make it easier for you, I gathered some of the plugins that are working
|
||||||
|
well, and I included them at the `addons/plugins`. For you to install the
|
||||||
|
ones you would like to use, you have to copy the `.lua` files into the
|
||||||
|
folder `.config/lite-xl/plugins/` and restart the editor.
|
||||||
|
|
||||||
|
The included plugins are the following:
|
||||||
|
|
||||||
|
**autoinsert**
|
||||||
|
Automatically inserts closing brackets and quotes. Also allows selected
|
||||||
|
text to be wrapped with brackets or quotes.
|
||||||
|
|
||||||
|
**autowrap**
|
||||||
|
Automatically hardwraps lines when typing
|
||||||
|
|
||||||
|
**bigclock**
|
||||||
|
Shows the current time and date in a view with large text
|
||||||
|
|
||||||
|
**bracketmatch**
|
||||||
|
Underlines matching pair for bracket under the caret
|
||||||
|
|
||||||
|
**colorpreview**
|
||||||
|
Underlays color values (eg. `#ff00ff` or `rgb(255, 0, 255)`) with their
|
||||||
|
resultant color.
|
||||||
|
|
||||||
|
**eofnewline**
|
||||||
|
Make sure the file ends with one blank line.
|
||||||
|
|
||||||
|
**ephemeraldocviews**
|
||||||
|
Preview tabs. Opening a doc will replace the contents of the preview tab.
|
||||||
|
Marks tabs as non-preview on any change or tab double clicking.
|
||||||
|
|
||||||
|
**ghmarkdown**
|
||||||
|
Opens a preview of the current markdown file in a browser window
|
||||||
|
|
||||||
|
**hidelinenumbers**
|
||||||
|
Hides the line numbers on the left of documents
|
||||||
|
|
||||||
|
**indentguide**
|
||||||
|
Adds indent guides
|
||||||
|
|
||||||
|
**language_make**
|
||||||
|
Syntax for the Make build system language
|
||||||
|
|
||||||
|
**language_sh**
|
||||||
|
Syntax for shell scripting language
|
||||||
|
|
||||||
|
**lfautoinsert**
|
||||||
|
Automatically inserts indentation and closing bracket/text after newline
|
||||||
|
|
||||||
|
**markers**
|
||||||
|
Add markers to docs and jump between them quickly
|
||||||
|
|
||||||
|
**memoryusage**
|
||||||
|
Show memory usage in the status view
|
||||||
|
|
||||||
|
**minimap**
|
||||||
|
Shows a minimap on the right-hand side of the docview.
|
||||||
|
|
||||||
|
**motiontrail**
|
||||||
|
Adds a motion-trail to the caret
|
||||||
|
|
||||||
|
**navigate**
|
||||||
|
Allows moving back and forward between document positions, reducing the
|
||||||
|
amount of scrolling
|
||||||
|
|
||||||
|
**rainbowparen**
|
||||||
|
Show nesting of parentheses with rainbow colours
|
||||||
|
|
||||||
|
**restoretabs**
|
||||||
|
Keep a list of recently closed tabs, and restore the tab in order on
|
||||||
|
cntrl+shift+t.
|
||||||
|
|
||||||
|
**selectionhighlight**
|
||||||
|
Highlights regions of code that match the current selection
|
||||||
|
|
||||||
|
**todotreeview**
|
||||||
|
Todo tree viewer for annotations in code like `TODO`, `BUG`, `FIX`,
|
||||||
|
`IMPROVEMENT`
|
||||||
|
|
||||||
|
## Tips and tricks
|
||||||
|
|
||||||
|
### Transitions
|
||||||
|
|
||||||
|
If you want to disable the transitions and make the scrolling a little faster,
|
||||||
|
open your configuration by clicking at the cog icon at the toolbar
|
||||||
|
(bottom left sixth icon) and add the followline at the end of the file and
|
||||||
|
save it.
|
||||||
|
|
||||||
|
```
|
||||||
|
config.transitions = false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hide files from the file list
|
||||||
|
|
||||||
|
If you would like to hide files or whole folder from the left side bar list,
|
||||||
|
open your configuration by clicking at the cog icon at the toolbar
|
||||||
|
(bottom left sixth icon) and add the followline at the end of the file and
|
||||||
|
save it. This hides all the files that start with a dot, and all the `.info`
|
||||||
|
files.
|
||||||
|
|
||||||
|
```
|
||||||
|
config.ignore_files = {"^%.", "%.info$"}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can add as many rules as you want in there, to hide fore files or
|
||||||
|
folders, as you like.
|
||||||
|
|
||||||
|
|
||||||
|
## Know issues
|
||||||
|
You can find the known issues at
|
||||||
|
https://git.walkero.gr/walkero/lite-xl/issues
|
||||||
|
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [1.16.12.7] - 2022-01-11
|
||||||
|
## Added
|
||||||
|
- Added config.scroll_past_end that when its true lets the user scroll
|
||||||
|
further than the end of the file. By default is set to true.
|
||||||
|
- Added "SDL_RENDERER_ACCELERATED" and "SDL_RENDERER_PRESENTVSYNC" on
|
||||||
|
SDL_CreateRenderer() since this reduces the CPU usage when the user
|
||||||
|
scrolls and seems to work pretty good on my systems (X5000, A1222 and
|
||||||
|
microAmigaOne). This is exeprimental. If this brings problems on your
|
||||||
|
system, you can disable them using SDL ENV variable, like below:
|
||||||
|
setenv SDL_RENDER_VSYNC 0
|
||||||
|
setenv SDL_RENDER_DRIVER "software"
|
||||||
|
- Added plugins and color schemas in addons folder and information at
|
||||||
|
the README_OS4 file (#10)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- Compiled Lite XL with gcc 8.4.0
|
||||||
|
|
||||||
|
## [1.16.12.6] - 2022-01-04
|
||||||
|
### Fixed
|
||||||
|
- Fixed a problem introduced in previous version when LiteXL was executed
|
||||||
|
from the root path of a partition or from ram disk (#13)
|
||||||
|
|
||||||
|
## [1.16.12.5] - 2022-01-03
|
||||||
|
### Changed
|
||||||
|
- Changed the Gfx memory leak solution to a fix that was applied by the
|
||||||
|
editor development team on Lua scripts at a later version. Less custom
|
||||||
|
code for AmigaOS 4 port.
|
||||||
|
- Now, when return from fullscreen, there is no extra header visible
|
||||||
|
at the top of the window content
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed the assertion error and crash when the window is resized (#2)
|
||||||
|
- Fixed the resolution on fullscreen toggle to be like the workbench (#4)
|
||||||
|
- Fixed loading the current folder from terminal using the dot, like
|
||||||
|
`lite .` or without it (#3)
|
||||||
|
|
||||||
|
## [1.16.12.4] - 2021-12-31
|
||||||
|
### Fixed
|
||||||
|
- Fixed the Gfx memory leak. Now LiteXL frees the reserved memory from the
|
||||||
|
gfx card.
|
||||||
|
|
||||||
|
## [1.16.12.3] - 2021-12-29
|
||||||
|
### Changed
|
||||||
|
- Compiled with an experimental version of the latest Anti-Grain Geometry
|
||||||
|
library. This is might have issues and crash LiteXL
|
||||||
|
|
||||||
|
## [1.16.12.2] - 2021-12-26
|
||||||
|
### Added
|
||||||
|
- Added Amiga version. This version of LiteXL is based on v1.16.12 source code
|
||||||
|
which will not change. I will use the fourth digit to distinguish different
|
||||||
|
AmigaOS 4 releases, until a new port of the latest available source (v2.x)
|
||||||
|
is made.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- The keyboard shortcuts are now working
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Now the `HOME` ENV variable is optional. If this is not set, the LiteXL
|
||||||
|
folder will be used to create user's `.config` folder
|
||||||
|
|
Binary file not shown.
|
@ -1,172 +1,216 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
|
||||||
|
|
||||||
if [ ! -e "src/api/api.h" ]; then
|
# strip-components is normally set to 1 to strip the initial "data" from the
|
||||||
echo "Please run this script from the root directory of Lite XL."; exit 1
|
# directory path.
|
||||||
fi
|
copy_directory_from_repo () {
|
||||||
|
local tar_options=()
|
||||||
source scripts/common.sh
|
if [[ $1 == --strip-components=* ]]; then
|
||||||
|
tar_options+=($1)
|
||||||
show_help() {
|
shift
|
||||||
echo
|
fi
|
||||||
echo "Usage: $0 <OPTIONS>"
|
local dirname="$1"
|
||||||
echo
|
local destdir="$2"
|
||||||
echo "Common options:"
|
git archive "$use_branch" "$dirname" --format=tar | tar xf - -C "$destdir" "${tar_options[@]}"
|
||||||
echo
|
|
||||||
echo "-h --help Show this help and exit."
|
|
||||||
echo "-b --builddir DIRNAME Set the name of the build directory (not path)."
|
|
||||||
echo " Default: '$(get_default_build_dir)'."
|
|
||||||
echo "-p --prefix PREFIX Install directory prefix."
|
|
||||||
echo " Default: '/'."
|
|
||||||
echo " --debug Debug this script."
|
|
||||||
echo
|
|
||||||
echo "Build options:"
|
|
||||||
echo
|
|
||||||
echo "-f --forcefallback Force to build subprojects dependencies statically."
|
|
||||||
echo "-B --bundle Create an App bundle (macOS only)"
|
|
||||||
echo "-P --portable Create a portable package."
|
|
||||||
echo "-O --pgo Use profile guided optimizations (pgo)."
|
|
||||||
echo " Requires running the application iteractively."
|
|
||||||
echo
|
|
||||||
echo "Package options:"
|
|
||||||
echo
|
|
||||||
echo "-d --destdir DIRNAME Set the name of the package directory (not path)."
|
|
||||||
echo " Default: 'lite-xl'."
|
|
||||||
echo "-v --version VERSION Sets the version on the package name."
|
|
||||||
echo "-A --appimage Create an AppImage (Linux only)."
|
|
||||||
echo "-D --dmg Create a DMG disk image (macOS only)."
|
|
||||||
echo " Requires NPM and AppDMG."
|
|
||||||
echo "-I --innosetup Create an InnoSetup installer (Windows only)."
|
|
||||||
echo "-r --release Compile in release mode."
|
|
||||||
echo "-S --source Create a source code package,"
|
|
||||||
echo " including subprojects dependencies."
|
|
||||||
echo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
# Check if build directory is ok to be used to build.
|
||||||
local build_dir
|
build_dir_is_usable () {
|
||||||
local build_dir_option=()
|
local build="$1"
|
||||||
local dest_dir
|
if [[ $build == */* || -z "$build" ]]; then
|
||||||
local dest_dir_option=()
|
echo "invalid build directory, no path allowed: \"$build\""
|
||||||
local prefix
|
return 1
|
||||||
local prefix_option=()
|
fi
|
||||||
local version
|
git ls-files --error-unmatch "$build" &> /dev/null
|
||||||
local version_option=()
|
if [ $? == 0 ]; then
|
||||||
local debug
|
echo "invalid path, \"$build\" is under revision control"
|
||||||
local force_fallback
|
return 1
|
||||||
local appimage
|
fi
|
||||||
local bundle
|
}
|
||||||
local innosetup
|
|
||||||
local portable
|
|
||||||
local pgo
|
|
||||||
local release
|
|
||||||
|
|
||||||
for i in "$@"; do
|
# Ordinary release build
|
||||||
case $i in
|
lite_build () {
|
||||||
-h|--help)
|
local build="$1"
|
||||||
show_help
|
build_dir_is_usable "$build" || exit 1
|
||||||
exit 0
|
rm -fr "$build"
|
||||||
;;
|
meson setup --buildtype=release "$build" || exit 1
|
||||||
-b|--builddir)
|
ninja -C "$build" || exit 1
|
||||||
build_dir="$2"
|
}
|
||||||
shift
|
|
||||||
shift
|
# Build using Profile Guided Optimizations (PGO)
|
||||||
;;
|
lite_build_pgo () {
|
||||||
-d|--destdir)
|
local build="$1"
|
||||||
dest_dir="$2"
|
build_dir_is_usable "$build" || exit 1
|
||||||
shift
|
rm -fr "$build"
|
||||||
shift
|
meson setup --buildtype=release -Db_pgo=generate "$build" || exit 1
|
||||||
;;
|
ninja -C "$build" || exit 1
|
||||||
-f|--forcefallback)
|
copy_directory_from_repo data "$build/src"
|
||||||
force_fallback="--forcefallback"
|
"$build/src/lite"
|
||||||
shift
|
meson configure -Db_pgo=use "$build"
|
||||||
;;
|
ninja -C "$build" || exit 1
|
||||||
-p|--prefix)
|
}
|
||||||
prefix="$2"
|
|
||||||
shift
|
lite_build_package_windows () {
|
||||||
shift
|
local portable="-msys"
|
||||||
;;
|
if [ "$1" == "-portable" ]; then
|
||||||
-v|--version)
|
portable=""
|
||||||
version="$2"
|
shift
|
||||||
shift
|
fi
|
||||||
shift
|
local build="$1"
|
||||||
;;
|
local arch="$2"
|
||||||
-A|--appimage)
|
local os="win"
|
||||||
appimage="--appimage"
|
local pdir=".package-build/lite-xl"
|
||||||
shift
|
if [ -z "$portable" ]; then
|
||||||
;;
|
local bindir="$pdir"
|
||||||
-B|--bundle)
|
local datadir="$pdir/data"
|
||||||
bundle="--bundle"
|
else
|
||||||
shift
|
local bindir="$pdir/bin"
|
||||||
;;
|
local datadir="$pdir/share/lite-xl"
|
||||||
-D|--dmg)
|
fi
|
||||||
dmg="--dmg"
|
mkdir -p "$bindir"
|
||||||
shift
|
mkdir -p "$datadir"
|
||||||
;;
|
for module_name in core plugins colors fonts; do
|
||||||
-I|--innosetup)
|
copy_directory_from_repo --strip-components=1 "data/$module_name" "$datadir"
|
||||||
innosetup="--innosetup"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-P|--portable)
|
|
||||||
portable="--portable"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-r|--release)
|
|
||||||
release="--release"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-S|--source)
|
|
||||||
source="--source"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-O|--pgo)
|
|
||||||
pgo="--pgo"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--debug)
|
|
||||||
debug="--debug"
|
|
||||||
set -x
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# unknown option
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
done
|
||||||
|
for module_name in plugins colors; do
|
||||||
|
cp -r "$build/third/data/$module_name" "$datadir"
|
||||||
|
done
|
||||||
|
cp "$build/src/lite.exe" "$bindir"
|
||||||
|
strip --strip-all "$bindir/lite.exe"
|
||||||
|
pushd ".package-build"
|
||||||
|
local package_name="lite-xl-$os-$arch$portable.zip"
|
||||||
|
zip "$package_name" -r "lite-xl"
|
||||||
|
mv "$package_name" ..
|
||||||
|
popd
|
||||||
|
rm -fr ".package-build"
|
||||||
|
echo "created package $package_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
lite_build_package_macos () {
|
||||||
|
local build="$1"
|
||||||
|
local arch="$2"
|
||||||
|
local os="macos"
|
||||||
|
|
||||||
if [[ -n $1 ]]; then
|
local appdir=".package-build/lite-xl.app"
|
||||||
show_help
|
local bindir="$appdir/Contents/MacOS"
|
||||||
|
local datadir="$appdir/Contents/Resources"
|
||||||
|
mkdir -p "$bindir" "$datadir"
|
||||||
|
for module_name in core plugins colors fonts; do
|
||||||
|
copy_directory_from_repo --strip-components=1 "data/$module_name" "$datadir"
|
||||||
|
done
|
||||||
|
for module_name in plugins colors; do
|
||||||
|
cp -r "$build/third/data/$module_name" "$datadir"
|
||||||
|
done
|
||||||
|
cp dev-utils/icon.icns "$appdir/Contents/Resources/icon.icns"
|
||||||
|
cp dev-utils/Info.plist "$appdir/Contents/Info.plist"
|
||||||
|
cp "$build/src/lite" "$bindir/lite-xl"
|
||||||
|
strip "$bindir/lite-xl"
|
||||||
|
pushd ".package-build"
|
||||||
|
local package_name="lite-xl-$os-$arch.zip"
|
||||||
|
zip "$package_name" -r "lite-xl.app"
|
||||||
|
mv "$package_name" ..
|
||||||
|
popd
|
||||||
|
rm -fr ".package-build"
|
||||||
|
echo "created package $package_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
lite_build_package_linux () {
|
||||||
|
local portable=""
|
||||||
|
if [ "$1" == "-portable" ]; then
|
||||||
|
portable="-portable"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
local build="$1"
|
||||||
|
local arch="$2"
|
||||||
|
local os="linux"
|
||||||
|
local pdir=".package-build/lite-xl"
|
||||||
|
if [ "$portable" == "-portable" ]; then
|
||||||
|
local bindir="$pdir"
|
||||||
|
local datadir="$pdir/data"
|
||||||
|
else
|
||||||
|
local bindir="$pdir/bin"
|
||||||
|
local datadir="$pdir/share/lite-xl"
|
||||||
|
fi
|
||||||
|
mkdir -p "$bindir"
|
||||||
|
mkdir -p "$datadir"
|
||||||
|
for module_name in core plugins colors fonts; do
|
||||||
|
copy_directory_from_repo --strip-components=1 "data/$module_name" "$datadir"
|
||||||
|
done
|
||||||
|
for module_name in plugins colors; do
|
||||||
|
cp -r "$build/third/data/$module_name" "$datadir"
|
||||||
|
done
|
||||||
|
cp "$build/src/lite" "$bindir"
|
||||||
|
strip "$bindir/lite"
|
||||||
|
if [ -z "$portable" ]; then
|
||||||
|
mkdir -p "$pdir/share/applications" "$pdir/share/icons/hicolor/scalable/apps"
|
||||||
|
cp "dev-utils/lite-xl.desktop" "$pdir/share/applications"
|
||||||
|
cp "dev-utils/lite.svg" "$pdir/share/icons/hicolor/scalable/apps/lite-xl.svg"
|
||||||
|
fi
|
||||||
|
pushd ".package-build"
|
||||||
|
local package_name="lite-xl-$os-$arch$portable.tar.gz"
|
||||||
|
tar czf "$package_name" "lite-xl"
|
||||||
|
mv "$package_name" ..
|
||||||
|
popd
|
||||||
|
rm -fr ".package-build"
|
||||||
|
echo "created package $package_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
lite_build_package () {
|
||||||
|
if [[ "$OSTYPE" == msys || "$OSTYPE" == win32 ]]; then
|
||||||
|
lite_build_package_windows "$@"
|
||||||
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
lite_build_package_macos "$@"
|
||||||
|
elif [[ "$OSTYPE" == "linux"* || "$OSTYPE" == "freebsd"* ]]; then
|
||||||
|
lite_build_package_linux "$@"
|
||||||
|
else
|
||||||
|
echo "Unknown OS type \"$OSTYPE\""
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n $build_dir ]]; then build_dir_option=("--builddir" "${build_dir}"); fi
|
|
||||||
if [[ -n $dest_dir ]]; then dest_dir_option=("--destdir" "${dest_dir}"); fi
|
|
||||||
if [[ -n $prefix ]]; then prefix_option=("--prefix" "${prefix}"); fi
|
|
||||||
if [[ -n $version ]]; then version_option=("--version" "${version}"); fi
|
|
||||||
|
|
||||||
source scripts/build.sh \
|
|
||||||
${build_dir_option[@]} \
|
|
||||||
${prefix_option[@]} \
|
|
||||||
$debug \
|
|
||||||
$force_fallback \
|
|
||||||
$bundle \
|
|
||||||
$portable \
|
|
||||||
$release \
|
|
||||||
$pgo
|
|
||||||
|
|
||||||
source scripts/package.sh \
|
|
||||||
${build_dir_option[@]} \
|
|
||||||
${dest_dir_option[@]} \
|
|
||||||
${prefix_option[@]} \
|
|
||||||
${version_option[@]} \
|
|
||||||
--binary \
|
|
||||||
--addons \
|
|
||||||
$debug \
|
|
||||||
$appimage \
|
|
||||||
$dmg \
|
|
||||||
$innosetup \
|
|
||||||
$release \
|
|
||||||
$source
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
lite_copy_third_party_modules () {
|
||||||
|
local build="$1"
|
||||||
|
curl --insecure -L "https://github.com/rxi/lite-colors/archive/master.zip" -o "$build/rxi-lite-colors.zip"
|
||||||
|
mkdir -p "$build/third/data/colors" "$build/third/data/plugins"
|
||||||
|
unzip "$build/rxi-lite-colors.zip" -d "$build"
|
||||||
|
mv "$build/lite-colors-master/colors" "$build/third/data"
|
||||||
|
rm -fr "$build/lite-colors-master"
|
||||||
|
}
|
||||||
|
|
||||||
|
unset arch
|
||||||
|
while [ ! -z {$1+x} ]; do
|
||||||
|
case $1 in
|
||||||
|
-pgo)
|
||||||
|
pgo=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-branch=*)
|
||||||
|
use_branch="${1#-branch=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
arch="$1"
|
||||||
|
break
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z ${arch+set} ]; then
|
||||||
|
echo "usage: $0 [options] <arch>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z ${use_branch+set} ]; then
|
||||||
|
use_branch="$(git rev-parse --abbrev-ref HEAD)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
build_dir=".build-$arch"
|
||||||
|
|
||||||
|
if [ -z ${pgo+set} ]; then
|
||||||
|
lite_build "$build_dir"
|
||||||
|
else
|
||||||
|
lite_build_pgo "$build_dir"
|
||||||
|
fi
|
||||||
|
lite_copy_third_party_modules "$build_dir"
|
||||||
|
lite_build_package "$build_dir" "$arch"
|
||||||
|
if [[ ! ( "$OSTYPE" == "linux"* || "$OSTYPE" == "freebsd"* || "$OSTYPE" == "darwin"* ) ]]; then
|
||||||
|
lite_build_package -portable "$build_dir" "$arch"
|
||||||
|
fi
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
CC="${CC:-gcc}"
|
|
||||||
CXX="${CXX:-g++}"
|
|
||||||
CFLAGS=
|
|
||||||
CXXFLAGS=
|
|
||||||
LDFLAGS=
|
|
||||||
BUILD_TYPE=Release
|
|
||||||
|
|
||||||
packages=(pcre2 freetype2 sdl2 lua)
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cflags+="-Wall -O3 -g -std=gnu11 -fno-strict-aliasing -Isrc -Ilib/font_renderer"
|
||||||
|
cflags+=" $(pkg-config --cflags lua5.2) $(sdl2-config --cflags)"
|
||||||
|
lflags="-static-libgcc -static-libstdc++"
|
||||||
|
for package in libagg freetype2 lua5.2 x11; do
|
||||||
|
lflags+=" $(pkg-config --libs $package)"
|
||||||
|
done
|
||||||
|
lflags+=" $(sdl2-config --libs) -lm"
|
||||||
|
|
||||||
|
if [[ $* == *windows* ]]; then
|
||||||
|
echo "cross compiling for windows is not yet supported"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
outfile="lite"
|
||||||
|
compiler="gcc"
|
||||||
|
cxxcompiler="g++"
|
||||||
|
fi
|
||||||
|
|
||||||
|
lib/font_renderer/build.sh || exit 1
|
||||||
|
libs=libfontrenderer.a
|
||||||
|
|
||||||
|
echo "compiling lite..."
|
||||||
|
for f in `find src -name "*.c"`; do
|
||||||
|
$compiler -c $cflags $f -o "${f//\//_}.o"
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
got_error=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! $got_error ]]; then
|
||||||
|
echo "linking..."
|
||||||
|
$cxxcompiler -o $outfile *.o $libs $lflags
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "cleaning up..."
|
||||||
|
rm *.o *.a
|
||||||
|
echo "done"
|
||||||
|
|
701
changelog.md
701
changelog.md
|
@ -1,519 +1,25 @@
|
||||||
# Changes Log
|
This files document the changes done in Lite XL for each release.
|
||||||
|
|
||||||
## [2.1.0] - 2022-09-25
|
### 1.16.12
|
||||||
|
|
||||||
### New Features
|
Add syntax support for C++.
|
||||||
* Make distinction between
|
|
||||||
[line and block comments](https://github.com/lite-xl/lite-xl/pull/771),
|
|
||||||
and added all appropriate functionality to the commenting/uncommenting lines.
|
|
||||||
|
|
||||||
* [Added in line paste mode](https://github.com/lite-xl/lite-xl/pull/713),
|
Respect the `XDG_CONFIG_HOME` variable if set to define the USERDIR.
|
||||||
if you copy without a selection.
|
|
||||||
|
|
||||||
* Many [improvements to treeview](https://github.com/lite-xl/lite-xl/pull/732),
|
Fix an error that prevented navigating large repositories in some rare situations.
|
||||||
including keyboard navigation of treeview, and ability to specify single vs.
|
|
||||||
double-click behavior.
|
|
||||||
|
|
||||||
* Added in [soft line wrapping](https://github.com/lite-xl/lite-xl/pull/636)
|
Minor preformance improvements for drawing operations and events handling.
|
||||||
as core plugin, under `linewrapping.lua`, use `F10` to activate.
|
|
||||||
|
|
||||||
* Revamped [StatusView](https://github.com/lite-xl/lite-xl/pull/852) API with
|
Improve macOS keybindings thanks to @bjornbm and @prantlf.
|
||||||
new features that include:
|
|
||||||
|
|
||||||
* Support for predicates, click actions, tooltips on item hover
|
Improve behavior of applications when restoring workspaces to avoid displaying empty documents.
|
||||||
and custom drawing of added items.
|
|
||||||
* Hide items that are too huge by rendering with clip_rect.
|
|
||||||
* Ability to drag or scroll the left or right if too many items to display.
|
|
||||||
* New status bar commands accessible from the command palette that
|
|
||||||
include: toggling status bar visibility, toggling specific item visibility,
|
|
||||||
enable/disable status messages, etc...
|
|
||||||
|
|
||||||
* Added `renderer.font.group` interface to set up
|
### 1.16.11
|
||||||
[font fallback groups](https://github.com/lite-xl/lite-xl/pull/616) in
|
|
||||||
the font renderer, if a token doesn't have a corresponding glyph.
|
|
||||||
|
|
||||||
**Example:**
|
When opening directories with too many files lite-xl now keep diplaying files and directories in the treeview.
|
||||||
```lua
|
The application remains functional and the directories can be explored without using too much memory.
|
||||||
local emoji_font = renderer.font.load(USERDIR .. "/fonts/NotoEmoji-Regular.ttf", 15 * SCALE)
|
In this operating mode the files of the project are not indexed so the command "Core: Find File" will act as the "Core: Open File" command.
|
||||||
local nonicons = renderer.font.load(USERDIR .. "/fonts/nonicons.ttf", 15 * SCALE)
|
The "Project Search: Find" will work by searching all the files present in the project directory even if they are not indexed.
|
||||||
style.code_font = renderer.font.group({style.code_font, nonicons, emoji_font})
|
|
||||||
```
|
|
||||||
|
|
||||||
* Added in the ability to specify
|
|
||||||
[mouse clicks](https://github.com/lite-xl/lite-xl/pull/589) in the
|
|
||||||
keymap, allowing for easy binds of `ctrl+lclick`, and the like.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```lua
|
|
||||||
keymap.add { ["ctrl+shift+3lclick"] = "core:open-log" }
|
|
||||||
```
|
|
||||||
|
|
||||||
* Improved ability for plugins to be loaded at a given time, by making the
|
|
||||||
convention of defining a config for the plugin using `common.merge` to merge
|
|
||||||
existing hashes together, rather than overwriting.
|
|
||||||
|
|
||||||
* Releases will now include all language plugins and the
|
|
||||||
[settings gui](https://github.com/lite-xl/lite-xl-plugins/pull/65) plugin.
|
|
||||||
|
|
||||||
* New [core.warn](https://github.com/lite-xl/lite-xl/pull/1005) was introduced.
|
|
||||||
|
|
||||||
* Added [suggestions warping](https://github.com/lite-xl/lite-xl/pull/1003)
|
|
||||||
for `CommandView`.
|
|
||||||
|
|
||||||
* Allow regexes in tokenizer to
|
|
||||||
[split tokens with group](https://github.com/lite-xl/lite-xl/pull/999).
|
|
||||||
|
|
||||||
* Added [settings gui support](https://github.com/lite-xl/lite-xl/pull/995)
|
|
||||||
to core plugins.
|
|
||||||
|
|
||||||
* Support for [stricter predicates](https://github.com/lite-xl/lite-xl/pull/990)
|
|
||||||
by appending a `!`, eg: `"core.docview!"`.
|
|
||||||
|
|
||||||
* [UTF8 support in tokenizer](https://github.com/lite-xl/lite-xl/pull/945)
|
|
||||||
and new utf8 counter parts of string functions,
|
|
||||||
eg: `string.ulen`, `string.ulower`, etc...
|
|
||||||
|
|
||||||
* Added [utf8 support](https://github.com/lite-xl/lite-xl/pull/986) on doc
|
|
||||||
lower and upper commands.
|
|
||||||
|
|
||||||
* Allow syntax patterns to match with the
|
|
||||||
[beginning of the line](https://github.com/lite-xl/lite-xl/pull/860).
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```lua
|
|
||||||
{ pattern = "^my_pattern_starting_at_beginning", type="symbol" }
|
|
||||||
```
|
|
||||||
|
|
||||||
* [Add View:on_file_dropped](https://github.com/lite-xl/lite-xl/pull/845).
|
|
||||||
|
|
||||||
* Implemented new function to retrieve current process id of lite-xl
|
|
||||||
[system.get_process_id()](https://github.com/lite-xl/lite-xl/pull/833).
|
|
||||||
|
|
||||||
* [Allow functions in keymap](https://github.com/lite-xl/lite-xl/pull/948).
|
|
||||||
|
|
||||||
* [Add type ahead to CommandView](https://github.com/lite-xl/lite-xl/pull/963).
|
|
||||||
|
|
||||||
* Add syntax symbols to
|
|
||||||
[auto-complete](https://github.com/lite-xl/lite-xl/pull/913).
|
|
||||||
|
|
||||||
* Add [animation categories](https://github.com/lite-xl/lite-xl/pull/941)
|
|
||||||
to enable finer transitions control.
|
|
||||||
|
|
||||||
* Added in a [native plugin](https://github.com/lite-xl/lite-xl/pull/527)
|
|
||||||
interface that allows for C-level interfacing with a statically-linked
|
|
||||||
lite-xl. The implementation of this may change in future.
|
|
||||||
|
|
||||||
* Config: added new development option to prevent plugin version checking at
|
|
||||||
startup named [skip_plugins_version](https://github.com/lite-xl/lite-xl/pull/879)
|
|
||||||
|
|
||||||
* Added a smoothing and strikethrough option to font loading
|
|
||||||
([#1087](https://github.com/lite-xl/lite-xl/pull/1087))
|
|
||||||
|
|
||||||
* Allow command predicates to manage parameters, allow overwriting commands
|
|
||||||
([#1098](https://github.com/lite-xl/lite-xl/pull/1098))
|
|
||||||
|
|
||||||
* Added in simple directory search to treeview.
|
|
||||||
([#1110](https://github.com/lite-xl/lite-xl/pull/1110))
|
|
||||||
|
|
||||||
* Added in native modules suffixes.
|
|
||||||
([#1111](https://github.com/lite-xl/lite-xl/pull/1111))
|
|
||||||
|
|
||||||
* plugin scale: added option to set default scale
|
|
||||||
([#1115](https://github.com/lite-xl/lite-xl/pull/1115))
|
|
||||||
|
|
||||||
* Added in ability to have init.so as a require for cpath.
|
|
||||||
([#1126](https://github.com/lite-xl/lite-xl/pull/1126))
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
* [Load space metrics only when creating font](https://github.com/lite-xl/lite-xl/pull/1032)
|
|
||||||
|
|
||||||
* [Performance improvement](https://github.com/lite-xl/lite-xl/pull/883)
|
|
||||||
of detect indent plugin.
|
|
||||||
|
|
||||||
* Improve performance of
|
|
||||||
[ren_draw_rect](https://github.com/lite-xl/lite-xl/pull/935).
|
|
||||||
|
|
||||||
* Improved [tokenizer performance](https://github.com/lite-xl/lite-xl/pull/896).
|
|
||||||
|
|
||||||
* drawwhitespace: [Cache whitespace location](https://github.com/lite-xl/lite-xl/pull/1030)
|
|
||||||
|
|
||||||
* CommandView: improve performance by
|
|
||||||
[only drawing visible](https://github.com/lite-xl/lite-xl/pull/1047)
|
|
||||||
|
|
||||||
### Backward Incompatible Changes
|
|
||||||
* [Upgraded Lua to 5.4](https://github.com/lite-xl/lite-xl/pull/781), which
|
|
||||||
should improve performance, and provide useful extra functionality. It should
|
|
||||||
also be more available out of the box with most modern
|
|
||||||
linux/unix-based package managers.
|
|
||||||
|
|
||||||
* Bumped plugin mod-version number as various interfaces like: `DocView`,
|
|
||||||
`StatusView` and `CommandView` have changed which should require a revision
|
|
||||||
from plugin developers to make sure their plugins work with this new release.
|
|
||||||
|
|
||||||
* Changed interface for key handling; now, all components should return true if
|
|
||||||
they've handled the event.
|
|
||||||
|
|
||||||
* For plugin developers, declaring config options by directly assigning
|
|
||||||
to the plugin table (eg: `config.plugins.plugin_name.myvalue = 10`) was
|
|
||||||
deprecated in favor of using `common.merge`.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```lua
|
|
||||||
config.plugins.autowrap = common.merge({
|
|
||||||
enabled = false,
|
|
||||||
files = { "%.md$", "%.txt$" }
|
|
||||||
}, config.plugins.autowrap)
|
|
||||||
```
|
|
||||||
|
|
||||||
* `DocView:draw_text_line` and related functions been used by plugin developers
|
|
||||||
require a revision, since some of this interfaces were updated to support
|
|
||||||
line wrapping.
|
|
||||||
|
|
||||||
* Removed `cp_replace`, and replaced this with a core plugin,
|
|
||||||
[drawwhitespace.lua](https://github.com/lite-xl/lite-xl/pull/908).
|
|
||||||
|
|
||||||
### Deprecated Features
|
|
||||||
* For plugins the usage of the `--lite-xl` version tag was dropped
|
|
||||||
in favor of `--mod-version`.
|
|
||||||
|
|
||||||
* Overriding `StatusView:get_items()` has been deprecated in favor of
|
|
||||||
the new dedicated interface to insert status bar items:
|
|
||||||
|
|
||||||
**New Interface:**
|
|
||||||
```lua
|
|
||||||
------@return StatusView.Item
|
|
||||||
function StatusView:add_item(
|
|
||||||
{ predicate, name, alignment, get_item, command, position, tooltip, separator }
|
|
||||||
) end
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```lua
|
|
||||||
core.status_view:add_item({
|
|
||||||
predicate = nil,
|
|
||||||
name = "status:memory-usage",
|
|
||||||
alignment = StatusView.Item.RIGHT,
|
|
||||||
get_item = function()
|
|
||||||
return {
|
|
||||||
style.text,
|
|
||||||
string.format(
|
|
||||||
"%.2f MB",
|
|
||||||
(math.floor(collectgarbage("count") / 10.24) / 100)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
command = nil,
|
|
||||||
position = 1,
|
|
||||||
tooltip = "lua memory usage",
|
|
||||||
separator = core.status_view.separator2
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
* [CommandView:enter](https://github.com/lite-xl/lite-xl/pull/1004) now accepts
|
|
||||||
a single options table as a parameter, meaning that the old way of calling
|
|
||||||
this function will now show a deprecation message. Also `CommandView:set_text`
|
|
||||||
and `CommandView:set_hidden_suggestions` has been
|
|
||||||
[deprecated](https://github.com/lite-xl/lite-xl/pull/1014).
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```lua
|
|
||||||
core.command_view:enter("Title", {
|
|
||||||
submit = function() end,
|
|
||||||
suggest = function() return end,
|
|
||||||
cancel = function() end,
|
|
||||||
validate = function() return true end,
|
|
||||||
text = "",
|
|
||||||
select_text = false,
|
|
||||||
show_suggestions = true,
|
|
||||||
typeahead = true,
|
|
||||||
wrap = true
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Other Changes
|
|
||||||
* Removed `dmon`, and implemented independent backends for dirmonitoring. Also
|
|
||||||
more cleanly split out dirmonitoring into its own class in lua, from core.init.
|
|
||||||
We should now support FreeBSD; and any other system that uses `kqueue` as
|
|
||||||
their dir monitoring library. We also have a dummy-backend, which reverts
|
|
||||||
transparently to scanning if there is some issue with applying OS-level
|
|
||||||
watches (such as system limits).
|
|
||||||
|
|
||||||
* Removed `libagg` and the font renderer; compacted all font rendering into a
|
|
||||||
single renderer.c file which uses `libfreetype` directly. Now allows for ad-hoc
|
|
||||||
bolding, italics, and underlining of fonts.
|
|
||||||
|
|
||||||
* Removed `reproc` and replaced this with a simple POSIX/Windows implementation
|
|
||||||
in `process.c`. This allows for greater tweakability (i.e. we can now `break`
|
|
||||||
for debugging purposes), performance (startup time of subprocesses is
|
|
||||||
noticeably shorter), and simplicity (we no longer have to link reproc, or
|
|
||||||
winsock, on windows).
|
|
||||||
|
|
||||||
* [Split out `Node` and `EmptyView`](https://github.com/lite-xl/lite-xl/pull/715)
|
|
||||||
into their own lua files, for plugin extensibility reasons.
|
|
||||||
|
|
||||||
* Improved fuzzy_matching to probably give you something closer to what you're
|
|
||||||
looking for.
|
|
||||||
|
|
||||||
* Improved handling of alternate keyboard layouts.
|
|
||||||
|
|
||||||
* Added in a default keymap for `core:restart`, `ctrl+shift+r`.
|
|
||||||
|
|
||||||
* Improvements to the [C and C++](https://github.com/lite-xl/lite-xl/pull/875)
|
|
||||||
syntax files.
|
|
||||||
|
|
||||||
* Improvements to [markdown](https://github.com/lite-xl/lite-xl/pull/862)
|
|
||||||
syntax file.
|
|
||||||
|
|
||||||
* [Improvements to borderless](https://github.com/lite-xl/lite-xl/pull/994)
|
|
||||||
mode on Windows.
|
|
||||||
|
|
||||||
* Fixed a bunch of problems relating to
|
|
||||||
[multi-cursor](https://github.com/lite-xl/lite-xl/pull/886).
|
|
||||||
|
|
||||||
* NagView: [support vscroll](https://github.com/lite-xl/lite-xl/pull/876) when
|
|
||||||
message is too long.
|
|
||||||
|
|
||||||
* Meson improvements which include:
|
|
||||||
* Added in meson wraps for freetype, pcre2, and SDL2 which target public,
|
|
||||||
rather than lite-xl maintained repos.
|
|
||||||
* [Seperate dirmonitor logic](https://github.com/lite-xl/lite-xl/pull/866),
|
|
||||||
add build time detection of features.
|
|
||||||
* Add [fallbacks](https://github.com/lite-xl/lite-xl/pull/798) to all
|
|
||||||
common dependencies.
|
|
||||||
* [Update SDL to 2.0.20](https://github.com/lite-xl/lite-xl/pull/884).
|
|
||||||
* install [docs/api](https://github.com/lite-xl/lite-xl/pull/979) to datadir
|
|
||||||
for lsp support.
|
|
||||||
|
|
||||||
* Always check if the beginning of the
|
|
||||||
[text needs to be clipped](https://github.com/lite-xl/lite-xl/pull/871).
|
|
||||||
|
|
||||||
* Added [git commit](https://github.com/lite-xl/lite-xl/pull/859)
|
|
||||||
on development builds.
|
|
||||||
|
|
||||||
* Update [autocomplete](https://github.com/lite-xl/lite-xl/pull/832)
|
|
||||||
with changes needed for latest LSP plugin.
|
|
||||||
|
|
||||||
* Use SDL to manage color format mapping in
|
|
||||||
[ren_draw_rect](https://github.com/lite-xl/lite-xl/pull/829).
|
|
||||||
|
|
||||||
* Various code [clean ups](https://github.com/lite-xl/lite-xl/pull/826).
|
|
||||||
|
|
||||||
* [Autoreload Nagview](https://github.com/lite-xl/lite-xl/pull/942).
|
|
||||||
|
|
||||||
* [Enhancements to scrollbar](https://github.com/lite-xl/lite-xl/pull/916).
|
|
||||||
|
|
||||||
* Set the correct working directory for the
|
|
||||||
[AppImage version](https://github.com/lite-xl/lite-xl/pull/937).
|
|
||||||
|
|
||||||
* Core: fixes and changes to
|
|
||||||
[temp file](https://github.com/lite-xl/lite-xl/pull/906) functions.
|
|
||||||
|
|
||||||
* [Added plugin load-time log](https://github.com/lite-xl/lite-xl/pull/966).
|
|
||||||
|
|
||||||
* TreeView improvements for
|
|
||||||
[multi-project](https://github.com/lite-xl/lite-xl/pull/1010).
|
|
||||||
|
|
||||||
* Open LogView on user/project
|
|
||||||
[module reload error](https://github.com/lite-xl/lite-xl/pull/1022).
|
|
||||||
|
|
||||||
* Check if ["open" pattern is escaped](https://github.com/lite-xl/lite-xl/pull/1034)
|
|
||||||
|
|
||||||
* Support [UTF-8 on Windows](https://github.com/lite-xl/lite-xl/pull/1041) (Lua)
|
|
||||||
|
|
||||||
* Make system.* functions support
|
|
||||||
[UTF8 filenames on windows](https://github.com/lite-xl/lite-xl/pull/1042)
|
|
||||||
|
|
||||||
* [Fix memory leak](https://github.com/lite-xl/lite-xl/pull/1039) and wrong
|
|
||||||
check in font_retrieve
|
|
||||||
|
|
||||||
* Many, many, many more changes that are too numerous to list.
|
|
||||||
|
|
||||||
* CommandView: do not change caret size with config.line_height
|
|
||||||
([#1080](https://github.com/lite-xl/lite-xl/pull/1080))
|
|
||||||
|
|
||||||
## [2.0.5] - 2022-01-29
|
|
||||||
|
|
||||||
Revamp the project's user module so that modifications are immediately applied.
|
|
||||||
|
|
||||||
Add a mechanism to ignore files or directory based on their project's path.
|
|
||||||
The new mechanism is backward compatible.*
|
|
||||||
|
|
||||||
Essentially there are two mechanisms:
|
|
||||||
|
|
||||||
- if a '/' or a '/$' appear at the end of the pattern it will match only
|
|
||||||
directories
|
|
||||||
- if a '/' appears anywhere in the pattern except at the end the pattern will
|
|
||||||
be applied to the path
|
|
||||||
|
|
||||||
In the first case, when the pattern corresponds to a directory, a '/' will be
|
|
||||||
appended to the name of each directory before checking the pattern.
|
|
||||||
|
|
||||||
In the second case, when the pattern corresponds to a path, the complete path of
|
|
||||||
the file or directory will be used with an initial '/' added to the path.
|
|
||||||
|
|
||||||
Fix several problems with the directory monitoring library.
|
|
||||||
Now the application should no longer assert when some related system call fails
|
|
||||||
and we fallback to rescan when an error happens.
|
|
||||||
On linux no longer use the recursive monitoring which was a source of problem.
|
|
||||||
|
|
||||||
Directory monitoring is now aware of symlinks and treat them appropriately.
|
|
||||||
|
|
||||||
Fix problem when encountering special files type on linux.
|
|
||||||
|
|
||||||
Improve directory monitoring so that the related thread actually waits without
|
|
||||||
using any CPU time when there are no events.
|
|
||||||
|
|
||||||
Improve the suggestion when changing project folder or opening a new one.
|
|
||||||
Now the previously used directory are suggested but if the path is changed the
|
|
||||||
actual existing directories that match the pattern are suggested.
|
|
||||||
In addition always use the text entered in the command view even if a suggested
|
|
||||||
entry is highlighted.
|
|
||||||
|
|
||||||
The NagView warning window now no longer moves the document content.
|
|
||||||
|
|
||||||
## [2.0.4] - 2021-12-20
|
|
||||||
|
|
||||||
Fix some bugs related to newly introduced directory monitoring using the
|
|
||||||
dmon library.
|
|
||||||
|
|
||||||
Fix a problem with plain text search using Lua patterns by error.
|
|
||||||
|
|
||||||
Fix a problem with visualization of UTF-8 characters that caused garbage
|
|
||||||
characters visualization.
|
|
||||||
|
|
||||||
Other fixes and improvements contributed by @Guldoman.
|
|
||||||
|
|
||||||
## [2.0.3] - 2021-10-23
|
|
||||||
|
|
||||||
Replace periodic rescan of project folder with a notification based system
|
|
||||||
using the [dmon library](https://github.com/septag/dmon). Improves performance
|
|
||||||
especially for large project folders since the application no longer needs to
|
|
||||||
rescan. The application also reports immediately any change in the project
|
|
||||||
directory even when the application is unfocused.
|
|
||||||
|
|
||||||
Improved find-replace reverse and forward search.
|
|
||||||
|
|
||||||
Fixed a bug in incremental syntax highlighting affecting documents with
|
|
||||||
multiple-lines comments or strings.
|
|
||||||
|
|
||||||
The application now always shows the tabs in the documents' view even when
|
|
||||||
a single document is opened. Can be changed with the option
|
|
||||||
`config.always_show_tabs`.
|
|
||||||
|
|
||||||
Fix problem with numeric keypad function keys not properly working.
|
|
||||||
|
|
||||||
Fix problem with pixel not correctly drawn at the window's right edge.
|
|
||||||
|
|
||||||
Treat correctly and open network paths on Windows.
|
|
||||||
|
|
||||||
Add some improvements for very slow network file systems.
|
|
||||||
|
|
||||||
Fix problem with python syntax highlighting, contributed by @dflock.
|
|
||||||
|
|
||||||
## [2.0.2] - 2021-09-10
|
|
||||||
|
|
||||||
Fix problem project directory when starting the application from Launcher on
|
|
||||||
macOS.
|
|
||||||
|
|
||||||
Improved LogView. Entries can now be expanded and there is a context menu to
|
|
||||||
copy the item's content.
|
|
||||||
|
|
||||||
Change the behavior of `ctrl+d` to add a multi-cursor selection to the next
|
|
||||||
occurrence. The old behavior to move the selection to the next occurrence is
|
|
||||||
now done using the shortcut `ctrl+f3`.
|
|
||||||
|
|
||||||
Added a command to create a multi-cursor with all the occurrences of the
|
|
||||||
current selection. Activated with the shortcut `ctrl+shift+l`.
|
|
||||||
|
|
||||||
Fix problem when trying to close an unsaved new document.
|
|
||||||
|
|
||||||
No longer shows an error for the `-psn` argument passed to the application on
|
|
||||||
macOS.
|
|
||||||
|
|
||||||
Fix `treeview:open-in-system` command on Windows.
|
|
||||||
|
|
||||||
Fix rename command to update name of document if opened.
|
|
||||||
|
|
||||||
Improve the find and replace dialog so that previously used expressions can be
|
|
||||||
recalled using "up" and "down" keys.
|
|
||||||
|
|
||||||
Build package script rewrite with many improvements.
|
|
||||||
|
|
||||||
Use bigger fonts by default.
|
|
||||||
|
|
||||||
Other minor improvements and fixes.
|
|
||||||
|
|
||||||
With many thanks to the contributors: @adamharrison, @takase1121, @Guldoman,
|
|
||||||
@redtide, @Timofffee, @boppyt, @Jan200101.
|
|
||||||
|
|
||||||
## [2.0.1] - 2021-08-28
|
|
||||||
|
|
||||||
Fix a few bugs and we mandate the mod-version 2 for plugins.
|
|
||||||
This means that users should ensure they have up-to-date plugins for Lite XL 2.0.
|
|
||||||
|
|
||||||
Here some details about the bug fixes:
|
|
||||||
|
|
||||||
- fix a bug that created a fatal error when using the command to change project
|
|
||||||
folder or when closing all the active documents
|
|
||||||
- add a limit to avoid scaling fonts too much and fix a related invalid memory
|
|
||||||
access for very small fonts
|
|
||||||
- fix focus problem with NagView when switching project directory
|
|
||||||
- fix error that prevented the verification of plugins versions
|
|
||||||
- fix error on X11 that caused a bug window event on exit
|
|
||||||
|
|
||||||
## [2.0] - 2021-08-16
|
|
||||||
|
|
||||||
The 2.0 version of lite contains *breaking changes* to lite, in terms of how
|
|
||||||
plugin settings are structured; any custom plugins may need to be adjusted
|
|
||||||
accordingly (see note below about plugin namespacing).
|
|
||||||
|
|
||||||
Contains the following new features:
|
|
||||||
|
|
||||||
Full PCRE (regex) support for find and replace, as well as in language syntax
|
|
||||||
definitions. Can be accessed programatically via the lua `regex` module.
|
|
||||||
|
|
||||||
A full, finalized subprocess API, using libreproc. Subprocess can be started
|
|
||||||
and interacted with using `Process.new`.
|
|
||||||
|
|
||||||
Support for multi-cursor editing. Cursors can be created by either ctrl+clicking
|
|
||||||
on the screen, or by using the keyboard shortcuts ctrl+shift+up/down to create
|
|
||||||
an additional cursor on the previous/next line.
|
|
||||||
|
|
||||||
All build systems other than meson removed.
|
|
||||||
|
|
||||||
A more organized directory structure has been implemented; in particular a docs
|
|
||||||
folder which contains C api documentation, and a resource folder which houses
|
|
||||||
all build resources.
|
|
||||||
|
|
||||||
Plugin config namespacing has been implemented. This means that instead of
|
|
||||||
using `config.myplugin.a`, to read settings, and `config.myplugin = false` to
|
|
||||||
disable plugins, this has been changed to `config.plugins.myplugin.a`, and
|
|
||||||
`config.plugins.myplugin = false` respectively. This may require changes to
|
|
||||||
your user plugin, or to any custom plugins you have.
|
|
||||||
|
|
||||||
A context menu on right click has been added.
|
|
||||||
|
|
||||||
Changes to how we deal with indentation have been implemented; in particular,
|
|
||||||
hitting home no longer brings you to the start of a line, it'll bring you to
|
|
||||||
the start of indentation, which is more in line with other editors.
|
|
||||||
|
|
||||||
Lineguide, and scale plugins moved into the core, and removed from
|
|
||||||
`lite-plugins`. This may also require you to adjust your personal plugin
|
|
||||||
folder to remove these if they're present.
|
|
||||||
|
|
||||||
In addition, there have been many other small fixes and improvements, too
|
|
||||||
numerous to list here.
|
|
||||||
|
|
||||||
## [1.16.11] - 2021-05-28
|
|
||||||
|
|
||||||
When opening directories with too many files lite-xl now keep displaying files
|
|
||||||
and directories in the treeview. The application remains functional and the
|
|
||||||
directories can be explored without using too much memory. In this operating
|
|
||||||
mode the files of the project are not indexed so the command "Core: Find File"
|
|
||||||
will act as the "Core: Open File" command.The "Project Search: Find" will work
|
|
||||||
by searching all the files present in the project directory even if they are
|
|
||||||
not indexed.
|
|
||||||
|
|
||||||
Implemented changing fonts per syntax group by @liquidev.
|
Implemented changing fonts per syntax group by @liquidev.
|
||||||
|
|
||||||
|
@ -533,30 +39,30 @@ Fix bug with close button not working in borderless window mode.
|
||||||
|
|
||||||
Fix problem with normalization of filename for opened documents.
|
Fix problem with normalization of filename for opened documents.
|
||||||
|
|
||||||
## [1.16.10] - 2021-05-22
|
### 1.16.10
|
||||||
|
|
||||||
Improved syntax highlight system thanks to @liquidev and @adamharrison.
|
Improved syntax highlight system thanks to @liquidev and @adamharrison.
|
||||||
Thanks to the new system we provide more a accurate syntax highlighting for
|
Thanks to the new system we provide more a accurate syntax highlighting for Lua, C and C++.
|
||||||
Lua, C and C++. Other syntax improvements contributed by @vincens2005.
|
Other syntax improvements contributed by @vincens2005.
|
||||||
|
|
||||||
Move to JetBrains Mono and Fira Sans fonts for code and UI respectively.
|
Move to JetBrains Mono and Fira Sans fonts for code and UI respectively.
|
||||||
They are provided under the SIL Open Font License, Version 1.1.
|
Thet are provided under the SIL Open Font License, Version 1.1.
|
||||||
See `doc/licenses.md` for license details.
|
See `doc/licenses.md` for license details.
|
||||||
|
|
||||||
Fixed bug with fonts and rencache module. Under very specific situations the
|
Fixed bug with fonts and rencache module.
|
||||||
application was crashing due to invalid memory access.
|
Under very specific situations the application was crashing due to invalid memory access.
|
||||||
|
|
||||||
Add documentation for keymap binding, thanks to @Janis-Leuenberger.
|
Add documentation for keymap binding, thanks to @Janis-Leuenberger.
|
||||||
|
|
||||||
Added a contributors page in `doc/contributors.md`.
|
Added a contributors page in `doc/contributors.md`.
|
||||||
|
|
||||||
## [1.16.9] - 2021-05-06
|
### 1.16.9
|
||||||
|
|
||||||
Fix a bug related to nested panes resizing.
|
Fix a bug related to nested panes resizing.
|
||||||
|
|
||||||
Fix problem preventing creating a new file.
|
Fix problem preventing creating a new file.
|
||||||
|
|
||||||
## [1.16.8] - 2021-05-06
|
### 1.16.8
|
||||||
|
|
||||||
Fix application crash when using the command `core:restart`.
|
Fix application crash when using the command `core:restart`.
|
||||||
|
|
||||||
|
@ -578,28 +84,27 @@ Both kind of tags can appear in new plugins in the form:
|
||||||
|
|
||||||
where the old tag needs to appear at the end for compatibility.
|
where the old tag needs to appear at the end for compatibility.
|
||||||
|
|
||||||
## [1.16.7] - 2021-05-01
|
### 1.16.7
|
||||||
|
|
||||||
Add support for retina displays on Mac OS X.
|
Add support for retina displays on Mac OS X.
|
||||||
|
|
||||||
Fix a few problems related to file paths.
|
Fix a few problems related to file paths.
|
||||||
|
|
||||||
## [1.16.6] - 2021-04-21
|
### 1.16.6
|
||||||
|
|
||||||
Implement a system to check the compatibility of plugins by checking a release
|
Implement a system to check the compatibility of plugins by checking a release tag.
|
||||||
tag. Plugins that don't have the release tag will not be loaded.
|
Plugins that don't have the release tag will not be loaded.
|
||||||
|
|
||||||
Improve and extend the NagView with keyboard commands.
|
Improve and extend the NagView with keyboard commands.
|
||||||
Special thanks to @takase1121 for the implementation and @liquidev for proposing
|
Special thanks to @takase1121 for the implementation and @liquidev for proposing and
|
||||||
and discussing the enhancements.
|
discussing the enhancements.
|
||||||
|
|
||||||
Add support to build on Mac OS X and create an application bundle.
|
Add support to build on Mac OS X and create an application bundle.
|
||||||
Special thanks to @mathewmariani for his lite-macos fork, the Mac OS specific
|
Special thanks to @mathewmariani for his lite-macos fork, the Mac OS specific
|
||||||
resources and his support.
|
resources and his support.
|
||||||
|
|
||||||
Add hook function `DocView.on_text_change` so that plugin can accurately react
|
Add hook function `DocView.on_text_change` so that plugin can accurately react on document changes.
|
||||||
on document changes. Thanks to @vincens2005 for the suggestion and testing the
|
Thanks to @vincens2005 for the suggestion and testing the implementation.
|
||||||
implementation.
|
|
||||||
|
|
||||||
Enable borderless window mode using the `config.borderless` variable.
|
Enable borderless window mode using the `config.borderless` variable.
|
||||||
If enable the system window's bar will be replaced by a title bar provided
|
If enable the system window's bar will be replaced by a title bar provided
|
||||||
|
@ -617,14 +122,13 @@ commands `draw-whitespace:toggle`, `draw-whitespace:enable`,
|
||||||
|
|
||||||
Improve the NagView to accept keyboard commands and introduce dialog commands.
|
Improve the NagView to accept keyboard commands and introduce dialog commands.
|
||||||
|
|
||||||
Add hook function `Doc:on_text_change` called on document changes, to be
|
Add hook function `Doc:on_text_change` called on document changes, to be used by plugins.
|
||||||
used by plugins.
|
|
||||||
|
|
||||||
## [1.16.5] - 2021-03-20
|
### 1.16.5
|
||||||
|
|
||||||
Hotfix for Github's issue https://github.com/franko/lite-xl/issues/122
|
Hotfix for Github's issue https://github.com/franko/lite-xl/issues/122
|
||||||
|
|
||||||
## [1.16.4] - 2021-03-20
|
### 1.16.4
|
||||||
|
|
||||||
Add tooltips to show full file names from the tree-view.
|
Add tooltips to show full file names from the tree-view.
|
||||||
|
|
||||||
|
@ -639,7 +143,7 @@ Made borders between tabs look cleaner.
|
||||||
|
|
||||||
Fix problem with files using hard tabs.
|
Fix problem with files using hard tabs.
|
||||||
|
|
||||||
## [1.16.2] - 2021-03-05
|
### 1.16.2
|
||||||
|
|
||||||
Implement close button for tabs.
|
Implement close button for tabs.
|
||||||
|
|
||||||
|
@ -647,12 +151,12 @@ Make the command view list of suggestion scrollable to see all the items.
|
||||||
|
|
||||||
Improve update/resize behavior of treeview and toolbar.
|
Improve update/resize behavior of treeview and toolbar.
|
||||||
|
|
||||||
## [1.16.1] - 2021-02-25
|
### 1.16.1
|
||||||
|
|
||||||
Improve behavior of commands to move, delete and duplicate multiple lines:
|
Improve behavior of commands to move, delete and duplicate multiple lines:
|
||||||
no longer include the last line if it does not contain any selection.
|
no longer include the last line if it does not contain any selection.
|
||||||
|
|
||||||
Fix graphical artifacts when rendering some fonts like FiraSans.
|
Fix graphical artefacts when rendering some fonts like FiraSans.
|
||||||
|
|
||||||
Introduce the `config.transitions` boolean variable.
|
Introduce the `config.transitions` boolean variable.
|
||||||
When false the transitions will be disabled and changes will be done immediately.
|
When false the transitions will be disabled and changes will be done immediately.
|
||||||
|
@ -661,7 +165,7 @@ Very useful for remote sessions where visual transitions doesn't work well.
|
||||||
Fix many small problems related to the new toolbar and the tooptips.
|
Fix many small problems related to the new toolbar and the tooptips.
|
||||||
Fix problem with spacing in treeview when using monospace fonts.
|
Fix problem with spacing in treeview when using monospace fonts.
|
||||||
|
|
||||||
## [1.16] - 2021-02-19
|
### 1.16
|
||||||
|
|
||||||
Implement a toolbar shown in the bottom part of the tree-view.
|
Implement a toolbar shown in the bottom part of the tree-view.
|
||||||
The toolbar is especially meant for new users to give an easy, visual, access
|
The toolbar is especially meant for new users to give an easy, visual, access
|
||||||
|
@ -673,8 +177,8 @@ are actually resizable.
|
||||||
Add config mechanism to disable a plugin by setting
|
Add config mechanism to disable a plugin by setting
|
||||||
`config.<plugin-name> = false`.
|
`config.<plugin-name> = false`.
|
||||||
|
|
||||||
Improve the "detect indent" plugin to take into account the syntax and exclude
|
Improve the "detect indent" plugin to take into account the syntax and exclude comments
|
||||||
comments for much accurate results.
|
for much accurate results.
|
||||||
|
|
||||||
Add command `root:close-all` to close all the documents currently opened.
|
Add command `root:close-all` to close all the documents currently opened.
|
||||||
|
|
||||||
|
@ -682,24 +186,21 @@ Show the full path filename of the active document in the window's title.
|
||||||
|
|
||||||
Fix problem with user's module reload not always enabled.
|
Fix problem with user's module reload not always enabled.
|
||||||
|
|
||||||
## [1.15] - 2021-01-04
|
### 1.15
|
||||||
|
|
||||||
**Project directories**
|
**Project directories**
|
||||||
|
|
||||||
Extend your project by adding more directories using the command
|
Extend your project by adding more directories using the command `core:add-directory`.
|
||||||
`core:add-directory`. To remove them use the corresponding command
|
To remove them use the corresponding command `core:remove-directory`.
|
||||||
`core:remove-directory`.
|
|
||||||
|
|
||||||
**Workspaces**
|
**Workspaces**
|
||||||
|
|
||||||
The workspace plugin from rxi/lite-plugins is now part of Lite XL.
|
The workspace plugin from rxi/lite-plugins is now part of Lite XL.
|
||||||
In addition to the functionalities of the original plugin the extended version
|
In addition to the functionalities of the original plugin the extended version will
|
||||||
will also remember the window size and position and the additional project
|
also remember the window size and position and the additonal project directories.
|
||||||
directories.
|
To not interfere with the project's files the workspace file is saved in the personal
|
||||||
|
Lite's configuration folder.
|
||||||
To not interfere with the project's files the workspace file is saved in the
|
On unix-like systems it will be in: `$HOME/.config/lite-xl/ws`.
|
||||||
personal Lite's configuration folder. On unix-like systems it will be in:
|
|
||||||
`$HOME/.config/lite-xl/ws`.
|
|
||||||
|
|
||||||
**Scrolling the Tree View**
|
**Scrolling the Tree View**
|
||||||
|
|
||||||
|
@ -711,11 +212,10 @@ As in the unix shell `~` is now used to identify the home directory.
|
||||||
|
|
||||||
**Files and Directories**
|
**Files and Directories**
|
||||||
|
|
||||||
Add command to create a new empty directory within the project using the
|
Add command to create a new empty directory within the project using the command
|
||||||
command `files:create-directory`.
|
`files:create-directory`.
|
||||||
|
In addition a control-click on a project directory will prompt the user to create
|
||||||
In addition a control-click on a project directory will prompt the user to
|
a new directory inside the directory pointed.
|
||||||
create a new directory inside the directory pointed.
|
|
||||||
|
|
||||||
**New welcome screen**
|
**New welcome screen**
|
||||||
|
|
||||||
|
@ -723,56 +223,51 @@ Show 'Lite XL' instead of 'lite' and the version number.
|
||||||
|
|
||||||
**Various fixes and improvements**
|
**Various fixes and improvements**
|
||||||
|
|
||||||
A few quirks previously with some of the new features have been fixed for a
|
A few quirks previously with some of the new features have been fixed for a better user experience.
|
||||||
better user experience.
|
|
||||||
|
|
||||||
## [1.14] - 2020-12-13
|
### 1.14
|
||||||
|
|
||||||
**Project Management**
|
**Project Management**
|
||||||
|
|
||||||
Add a new command, Core: Change Project Folder, to change project directory by
|
Add a new command, Core: Change Project Folder, to change project directory by staying on the same window.
|
||||||
staying on the same window. All the current opened documents will be closed.
|
All the current opened documents will be closed.
|
||||||
The new command is associated with the keyboard combination ctrl+shit+c.
|
The new command is associated with the keyboard combination ctrl+shit+c.
|
||||||
|
|
||||||
A similar command is also added, Core: Open Project Folder, with key binding
|
A similar command is also added, Core: Open Project Folder, with key binding ctrl+shift+o.
|
||||||
ctrl+shift+o. It will open the chosen folder in a new window.
|
It will open the chosen folder in a new window.
|
||||||
|
|
||||||
In addition Lite XL will now remember the recently used projects across
|
In addition Lite XL will now remember the recently used projects across different sessions.
|
||||||
different sessions. When invoked without arguments it will now open the project
|
When invoked without arguments it will now open the project more recently used.
|
||||||
more recently used. If a directory is specified it will behave like before and
|
If a directory is specified it will behave like before and open the directory indicated as an argument.
|
||||||
open the directory indicated as an argument.
|
|
||||||
|
|
||||||
**Restart command**
|
**Restart command**
|
||||||
|
|
||||||
A Core: Restart command is added to restart the editor without leaving the
|
A Core: Restart command is added to restart the editor without leaving the current window.
|
||||||
current window. Very convenient when modifying the Lua code for the editor
|
Very convenient when modifying the Lua code for the editor itself.
|
||||||
itself.
|
|
||||||
|
|
||||||
**User's setting auto-reload**
|
**User's setting auto-reload**
|
||||||
|
|
||||||
When saving the user configuration, the user's module, the changes will be
|
When saving the user configuration, the user's module, the changes will be automatically applied to the
|
||||||
automatically applied to the current instance.
|
current instance.
|
||||||
|
|
||||||
**Bundle community provided colors schemes**
|
**Bundle community provided colors schemes**
|
||||||
|
|
||||||
Included now in the release files the colors schemes from
|
Included now in the release files the colors schemes from github.com/rxi/lite-colors.
|
||||||
github.com/rxi/lite-colors.
|
|
||||||
|
|
||||||
**Usability improvements**
|
**Usability improvements**
|
||||||
|
|
||||||
Improve left and right scrolling of text to behave like other editors and
|
Improve left and right scrolling of text to behave like other editors and improves text selection with mouse.
|
||||||
improves text selection with mouse.
|
|
||||||
|
|
||||||
**Fixes**
|
**Fixes**
|
||||||
|
|
||||||
Correct font's rendering for full hinting mode when using subpixel antialiasing.
|
Correct font's rendering for full hinting mode when using subpixel antialiasing.
|
||||||
|
|
||||||
## [1.13] - 2020-12-06
|
### 1.13
|
||||||
|
|
||||||
**Rendering options for fonts**
|
**Rendering options for fonts**
|
||||||
|
|
||||||
When loading fonts with the function renderer.font.load some rendering options
|
When loading fonts with the function renderer.font.load some rendering options can
|
||||||
can be optionally specified:
|
be optionally specified:
|
||||||
|
|
||||||
- antialiasing: grayscale or subpixel
|
- antialiasing: grayscale or subpixel
|
||||||
- hinting: none, slight or full
|
- hinting: none, slight or full
|
||||||
|
@ -781,39 +276,36 @@ See data/core/style.lua for the details about its utilisation.
|
||||||
|
|
||||||
The default remains antialiasing subpixel and hinting slight to reproduce the
|
The default remains antialiasing subpixel and hinting slight to reproduce the
|
||||||
behavior of previous versions.
|
behavior of previous versions.
|
||||||
The option grayscale with full hinting is specially interesting for crisp font
|
The option grayscale with full hinting is specially interesting for crisp font rendering
|
||||||
rendering without color artifacts.
|
without color artifacts.
|
||||||
|
|
||||||
**Unix-like install directories**
|
**Unix-like install directories**
|
||||||
|
|
||||||
Use unix-like install directories for the executable and for the data directory.
|
Use unix-like install directories for the executable and for the data directory.
|
||||||
The executable will be placed under $prefix/bin and the data folder will be
|
The executable will be placed under $prefix/bin and the data folder will be
|
||||||
$prefix/share/lite-xl.
|
$prefix/share/lite-xl.
|
||||||
|
|
||||||
The folder $prefix is not hard-coded in the binary but is determined at runtime
|
The folder $prefix is not hard-coded in the binary but is determined at runtime
|
||||||
as the directory such as the executable is inside $prefix/bin.
|
as the directory such as the executable is inside $prefix/bin.
|
||||||
|
If no such $prefix exist it will fall back to the old behavior and use the "data"
|
||||||
|
folder from the executable directory.
|
||||||
|
|
||||||
If no such $prefix exist it will fall back to the old behavior and use the
|
In addtion to the `EXEDIR` global variable an additional variable is exposed, `DATADIR`,
|
||||||
"data" folder from the executable directory.
|
to point to the data directory.
|
||||||
|
|
||||||
In addtion to the `EXEDIR` global variable an additional variable is exposed,
|
The old behavior using the "data" directory can be still selected at compile time
|
||||||
`DATADIR`, to point to the data directory.
|
using the "portable" option. The released Windows package will use the "data"
|
||||||
|
directory as before.
|
||||||
The old behavior using the "data" directory can be still selected at compile
|
|
||||||
time using the "portable" option. The released Windows package will use the
|
|
||||||
"data" directory as before.
|
|
||||||
|
|
||||||
**Configuration stored into the user's home directory**
|
**Configuration stored into the user's home directory**
|
||||||
|
|
||||||
Now the Lite XL user's configuration will be stored in the user's home directory
|
Now the Lite XL user's configuration will be stored in the user's home directory under
|
||||||
under .config/lite-xl".
|
".config/lite-xl".
|
||||||
|
The home directory is determined using the "HOME" environment variable except on Windows
|
||||||
The home directory is determined using the "HOME" environment variable except
|
wher "USERPROFILE" is used instead.
|
||||||
on Windows wher "USERPROFILE" is used instead.
|
|
||||||
|
|
||||||
A new global variable `USERDIR` is exposed to point to the user's directory.
|
A new global variable `USERDIR` is exposed to point to the user's directory.
|
||||||
|
|
||||||
## [1.11] - 2020-07-05
|
### 1.11
|
||||||
|
|
||||||
- include changes from rxi's Lite 1.11
|
- include changes from rxi's Lite 1.11
|
||||||
- fix behavior of tab to indent multiple lines
|
- fix behavior of tab to indent multiple lines
|
||||||
|
@ -821,36 +313,11 @@ A new global variable `USERDIR` is exposed to point to the user's directory.
|
||||||
- limit project scan to a maximum number of files to limit memory usage
|
- limit project scan to a maximum number of files to limit memory usage
|
||||||
- list recently visited files when using "Find File" command
|
- list recently visited files when using "Find File" command
|
||||||
|
|
||||||
## [1.08] - 2020-06-14
|
### 1.08
|
||||||
|
|
||||||
- Subpixel font rendering, removed gamma correction
|
- Subpixel font rendering, removed gamma correction
|
||||||
- Avoid using CPU when the editor is idle
|
- Avoid using CPU when the editor is idle
|
||||||
|
|
||||||
## [1.06] - 2020-05-31
|
### 1.06
|
||||||
|
|
||||||
- subpixel font rendering with gamma correction
|
- subpixel font rendering with gamma correction
|
||||||
|
|
||||||
[2.1.0]: https://github.com/lite-xl/lite-xl/releases/tag/v2.1.0
|
|
||||||
[2.0.5]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.5
|
|
||||||
[2.0.4]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.4
|
|
||||||
[2.0.3]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.3
|
|
||||||
[2.0.2]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.2
|
|
||||||
[2.0.1]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.1
|
|
||||||
[2.0]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.0
|
|
||||||
[1.16.11]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.11
|
|
||||||
[1.16.10]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.10
|
|
||||||
[1.16.9]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.9
|
|
||||||
[1.16.8]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.8
|
|
||||||
[1.16.7]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.7
|
|
||||||
[1.16.6]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.6
|
|
||||||
[1.16.5]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.5
|
|
||||||
[1.16.4]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.4
|
|
||||||
[1.16.2]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.2-lite-xl
|
|
||||||
[1.16.1]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.1-lite-xl
|
|
||||||
[1.16]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.0-lite-xl
|
|
||||||
[1.15]: https://github.com/lite-xl/lite-xl/releases/tag/v1.15-lite-xl
|
|
||||||
[1.14]: https://github.com/lite-xl/lite-xl/releases/tag/v1.14-lite-xl
|
|
||||||
[1.13]: https://github.com/lite-xl/lite-xl/releases/tag/v1.13-lite-xl
|
|
||||||
[1.11]: https://github.com/lite-xl/lite-xl/releases/tag/v1.11-lite-xl
|
|
||||||
[1.08]: https://github.com/lite-xl/lite-xl/releases/tag/v1.08-subpixel
|
|
||||||
[1.06]: https://github.com/lite-xl/lite-xl/releases/tag/1.06-subpixel-rc1
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
local style = require "core.style"
|
|
||||||
local common = require "core.common"
|
|
||||||
|
|
||||||
style.background = { common.color "#2e2e32" } -- Docview
|
|
||||||
style.background2 = { common.color "#252529" } -- Treeview
|
|
||||||
style.background3 = { common.color "#252529" } -- Command view
|
|
||||||
style.text = { common.color "#97979c" }
|
|
||||||
style.caret = { common.color "#93DDFA" }
|
|
||||||
style.accent = { common.color "#e1e1e6" }
|
|
||||||
-- style.dim - text color for nonactive tabs, tabs divider, prefix in log and
|
|
||||||
-- search result, hotkeys for context menu and command view
|
|
||||||
style.dim = { common.color "#525257" }
|
|
||||||
style.divider = { common.color "#202024" } -- Line between nodes
|
|
||||||
style.selection = { common.color "#48484f" }
|
|
||||||
style.line_number = { common.color "#525259" }
|
|
||||||
style.line_number2 = { common.color "#83838f" } -- With cursor
|
|
||||||
style.line_highlight = { common.color "#343438" }
|
|
||||||
style.scrollbar = { common.color "#414146" }
|
|
||||||
style.scrollbar2 = { common.color "#4b4b52" } -- Hovered
|
|
||||||
style.scrollbar_track = { common.color "#252529" }
|
|
||||||
style.nagbar = { common.color "#FF0000" }
|
|
||||||
style.nagbar_text = { common.color "#FFFFFF" }
|
|
||||||
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
|
|
||||||
style.drag_overlay = { common.color "rgba(255,255,255,0.1)" }
|
|
||||||
style.drag_overlay_tab = { common.color "#93DDFA" }
|
|
||||||
style.good = { common.color "#72b886" }
|
|
||||||
style.warn = { common.color "#FFA94D" }
|
|
||||||
style.error = { common.color "#FF3333" }
|
|
||||||
style.modified = { common.color "#1c7c9c" }
|
|
||||||
|
|
||||||
style.syntax["normal"] = { common.color "#e1e1e6" }
|
|
||||||
style.syntax["symbol"] = { common.color "#e1e1e6" }
|
|
||||||
style.syntax["comment"] = { common.color "#676b6f" }
|
|
||||||
style.syntax["keyword"] = { common.color "#E58AC9" } -- local function end if case
|
|
||||||
style.syntax["keyword2"] = { common.color "#F77483" } -- self int float
|
|
||||||
style.syntax["number"] = { common.color "#FFA94D" }
|
|
||||||
style.syntax["literal"] = { common.color "#FFA94D" } -- true false nil
|
|
||||||
style.syntax["string"] = { common.color "#f7c95c" }
|
|
||||||
style.syntax["operator"] = { common.color "#93DDFA" } -- = + - / < >
|
|
||||||
style.syntax["function"] = { common.color "#93DDFA" }
|
|
||||||
|
|
||||||
style.log["INFO"] = { icon = "i", color = style.text }
|
|
||||||
style.log["WARN"] = { icon = "!", color = style.warn }
|
|
||||||
style.log["ERROR"] = { icon = "!", color = style.error }
|
|
||||||
|
|
||||||
return style
|
|
|
@ -1,51 +0,0 @@
|
||||||
local b05 = 'rgba(0,0,0,0.5)' local red = '#994D4D'
|
|
||||||
local b80 = '#333333' local orange = '#B3661A'
|
|
||||||
local b60 = '#808080' local green = '#52994D'
|
|
||||||
local b40 = '#ADADAD' local teal = '#4D9999'
|
|
||||||
local b20 = '#CECECE' local blue = '#1A66B3'
|
|
||||||
local b00 = '#E6E6E6' local magenta = '#994D99'
|
|
||||||
--------------------------=--------------------------
|
|
||||||
local style = require 'core.style'
|
|
||||||
local common = require 'core.common'
|
|
||||||
--------------------------=--------------------------
|
|
||||||
style.line_highlight = { common.color(b20) }
|
|
||||||
style.background = { common.color(b00) }
|
|
||||||
style.background2 = { common.color(b20) }
|
|
||||||
style.background3 = { common.color(b20) }
|
|
||||||
style.text = { common.color(b60) }
|
|
||||||
style.caret = { common.color(b80) }
|
|
||||||
style.accent = { common.color(b80) }
|
|
||||||
style.dim = { common.color(b60) }
|
|
||||||
style.divider = { common.color(b40) }
|
|
||||||
style.selection = { common.color(b40) }
|
|
||||||
style.line_number = { common.color(b60) }
|
|
||||||
style.line_number2 = { common.color(b80) }
|
|
||||||
style.scrollbar = { common.color(b40) }
|
|
||||||
style.scrollbar2 = { common.color(b60) }
|
|
||||||
style.nagbar = { common.color(red) }
|
|
||||||
style.nagbar_text = { common.color(b00) }
|
|
||||||
style.nagbar_dim = { common.color(b05) }
|
|
||||||
--------------------------=--------------------------
|
|
||||||
style.syntax = {}
|
|
||||||
style.syntax['normal'] = { common.color(b80) }
|
|
||||||
style.syntax['symbol'] = { common.color(b80) }
|
|
||||||
style.syntax['comment'] = { common.color(b60) }
|
|
||||||
style.syntax['keyword'] = { common.color(blue) }
|
|
||||||
style.syntax['keyword2'] = { common.color(red) }
|
|
||||||
style.syntax['number'] = { common.color(teal) }
|
|
||||||
style.syntax['literal'] = { common.color(blue) }
|
|
||||||
style.syntax['string'] = { common.color(green) }
|
|
||||||
style.syntax['operator'] = { common.color(magenta) }
|
|
||||||
style.syntax['function'] = { common.color(blue) }
|
|
||||||
--------------------------=--------------------------
|
|
||||||
style.syntax.paren1 = { common.color(magenta) }
|
|
||||||
style.syntax.paren2 = { common.color(orange) }
|
|
||||||
style.syntax.paren3 = { common.color(teal) }
|
|
||||||
style.syntax.paren4 = { common.color(blue) }
|
|
||||||
style.syntax.paren5 = { common.color(red) }
|
|
||||||
--------------------------=--------------------------
|
|
||||||
style.lint = {}
|
|
||||||
style.lint.info = { common.color(blue) }
|
|
||||||
style.lint.hint = { common.color(green) }
|
|
||||||
style.lint.warning = { common.color(red) }
|
|
||||||
style.lint.error = { common.color(orange) }
|
|
|
@ -1,36 +0,0 @@
|
||||||
local bit = {}
|
|
||||||
|
|
||||||
local LUA_NBITS = 32
|
|
||||||
local ALLONES = (~(((~0) << (LUA_NBITS - 1)) << 1))
|
|
||||||
|
|
||||||
local function trim(x)
|
|
||||||
return (x & ALLONES)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function mask(n)
|
|
||||||
return (~((ALLONES << 1) << ((n) - 1)))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function check_args(field, width)
|
|
||||||
assert(field >= 0, "field cannot be negative")
|
|
||||||
assert(width > 0, "width must be positive")
|
|
||||||
assert(field + width < LUA_NBITS and field + width >= 0,
|
|
||||||
"trying to access non-existent bits")
|
|
||||||
end
|
|
||||||
|
|
||||||
function bit.extract(n, field, width)
|
|
||||||
local w = width or 1
|
|
||||||
check_args(field, w)
|
|
||||||
local m = trim(n)
|
|
||||||
return m >> field & mask(w)
|
|
||||||
end
|
|
||||||
|
|
||||||
function bit.replace(n, v, field, width)
|
|
||||||
local w = width or 1
|
|
||||||
check_args(field, w)
|
|
||||||
local m = trim(n)
|
|
||||||
local x = v & mask(width);
|
|
||||||
return m & ~(mask(w) << field) | (x << field)
|
|
||||||
end
|
|
||||||
|
|
||||||
return bit
|
|
|
@ -6,48 +6,17 @@ command.map = {}
|
||||||
local always_true = function() return true end
|
local always_true = function() return true end
|
||||||
|
|
||||||
|
|
||||||
---Used iternally by command.add, statusview, and contextmenu to generate a
|
function command.add(predicate, map)
|
||||||
---function with a condition to evaluate returning the boolean result of this
|
|
||||||
---evaluation.
|
|
||||||
---
|
|
||||||
---If a string predicate is given it is treated as a require import that should
|
|
||||||
---return a valid object which is checked against the current active view,
|
|
||||||
---eg: "core.docview" will match any view that inherits from DocView. Appending
|
|
||||||
---a `!` at the end of the string means we want to match the given object
|
|
||||||
---from the import strcitly eg: "core.docview!" only DocView is matched.
|
|
||||||
---A function that returns a boolean can be used instead to perform a custom
|
|
||||||
---evaluation, setting to nil means always evaluates to true.
|
|
||||||
---
|
|
||||||
---@param predicate string | table | function
|
|
||||||
---@return function
|
|
||||||
function command.generate_predicate(predicate)
|
|
||||||
predicate = predicate or always_true
|
predicate = predicate or always_true
|
||||||
local strict = false
|
|
||||||
if type(predicate) == "string" then
|
if type(predicate) == "string" then
|
||||||
if predicate:match("!$") then
|
|
||||||
strict = true
|
|
||||||
predicate = predicate:gsub("!$", "")
|
|
||||||
end
|
|
||||||
predicate = require(predicate)
|
predicate = require(predicate)
|
||||||
end
|
end
|
||||||
if type(predicate) == "table" then
|
if type(predicate) == "table" then
|
||||||
local class = predicate
|
local class = predicate
|
||||||
if not strict then
|
predicate = function() return core.active_view:is(class) end
|
||||||
predicate = function(...) return core.active_view:extends(class), core.active_view, ... end
|
|
||||||
else
|
|
||||||
predicate = function(...) return core.active_view:is(class), core.active_view, ... end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return predicate
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function command.add(predicate, map)
|
|
||||||
predicate = command.generate_predicate(predicate)
|
|
||||||
for name, fn in pairs(map) do
|
for name, fn in pairs(map) do
|
||||||
if command.map[name] then
|
assert(not command.map[name], "command already exists: " .. name)
|
||||||
core.log_quiet("Replacing existing command \"%s\"", name)
|
|
||||||
end
|
|
||||||
command.map[name] = { predicate = predicate, perform = fn }
|
command.map[name] = { predicate = predicate, perform = fn }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -64,34 +33,19 @@ end
|
||||||
|
|
||||||
function command.get_all_valid()
|
function command.get_all_valid()
|
||||||
local res = {}
|
local res = {}
|
||||||
local memoized_predicates = {}
|
|
||||||
for name, cmd in pairs(command.map) do
|
for name, cmd in pairs(command.map) do
|
||||||
if memoized_predicates[cmd.predicate] == nil then
|
if cmd.predicate() then
|
||||||
memoized_predicates[cmd.predicate] = cmd.predicate()
|
|
||||||
end
|
|
||||||
if memoized_predicates[cmd.predicate] then
|
|
||||||
table.insert(res, name)
|
table.insert(res, name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
function command.is_valid(name, ...)
|
|
||||||
return command.map[name] and command.map[name].predicate(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function perform(name, ...)
|
local function perform(name)
|
||||||
local cmd = command.map[name]
|
local cmd = command.map[name]
|
||||||
if not cmd then return false end
|
if cmd and cmd.predicate() then
|
||||||
local res = { cmd.predicate(...) }
|
cmd.perform()
|
||||||
if table.remove(res, 1) then
|
|
||||||
if #res > 0 then
|
|
||||||
-- send values returned from predicate
|
|
||||||
cmd.perform(table.unpack(res))
|
|
||||||
else
|
|
||||||
-- send original parameters
|
|
||||||
cmd.perform(...)
|
|
||||||
end
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
|
@ -105,10 +59,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
function command.add_defaults()
|
function command.add_defaults()
|
||||||
local reg = {
|
local reg = { "core", "root", "command", "doc", "findreplace", "files", "drawwhitespace" }
|
||||||
"core", "root", "command", "doc", "findreplace",
|
|
||||||
"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)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,24 +1,30 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
local CommandView = require "core.commandview"
|
||||||
|
|
||||||
command.add("core.commandview", {
|
local function has_commandview()
|
||||||
["command:submit"] = function(active_view)
|
return core.active_view:is(CommandView)
|
||||||
active_view:submit()
|
end
|
||||||
|
|
||||||
|
|
||||||
|
command.add(has_commandview, {
|
||||||
|
["command:submit"] = function()
|
||||||
|
core.active_view:submit()
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["command:complete"] = function(active_view)
|
["command:complete"] = function()
|
||||||
active_view:complete()
|
core.active_view:complete()
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["command:escape"] = function(active_view)
|
["command:escape"] = function()
|
||||||
active_view:exit()
|
core.active_view:exit()
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["command:select-previous"] = function(active_view)
|
["command:select-previous"] = function()
|
||||||
active_view:move_suggestion_idx(1)
|
core.active_view:move_suggestion_idx(1)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["command:select-next"] = function(active_view)
|
["command:select-next"] = function()
|
||||||
active_view:move_suggestion_idx(-1)
|
core.active_view:move_suggestion_idx(-1)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,18 +10,7 @@ local restore_title_view = false
|
||||||
|
|
||||||
local function suggest_directory(text)
|
local function suggest_directory(text)
|
||||||
text = common.home_expand(text)
|
text = common.home_expand(text)
|
||||||
local basedir = common.dirname(core.project_dir)
|
return common.home_encode_list(text == "" and core.recent_projects or common.dir_path_suggest(text))
|
||||||
return common.home_encode_list((basedir and text == basedir .. PATHSEP or text == "") and
|
|
||||||
core.recent_projects or common.dir_path_suggest(text))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function check_directory_path(path)
|
|
||||||
local abs_path = system.absolute_path(path)
|
|
||||||
local info = abs_path and system.get_file_info(abs_path)
|
|
||||||
if not info or info.type ~= 'dir' then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
return abs_path
|
|
||||||
end
|
end
|
||||||
|
|
||||||
command.add(nil, {
|
command.add(nil, {
|
||||||
|
@ -48,47 +37,41 @@ command.add(nil, {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:reload-module"] = function()
|
["core:reload-module"] = function()
|
||||||
core.command_view:enter("Reload Module", {
|
core.command_view:enter("Reload Module", function(text, item)
|
||||||
submit = function(text, item)
|
local text = item and item.text or text
|
||||||
local text = item and item.text or text
|
core.reload_module(text)
|
||||||
core.reload_module(text)
|
core.log("Reloaded module %q", text)
|
||||||
core.log("Reloaded module %q", text)
|
end, function(text)
|
||||||
end,
|
local items = {}
|
||||||
suggest = function(text)
|
for name in pairs(package.loaded) do
|
||||||
local items = {}
|
table.insert(items, name)
|
||||||
for name in pairs(package.loaded) do
|
|
||||||
table.insert(items, name)
|
|
||||||
end
|
|
||||||
return common.fuzzy_match(items, text)
|
|
||||||
end
|
end
|
||||||
})
|
return common.fuzzy_match(items, text)
|
||||||
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:find-command"] = function()
|
["core:find-command"] = function()
|
||||||
local commands = command.get_all_valid()
|
local commands = command.get_all_valid()
|
||||||
core.command_view:enter("Do Command", {
|
core.command_view:enter("Do Command", function(text, item)
|
||||||
submit = function(text, item)
|
if item then
|
||||||
if item then
|
command.perform(item.command)
|
||||||
command.perform(item.command)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
suggest = function(text)
|
|
||||||
local res = common.fuzzy_match(commands, text)
|
|
||||||
for i, name in ipairs(res) do
|
|
||||||
res[i] = {
|
|
||||||
text = command.prettify_name(name),
|
|
||||||
info = keymap.get_binding(name),
|
|
||||||
command = name,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
end
|
end
|
||||||
})
|
end, function(text)
|
||||||
|
local res = common.fuzzy_match(commands, text)
|
||||||
|
for i, name in ipairs(res) do
|
||||||
|
res[i] = {
|
||||||
|
text = command.prettify_name(name),
|
||||||
|
info = keymap.get_binding(name),
|
||||||
|
command = name,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:find-file"] = function()
|
["core:find-file"] = function()
|
||||||
if not core.project_files_number() then
|
if core.project_files_limit then
|
||||||
return command.perform "core:open-file"
|
return command.perform "core:open-file"
|
||||||
end
|
end
|
||||||
local files = {}
|
local files = {}
|
||||||
for dir, item in core.get_project_files() do
|
for dir, item in core.get_project_files() do
|
||||||
|
@ -97,72 +80,47 @@ command.add(nil, {
|
||||||
table.insert(files, common.home_encode(path .. item.filename))
|
table.insert(files, common.home_encode(path .. item.filename))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
core.command_view:enter("Open File From Project", {
|
core.command_view:enter("Open File From Project", function(text, item)
|
||||||
submit = function(text, item)
|
text = item and item.text or text
|
||||||
text = item and item.text or text
|
core.root_view:open_doc(core.open_doc(common.home_expand(text)))
|
||||||
core.root_view:open_doc(core.open_doc(common.home_expand(text)))
|
end, function(text)
|
||||||
end,
|
return common.fuzzy_match_with_recents(files, core.visited_files, text)
|
||||||
suggest = function(text)
|
end)
|
||||||
return common.fuzzy_match_with_recents(files, core.visited_files, text)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:new-doc"] = function()
|
["core:new-doc"] = function()
|
||||||
core.root_view:open_doc(core.open_doc())
|
core.root_view:open_doc(core.open_doc())
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:new-named-doc"] = function()
|
|
||||||
core.command_view:enter("File name", {
|
|
||||||
submit = function(text)
|
|
||||||
core.root_view:open_doc(core.open_doc(text))
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
|
|
||||||
["core:open-file"] = function()
|
["core:open-file"] = function()
|
||||||
local view = core.active_view
|
local view = core.active_view
|
||||||
local text
|
|
||||||
if view.doc and view.doc.abs_filename then
|
if view.doc and view.doc.abs_filename then
|
||||||
local dirname, filename = view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
local dirname, filename = view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
||||||
if dirname then
|
if dirname then
|
||||||
dirname = core.normalize_to_project_dir(dirname)
|
dirname = core.normalize_to_project_dir(dirname)
|
||||||
text = dirname == core.project_dir and "" or common.home_encode(dirname) .. PATHSEP
|
local text = dirname == core.project_dir and "" or common.home_encode(dirname) .. PATHSEP
|
||||||
|
core.command_view:set_text(text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
core.command_view:enter("Open File", {
|
core.command_view:enter("Open File", function(text)
|
||||||
text = text,
|
local filename = system.absolute_path(common.home_expand(text))
|
||||||
submit = function(text)
|
core.root_view:open_doc(core.open_doc(filename))
|
||||||
local filename = system.absolute_path(common.home_expand(text))
|
end, function (text)
|
||||||
core.root_view:open_doc(core.open_doc(filename))
|
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||||
end,
|
end, nil, function(text)
|
||||||
suggest = function (text)
|
local path_stat, err = system.get_file_info(common.home_expand(text))
|
||||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
if err then
|
||||||
end,
|
core.error("Cannot open file %q: %q", text, err)
|
||||||
validate = function(text)
|
elseif path_stat.type == 'dir' then
|
||||||
local filename = common.home_expand(text)
|
core.error("Cannot open %q, is a folder", text)
|
||||||
local path_stat, err = system.get_file_info(filename)
|
else
|
||||||
if err then
|
return true
|
||||||
if err:find("No such file", 1, true) then
|
end
|
||||||
-- check if the containing directory exists
|
end)
|
||||||
local dirname = common.dirname(filename)
|
|
||||||
local dir_stat = dirname and system.get_file_info(dirname)
|
|
||||||
if not dirname or (dir_stat and dir_stat.type == 'dir') then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
core.error("Cannot open file %s: %s", text, err)
|
|
||||||
elseif path_stat.type == 'dir' then
|
|
||||||
core.error("Cannot open %s, is a folder", text)
|
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:open-log"] = function()
|
["core:open-log"] = function()
|
||||||
local node = core.root_view:get_active_node_default()
|
local node = core.root_view:get_active_node()
|
||||||
node:add_view(LogView())
|
node:add_view(LogView())
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -173,79 +131,56 @@ command.add(nil, {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:open-project-module"] = function()
|
["core:open-project-module"] = function()
|
||||||
if not system.get_file_info(".lite_project.lua") then
|
local filename = ".lite_project.lua"
|
||||||
core.try(core.write_init_project_module, ".lite_project.lua")
|
if system.get_file_info(filename) then
|
||||||
|
core.root_view:open_doc(core.open_doc(filename))
|
||||||
|
else
|
||||||
|
local doc = core.open_doc()
|
||||||
|
core.root_view:open_doc(doc)
|
||||||
|
doc:save(filename)
|
||||||
end
|
end
|
||||||
local doc = core.open_doc(".lite_project.lua")
|
|
||||||
core.root_view:open_doc(doc)
|
|
||||||
doc:save()
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:change-project-folder"] = function()
|
["core:change-project-folder"] = function()
|
||||||
local dirname = common.dirname(core.project_dir)
|
core.command_view:enter("Change Project Folder", function(text, item)
|
||||||
local text
|
text = system.absolute_path(common.home_expand(item and item.text or text))
|
||||||
if dirname then
|
if text == core.project_dir then return end
|
||||||
text = common.home_encode(dirname) .. PATHSEP
|
local path_stat = system.get_file_info(text)
|
||||||
end
|
if not path_stat or path_stat.type ~= 'dir' then
|
||||||
core.command_view:enter("Change Project Folder", {
|
core.error("Cannot open folder %q", text)
|
||||||
text = text,
|
return
|
||||||
submit = function(text)
|
end
|
||||||
local path = common.home_expand(text)
|
core.confirm_close_all(core.open_folder_project, text)
|
||||||
local abs_path = check_directory_path(path)
|
end, suggest_directory)
|
||||||
if not abs_path then
|
|
||||||
core.error("Cannot open directory %q", path)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if abs_path == core.project_dir then return end
|
|
||||||
core.confirm_close_docs(core.docs, function(dirpath)
|
|
||||||
core.open_folder_project(dirpath)
|
|
||||||
end, abs_path)
|
|
||||||
end,
|
|
||||||
suggest = suggest_directory
|
|
||||||
})
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:open-project-folder"] = function()
|
["core:open-project-folder"] = function()
|
||||||
local dirname = common.dirname(core.project_dir)
|
core.command_view:enter("Open Project", function(text, item)
|
||||||
local text
|
text = common.home_expand(item and item.text or text)
|
||||||
if dirname then
|
local path_stat = system.get_file_info(text)
|
||||||
text = common.home_encode(dirname) .. PATHSEP
|
if not path_stat or path_stat.type ~= 'dir' then
|
||||||
end
|
core.error("Cannot open folder %q", text)
|
||||||
core.command_view:enter("Open Project", {
|
return
|
||||||
text = text,
|
end
|
||||||
submit = function(text)
|
system.exec(string.format("%q %q", EXEFILE, text))
|
||||||
local path = common.home_expand(text)
|
end, suggest_directory)
|
||||||
local abs_path = check_directory_path(path)
|
|
||||||
if not abs_path then
|
|
||||||
core.error("Cannot open directory %q", path)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if abs_path == core.project_dir then
|
|
||||||
core.error("Directory %q is currently opened", abs_path)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
system.exec(string.format("%q %q", EXEFILE, abs_path))
|
|
||||||
end,
|
|
||||||
suggest = suggest_directory
|
|
||||||
})
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:add-directory"] = function()
|
["core:add-directory"] = function()
|
||||||
core.command_view:enter("Add Directory", {
|
core.command_view:enter("Add Directory", function(text)
|
||||||
submit = function(text)
|
text = common.home_expand(text)
|
||||||
text = common.home_expand(text)
|
local path_stat, err = system.get_file_info(text)
|
||||||
local path_stat, err = system.get_file_info(text)
|
if not path_stat then
|
||||||
if not path_stat then
|
core.error("cannot open %q: %s", text, err)
|
||||||
core.error("cannot open %q: %s", text, err)
|
return
|
||||||
return
|
elseif path_stat.type ~= 'dir' then
|
||||||
elseif path_stat.type ~= 'dir' then
|
core.error("%q is not a directory", text)
|
||||||
core.error("%q is not a directory", text)
|
return
|
||||||
return
|
end
|
||||||
end
|
core.add_project_directory(system.absolute_path(text))
|
||||||
core.add_project_directory(system.absolute_path(text))
|
-- TODO: add the name of directory to prioritize
|
||||||
end,
|
core.reschedule_project_scan()
|
||||||
suggest = suggest_directory
|
end, suggest_directory)
|
||||||
})
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:remove-directory"] = function()
|
["core:remove-directory"] = function()
|
||||||
|
@ -254,17 +189,14 @@ command.add(nil, {
|
||||||
for i = n, 2, -1 do
|
for i = n, 2, -1 do
|
||||||
dir_list[n - i + 1] = core.project_directories[i].name
|
dir_list[n - i + 1] = core.project_directories[i].name
|
||||||
end
|
end
|
||||||
core.command_view:enter("Remove Directory", {
|
core.command_view:enter("Remove Directory", function(text, item)
|
||||||
submit = function(text, item)
|
text = common.home_expand(item and item.text or text)
|
||||||
text = common.home_expand(item and item.text or text)
|
if not core.remove_project_directory(text) then
|
||||||
if not core.remove_project_directory(text) then
|
core.error("No directory %q to be removed", text)
|
||||||
core.error("No directory %q to be removed", text)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
suggest = function(text)
|
|
||||||
text = common.home_expand(text)
|
|
||||||
return common.home_encode_list(common.dir_list_suggest(text, dir_list))
|
|
||||||
end
|
end
|
||||||
})
|
end, function(text)
|
||||||
|
text = common.home_expand(text)
|
||||||
|
return common.home_encode_list(common.dir_list_suggest(text, dir_list))
|
||||||
|
end)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
local core = require "core"
|
|
||||||
local command = require "core.command"
|
|
||||||
local common = require "core.common"
|
|
||||||
|
|
||||||
command.add("core.nagview", {
|
|
||||||
["dialog:previous-entry"] = function(v)
|
|
||||||
local hover = v.hovered_item or 1
|
|
||||||
v:change_hovered(hover == 1 and #v.options or hover - 1)
|
|
||||||
end,
|
|
||||||
["dialog:next-entry"] = function(v)
|
|
||||||
local hover = v.hovered_item or 1
|
|
||||||
v:change_hovered(hover == #v.options and 1 or hover + 1)
|
|
||||||
end,
|
|
||||||
["dialog:select-yes"] = function(v)
|
|
||||||
if v ~= core.nag_view then return end
|
|
||||||
v:change_hovered(common.find_index(v.options, "default_yes"))
|
|
||||||
command.perform "dialog:select"
|
|
||||||
end,
|
|
||||||
["dialog:select-no"] = function(v)
|
|
||||||
if v ~= core.nag_view then return end
|
|
||||||
v:change_hovered(common.find_index(v.options, "default_no"))
|
|
||||||
command.perform "dialog:select"
|
|
||||||
end,
|
|
||||||
["dialog:select"] = function(v)
|
|
||||||
if v.hovered_item then
|
|
||||||
v.on_selected(v.options[v.hovered_item])
|
|
||||||
v:next()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
})
|
|
|
@ -16,16 +16,21 @@ local function doc()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function doc_multiline_selections(sort)
|
local function get_indent_string()
|
||||||
local iter, state, idx, line1, col1, line2, col2 = doc():get_selections(sort)
|
if config.tab_type == "hard" then
|
||||||
return function()
|
return "\t"
|
||||||
idx, line1, col1, line2, col2 = iter(state, idx)
|
|
||||||
if idx and line2 > line1 and col2 == 1 then
|
|
||||||
line2 = line2 - 1
|
|
||||||
col2 = #doc().lines[line2]
|
|
||||||
end
|
|
||||||
return idx, line1, col1, line2, col2
|
|
||||||
end
|
end
|
||||||
|
return string.rep(" ", config.indent_size)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function doc_multiline_selection(sort)
|
||||||
|
local line1, col1, line2, col2, swap = doc():get_selection(sort)
|
||||||
|
if line2 > line1 and col2 == 1 then
|
||||||
|
line2 = line2 - 1
|
||||||
|
col2 = #doc().lines[line2]
|
||||||
|
end
|
||||||
|
return line1, col1, line2, col2, swap
|
||||||
end
|
end
|
||||||
|
|
||||||
local function append_line_if_last_line(line)
|
local function append_line_if_last_line(line)
|
||||||
|
@ -34,388 +39,260 @@ local function append_line_if_last_line(line)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function save(filename)
|
local function save(filename)
|
||||||
local abs_filename
|
doc():save(filename and core.normalize_to_project_dir(filename))
|
||||||
if filename then
|
|
||||||
filename = core.normalize_to_project_dir(filename)
|
|
||||||
abs_filename = core.project_absolute_path(filename)
|
|
||||||
end
|
|
||||||
doc():save(filename, abs_filename)
|
|
||||||
local saved_filename = doc().filename
|
local saved_filename = doc().filename
|
||||||
|
core.on_doc_save(saved_filename)
|
||||||
core.log("Saved \"%s\"", saved_filename)
|
core.log("Saved \"%s\"", saved_filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function cut_or_copy(delete)
|
-- returns the size of the original indent, and the indent
|
||||||
local full_text = ""
|
-- in your config format, rounded either up or down
|
||||||
local text = ""
|
local function get_line_indent(line, rnd_up)
|
||||||
core.cursor_clipboard = {}
|
local _, e = line:find("^[ \t]+")
|
||||||
core.cursor_clipboard_whole_line = {}
|
local soft_tab = string.rep(" ", config.indent_size)
|
||||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
if config.tab_type == "hard" then
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
local indent = e and line:sub(1, e):gsub(soft_tab, "\t") or ""
|
||||||
text = doc():get_text(line1, col1, line2, col2)
|
return e, indent:gsub(" +", rnd_up and "\t" or "")
|
||||||
full_text = full_text == "" and text or (full_text .. " " .. text)
|
|
||||||
core.cursor_clipboard_whole_line[idx] = false
|
|
||||||
if delete then
|
|
||||||
doc():delete_to_cursor(idx, 0)
|
|
||||||
end
|
|
||||||
else -- Cut/copy whole line
|
|
||||||
text = doc().lines[line1]
|
|
||||||
full_text = full_text == "" and text or (full_text .. text)
|
|
||||||
core.cursor_clipboard_whole_line[idx] = true
|
|
||||||
if delete then
|
|
||||||
if line1 < #doc().lines then
|
|
||||||
doc():remove(line1, 1, line1 + 1, 1)
|
|
||||||
elseif #doc().lines == 1 then
|
|
||||||
doc():remove(line1, 1, line1, math.huge)
|
|
||||||
else
|
|
||||||
doc():remove(line1 - 1, math.huge, line1, math.huge)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
core.cursor_clipboard[idx] = text
|
|
||||||
end
|
|
||||||
core.cursor_clipboard["full"] = full_text
|
|
||||||
system.set_clipboard(full_text)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function split_cursor(direction)
|
|
||||||
local new_cursors = {}
|
|
||||||
for _, line1, col1 in doc():get_selections() do
|
|
||||||
if line1 + direction >= 1 and line1 + direction <= #doc().lines then
|
|
||||||
table.insert(new_cursors, { line1 + direction, col1 })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for i,v in ipairs(new_cursors) do doc():add_selection(v[1], v[2]) end
|
|
||||||
core.blink_reset()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function set_cursor(dv, x, y, snap_type)
|
|
||||||
local line, col = dv:resolve_screen_position(x, y)
|
|
||||||
dv.doc:set_selection(line, col, line, col)
|
|
||||||
if snap_type == "word" or snap_type == "lines" then
|
|
||||||
command.perform("doc:select-" .. snap_type)
|
|
||||||
end
|
|
||||||
dv.mouse_selecting = { line, col, snap_type }
|
|
||||||
core.blink_reset()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function line_comment(comment, line1, col1, line2, col2)
|
|
||||||
local start_comment = (type(comment) == 'table' and comment[1] or comment) .. " "
|
|
||||||
local end_comment = (type(comment) == 'table' and " " .. comment[2])
|
|
||||||
local uncomment = true
|
|
||||||
local start_offset = math.huge
|
|
||||||
for line = line1, line2 do
|
|
||||||
local text = doc().lines[line]
|
|
||||||
local s = text:find("%S")
|
|
||||||
if s then
|
|
||||||
local cs, ce = text:find(start_comment, s, true)
|
|
||||||
if cs ~= s then
|
|
||||||
uncomment = false
|
|
||||||
end
|
|
||||||
start_offset = math.min(start_offset, s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local end_line = col2 == #doc().lines[line2]
|
|
||||||
for line = line1, line2 do
|
|
||||||
local text = doc().lines[line]
|
|
||||||
local s = text:find("%S")
|
|
||||||
if s and uncomment then
|
|
||||||
if end_comment and text:sub(#text - #end_comment, #text - 1) == end_comment then
|
|
||||||
doc():remove(line, #text - #end_comment, line, #text)
|
|
||||||
end
|
|
||||||
local cs, ce = text:find(start_comment, s, true)
|
|
||||||
if ce then
|
|
||||||
doc():remove(line, cs, line, ce + 1)
|
|
||||||
end
|
|
||||||
elseif s then
|
|
||||||
doc():insert(line, start_offset, start_comment)
|
|
||||||
if end_comment then
|
|
||||||
doc():insert(line, #doc().lines[line], " " .. comment[2])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
col1 = col1 + (col1 > start_offset and #start_comment or 0) * (uncomment and -1 or 1)
|
|
||||||
col2 = col2 + (col2 > start_offset and #start_comment or 0) * (uncomment and -1 or 1)
|
|
||||||
if end_comment and end_line then
|
|
||||||
col2 = col2 + #end_comment * (uncomment and -1 or 1)
|
|
||||||
end
|
|
||||||
return line1, col1, line2, col2
|
|
||||||
end
|
|
||||||
|
|
||||||
local function block_comment(comment, line1, col1, line2, col2)
|
|
||||||
-- automatically skip spaces
|
|
||||||
local word_start = doc():get_text(line1, col1, line1, math.huge):find("%S")
|
|
||||||
local word_end = doc():get_text(line2, 1, line2, col2):find("%s*$")
|
|
||||||
col1 = col1 + (word_start and (word_start - 1) or 0)
|
|
||||||
col2 = word_end and word_end or col2
|
|
||||||
|
|
||||||
local block_start = doc():get_text(line1, col1, line1, col1 + #comment[1])
|
|
||||||
local block_end = doc():get_text(line2, col2 - #comment[2], line2, col2)
|
|
||||||
|
|
||||||
if block_start == comment[1] and block_end == comment[2] then
|
|
||||||
-- remove up to 1 whitespace after the comment
|
|
||||||
local start_len, stop_len = #comment[1], #comment[2]
|
|
||||||
if doc():get_text(line1, col1 + #comment[1], line1, col1 + #comment[1] + 1):find("%s$") then
|
|
||||||
start_len = start_len + 1
|
|
||||||
end
|
|
||||||
if doc():get_text(line2, col2 - #comment[2] - 1, line2, col2):find("^%s") then
|
|
||||||
stop_len = stop_len + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
doc():remove(line1, col1, line1, col1 + start_len)
|
|
||||||
col2 = col2 - (line1 == line2 and start_len or 0)
|
|
||||||
doc():remove(line2, col2 - stop_len, line2, col2)
|
|
||||||
|
|
||||||
return line1, col1, line2, col2 - stop_len
|
|
||||||
else
|
else
|
||||||
doc():insert(line1, col1, comment[1] .. " ")
|
local indent = e and line:sub(1, e):gsub("\t", soft_tab) or ""
|
||||||
col2 = col2 + (line1 == line2 and (#comment[1] + 1) or 0)
|
local number = #indent / #soft_tab
|
||||||
doc():insert(line2, col2, " " .. comment[2])
|
return e, indent:sub(1,
|
||||||
|
(rnd_up and math.ceil(number) or math.floor(number))*#soft_tab)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return line1, col1, line2, col2 + #comment[2] + 1
|
-- un/indents text; behaviour varies based on selection and un/indent.
|
||||||
|
-- * if there's a selection, it will stay static around the
|
||||||
|
-- text for both indenting and unindenting.
|
||||||
|
-- * if you are in the beginning whitespace of a line, and are indenting, the
|
||||||
|
-- cursor will insert the exactly appropriate amount of spaces, and jump the
|
||||||
|
-- cursor to the beginning of first non whitespace characters
|
||||||
|
-- * if you are not in the beginning whitespace of a line, and you indent, it
|
||||||
|
-- inserts the appropriate whitespace, as if you typed them normally.
|
||||||
|
-- * if you are unindenting, the cursor will jump to the start of the line,
|
||||||
|
-- and remove the appropriate amount of spaces (or a tab).
|
||||||
|
local function indent_text(unindent)
|
||||||
|
local text = get_indent_string()
|
||||||
|
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
|
||||||
|
local _, se = doc().lines[line1]:find("^[ \t]+")
|
||||||
|
local in_beginning_whitespace = col1 == 1 or (se and col1 <= se + 1)
|
||||||
|
if unindent or doc():has_selection() or in_beginning_whitespace then
|
||||||
|
local l1d, l2d = #doc().lines[line1], #doc().lines[line2]
|
||||||
|
for line = line1, line2 do
|
||||||
|
local e, rnded = get_line_indent(doc().lines[line], unindent)
|
||||||
|
doc():remove(line, 1, line, (e or 0) + 1)
|
||||||
|
doc():insert(line, 1,
|
||||||
|
unindent and rnded:sub(1, #rnded - #text) or rnded .. text)
|
||||||
|
end
|
||||||
|
l1d, l2d = #doc().lines[line1] - l1d, #doc().lines[line2] - l2d
|
||||||
|
if (unindent or in_beginning_whitespace) and not doc():has_selection() then
|
||||||
|
local start_cursor = (se and se + 1 or 1) + l1d or #(doc().lines[line1])
|
||||||
|
doc():set_selection(line1, start_cursor, line2, start_cursor, swap)
|
||||||
|
else
|
||||||
|
doc():set_selection(line1, col1 + l1d, line2, col2 + l2d, swap)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
doc():text_input(text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local commands = {
|
local commands = {
|
||||||
["doc:select-none"] = function(dv)
|
["doc:undo"] = function()
|
||||||
local line, col = dv.doc:get_selection()
|
doc():undo()
|
||||||
dv.doc:set_selection(line, col)
|
end,
|
||||||
|
|
||||||
|
["doc:redo"] = function()
|
||||||
|
doc():redo()
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:cut"] = function()
|
["doc:cut"] = function()
|
||||||
cut_or_copy(true)
|
if doc():has_selection() then
|
||||||
|
local text = doc():get_text(doc():get_selection())
|
||||||
|
system.set_clipboard(text)
|
||||||
|
doc():delete_to(0)
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:copy"] = function()
|
["doc:copy"] = function()
|
||||||
cut_or_copy(false)
|
if doc():has_selection() then
|
||||||
end,
|
local text = doc():get_text(doc():get_selection())
|
||||||
|
system.set_clipboard(text)
|
||||||
["doc:undo"] = function(dv)
|
|
||||||
dv.doc:undo()
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:redo"] = function(dv)
|
|
||||||
dv.doc:redo()
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:paste"] = function(dv)
|
|
||||||
local clipboard = system.get_clipboard()
|
|
||||||
-- If the clipboard has changed since our last look, use that instead
|
|
||||||
local external_paste = core.cursor_clipboard["full"] ~= clipboard
|
|
||||||
if external_paste then
|
|
||||||
core.cursor_clipboard = {}
|
|
||||||
core.cursor_clipboard_whole_line = {}
|
|
||||||
end
|
end
|
||||||
local value, whole_line
|
end,
|
||||||
for idx, line1, col1, line2, col2 in dv.doc:get_selections() do
|
|
||||||
if #core.cursor_clipboard_whole_line == (#dv.doc.selections/4) then
|
["doc:paste"] = function()
|
||||||
value = core.cursor_clipboard[idx]
|
doc():text_input(system.get_clipboard():gsub("\r", ""))
|
||||||
whole_line = core.cursor_clipboard_whole_line[idx] == true
|
end,
|
||||||
else
|
|
||||||
value = clipboard
|
["doc:newline"] = function()
|
||||||
whole_line = not external_paste and clipboard:find("\n") ~= nil
|
local line, col = doc():get_selection()
|
||||||
|
local indent = doc().lines[line]:match("^[\t ]*")
|
||||||
|
if col <= #indent then
|
||||||
|
indent = indent:sub(#indent + 2 - col)
|
||||||
|
end
|
||||||
|
doc():text_input("\n" .. indent)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:newline-below"] = function()
|
||||||
|
local line = doc():get_selection()
|
||||||
|
local indent = doc().lines[line]:match("^[\t ]*")
|
||||||
|
doc():insert(line, math.huge, "\n" .. indent)
|
||||||
|
doc():set_selection(line + 1, math.huge)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:newline-above"] = function()
|
||||||
|
local line = doc():get_selection()
|
||||||
|
local indent = doc().lines[line]:match("^[\t ]*")
|
||||||
|
doc():insert(line, 1, indent .. "\n")
|
||||||
|
doc():set_selection(line, math.huge)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:delete"] = function()
|
||||||
|
local line, col = doc():get_selection()
|
||||||
|
if not doc():has_selection() and doc().lines[line]:find("^%s*$", col) then
|
||||||
|
doc():remove(line, col, line, math.huge)
|
||||||
|
end
|
||||||
|
doc():delete_to(translate.next_char)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:backspace"] = function()
|
||||||
|
local line, col = doc():get_selection()
|
||||||
|
if not doc():has_selection() then
|
||||||
|
local text = doc():get_text(line, 1, line, col)
|
||||||
|
if #text >= config.indent_size and text:find("^ *$") then
|
||||||
|
doc():delete_to(0, -config.indent_size)
|
||||||
|
return
|
||||||
end
|
end
|
||||||
if whole_line then
|
end
|
||||||
dv.doc:insert(line1, 1, value:gsub("\r", ""))
|
doc():delete_to(translate.previous_char)
|
||||||
if col1 == 1 then
|
end,
|
||||||
dv.doc:move_to_cursor(idx, #value)
|
|
||||||
|
["doc:select-all"] = function()
|
||||||
|
doc():set_selection(1, 1, math.huge, math.huge)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:select-none"] = function()
|
||||||
|
local line, col = doc():get_selection()
|
||||||
|
doc():set_selection(line, col)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:select-lines"] = function()
|
||||||
|
local line1, _, line2, _, swap = doc():get_selection(true)
|
||||||
|
append_line_if_last_line(line2)
|
||||||
|
doc():set_selection(line1, 1, line2 + 1, 1, swap)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:select-word"] = function()
|
||||||
|
local line1, col1 = doc():get_selection(true)
|
||||||
|
local line1, col1 = translate.start_of_word(doc(), line1, col1)
|
||||||
|
local line2, col2 = translate.end_of_word(doc(), line1, col1)
|
||||||
|
doc():set_selection(line2, col2, line1, col1)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:join-lines"] = function()
|
||||||
|
local line1, _, line2 = doc():get_selection(true)
|
||||||
|
if line1 == line2 then line2 = line2 + 1 end
|
||||||
|
local text = doc():get_text(line1, 1, line2, math.huge)
|
||||||
|
text = text:gsub("(.-)\n[\t ]*", function(x)
|
||||||
|
return x:find("^%s*$") and x or x .. " "
|
||||||
|
end)
|
||||||
|
doc():insert(line1, 1, text)
|
||||||
|
doc():remove(line1, #text + 1, line2, math.huge)
|
||||||
|
if doc():has_selection() then
|
||||||
|
doc():set_selection(line1, math.huge)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:indent"] = function()
|
||||||
|
indent_text()
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:unindent"] = function()
|
||||||
|
indent_text(true)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:duplicate-lines"] = function()
|
||||||
|
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
|
||||||
|
append_line_if_last_line(line2)
|
||||||
|
local text = doc():get_text(line1, 1, line2 + 1, 1)
|
||||||
|
doc():insert(line2 + 1, 1, text)
|
||||||
|
local n = line2 - line1 + 1
|
||||||
|
doc():set_selection(line1 + n, col1, line2 + n, col2, swap)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:delete-lines"] = function()
|
||||||
|
local line1, col1, line2 = doc_multiline_selection(true)
|
||||||
|
append_line_if_last_line(line2)
|
||||||
|
doc():remove(line1, 1, line2 + 1, 1)
|
||||||
|
doc():set_selection(line1, col1)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:move-lines-up"] = function()
|
||||||
|
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
|
||||||
|
append_line_if_last_line(line2)
|
||||||
|
if line1 > 1 then
|
||||||
|
local text = doc().lines[line1 - 1]
|
||||||
|
doc():insert(line2 + 1, 1, text)
|
||||||
|
doc():remove(line1 - 1, 1, line1, 1)
|
||||||
|
doc():set_selection(line1 - 1, col1, line2 - 1, col2, swap)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:move-lines-down"] = function()
|
||||||
|
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
|
||||||
|
append_line_if_last_line(line2 + 1)
|
||||||
|
if line2 < #doc().lines then
|
||||||
|
local text = doc().lines[line2 + 1]
|
||||||
|
doc():remove(line2 + 1, 1, line2 + 2, 1)
|
||||||
|
doc():insert(line1, 1, text)
|
||||||
|
doc():set_selection(line1 + 1, col1, line2 + 1, col2, swap)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:toggle-line-comments"] = function()
|
||||||
|
local comment = doc().syntax.comment
|
||||||
|
if not comment then return end
|
||||||
|
local indentation = get_indent_string()
|
||||||
|
local comment_text = comment .. " "
|
||||||
|
local line1, _, line2 = doc_multiline_selection(true)
|
||||||
|
local uncomment = true
|
||||||
|
local start_offset = math.huge
|
||||||
|
for line = line1, line2 do
|
||||||
|
local text = doc().lines[line]
|
||||||
|
local s = text:find("%S")
|
||||||
|
local cs, ce = text:find(comment_text, s, true)
|
||||||
|
if s and cs ~= s then
|
||||||
|
uncomment = false
|
||||||
|
start_offset = math.min(start_offset, s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for line = line1, line2 do
|
||||||
|
local text = doc().lines[line]
|
||||||
|
local s = text:find("%S")
|
||||||
|
if uncomment then
|
||||||
|
local cs, ce = text:find(comment_text, s, true)
|
||||||
|
if ce then
|
||||||
|
doc():remove(line, cs, line, ce + 1)
|
||||||
end
|
end
|
||||||
else
|
elseif s then
|
||||||
dv.doc:text_input(value:gsub("\r", ""), idx)
|
doc():insert(line, start_offset, comment_text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:newline"] = function(dv)
|
["doc:upper-case"] = function()
|
||||||
for idx, line, col in dv.doc:get_selections(false, true) do
|
doc():replace(string.upper)
|
||||||
local indent = dv.doc.lines[line]:match("^[\t ]*")
|
|
||||||
if col <= #indent then
|
|
||||||
indent = indent:sub(#indent + 2 - col)
|
|
||||||
end
|
|
||||||
-- Remove current line if it contains only whitespace
|
|
||||||
if dv.doc.lines[line]:match("^%s+$") then
|
|
||||||
dv.doc:remove(line, 1, line, math.huge)
|
|
||||||
end
|
|
||||||
dv.doc:text_input("\n" .. indent, idx)
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:newline-below"] = function(dv)
|
["doc:lower-case"] = function()
|
||||||
for idx, line in dv.doc:get_selections(false, true) do
|
doc():replace(string.lower)
|
||||||
local indent = dv.doc.lines[line]:match("^[\t ]*")
|
|
||||||
dv.doc:insert(line, math.huge, "\n" .. indent)
|
|
||||||
dv.doc:set_selections(idx, line + 1, math.huge)
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:newline-above"] = function(dv)
|
["doc:go-to-line"] = function()
|
||||||
for idx, line in dv.doc:get_selections(false, true) do
|
local dv = dv()
|
||||||
local indent = dv.doc.lines[line]:match("^[\t ]*")
|
|
||||||
dv.doc:insert(line, 1, indent .. "\n")
|
|
||||||
dv.doc:set_selections(idx, line, math.huge)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:delete"] = function(dv)
|
|
||||||
for idx, line1, col1, line2, col2 in dv.doc:get_selections() do
|
|
||||||
if line1 == line2 and col1 == col2 and dv.doc.lines[line1]:find("^%s*$", col1) then
|
|
||||||
dv.doc:remove(line1, col1, line1, math.huge)
|
|
||||||
end
|
|
||||||
dv.doc:delete_to_cursor(idx, translate.next_char)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:backspace"] = function(dv)
|
|
||||||
local _, indent_size = dv.doc:get_indent_info()
|
|
||||||
for idx, line1, col1, line2, col2 in dv.doc:get_selections() do
|
|
||||||
if line1 == line2 and col1 == col2 then
|
|
||||||
local text = dv.doc:get_text(line1, 1, line1, col1)
|
|
||||||
if #text >= indent_size and text:find("^ *$") then
|
|
||||||
dv.doc:delete_to_cursor(idx, 0, -indent_size)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
dv.doc:delete_to_cursor(idx, translate.previous_char)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:select-all"] = function(dv)
|
|
||||||
dv.doc:set_selection(1, 1, math.huge, math.huge)
|
|
||||||
-- avoid triggering DocView:scroll_to_make_visible
|
|
||||||
dv.last_line1 = 1
|
|
||||||
dv.last_col1 = 1
|
|
||||||
dv.last_line2 = #dv.doc.lines
|
|
||||||
dv.last_col2 = #dv.doc.lines[#dv.doc.lines]
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:select-lines"] = function(dv)
|
|
||||||
for idx, line1, _, line2 in dv.doc:get_selections(true) do
|
|
||||||
append_line_if_last_line(line2)
|
|
||||||
dv.doc:set_selections(idx, line1, 1, line2 + 1, 1)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:select-word"] = function(dv)
|
|
||||||
for idx, line1, col1 in dv.doc:get_selections(true) do
|
|
||||||
local line1, col1 = translate.start_of_word(dv.doc, line1, col1)
|
|
||||||
local line2, col2 = translate.end_of_word(dv.doc, line1, col1)
|
|
||||||
dv.doc:set_selections(idx, line2, col2, line1, col1)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:join-lines"] = function(dv)
|
|
||||||
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true) do
|
|
||||||
if line1 == line2 then line2 = line2 + 1 end
|
|
||||||
local text = dv.doc:get_text(line1, 1, line2, math.huge)
|
|
||||||
text = text:gsub("(.-)\n[\t ]*", function(x)
|
|
||||||
return x:find("^%s*$") and x or x .. " "
|
|
||||||
end)
|
|
||||||
dv.doc:insert(line1, 1, text)
|
|
||||||
dv.doc:remove(line1, #text + 1, line2, math.huge)
|
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
|
||||||
dv.doc:set_selections(idx, line1, math.huge)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:indent"] = function(dv)
|
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
|
||||||
local l1, c1, l2, c2 = dv.doc:indent_text(false, line1, col1, line2, col2)
|
|
||||||
if l1 then
|
|
||||||
dv.doc:set_selections(idx, l1, c1, l2, c2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:unindent"] = function(dv)
|
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
|
||||||
local l1, c1, l2, c2 = dv.doc:indent_text(true, line1, col1, line2, col2)
|
|
||||||
if l1 then
|
|
||||||
dv.doc:set_selections(idx, l1, c1, l2, c2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:duplicate-lines"] = function(dv)
|
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
|
||||||
append_line_if_last_line(line2)
|
|
||||||
local text = doc():get_text(line1, 1, line2 + 1, 1)
|
|
||||||
dv.doc:insert(line2 + 1, 1, text)
|
|
||||||
local n = line2 - line1 + 1
|
|
||||||
dv.doc:set_selections(idx, line1 + n, col1, line2 + n, col2)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:delete-lines"] = function(dv)
|
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
|
||||||
append_line_if_last_line(line2)
|
|
||||||
dv.doc:remove(line1, 1, line2 + 1, 1)
|
|
||||||
dv.doc:set_selections(idx, line1, col1)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:move-lines-up"] = function(dv)
|
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
|
||||||
append_line_if_last_line(line2)
|
|
||||||
if line1 > 1 then
|
|
||||||
local text = doc().lines[line1 - 1]
|
|
||||||
dv.doc:insert(line2 + 1, 1, text)
|
|
||||||
dv.doc:remove(line1 - 1, 1, line1, 1)
|
|
||||||
dv.doc:set_selections(idx, line1 - 1, col1, line2 - 1, col2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:move-lines-down"] = function(dv)
|
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
|
||||||
append_line_if_last_line(line2 + 1)
|
|
||||||
if line2 < #dv.doc.lines then
|
|
||||||
local text = dv.doc.lines[line2 + 1]
|
|
||||||
dv.doc:remove(line2 + 1, 1, line2 + 2, 1)
|
|
||||||
dv.doc:insert(line1, 1, text)
|
|
||||||
dv.doc:set_selections(idx, line1 + 1, col1, line2 + 1, col2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["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
|
|
||||||
-- if nothing is selected, toggle the whole line
|
|
||||||
if line1 == line2 and col1 == col2 then
|
|
||||||
col1 = 1
|
|
||||||
col2 = #dv.doc.lines[line2]
|
|
||||||
end
|
|
||||||
dv.doc:set_selections(idx, block_comment(comment, line1, col1, line2, col2))
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:toggle-line-comments"] = function(dv)
|
|
||||||
local comment = dv.doc.syntax.comment or dv.doc.syntax.block_comment
|
|
||||||
if comment then
|
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
|
||||||
dv.doc:set_selections(idx, line_comment(comment, line1, col1, line2, col2))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:upper-case"] = function(dv)
|
|
||||||
dv.doc:replace(string.uupper)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:lower-case"] = function(dv)
|
|
||||||
dv.doc:replace(string.ulower)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:go-to-line"] = function(dv)
|
|
||||||
local items
|
local items
|
||||||
local function init_items()
|
local function init_items()
|
||||||
if items then return end
|
if items then return end
|
||||||
|
@ -427,194 +304,108 @@ local commands = {
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
core.command_view:enter("Go To Line", {
|
core.command_view:enter("Go To Line", function(text, item)
|
||||||
submit = function(text, item)
|
local line = item and item.line or tonumber(text)
|
||||||
local line = item and item.line or tonumber(text)
|
if not line then
|
||||||
if not line then
|
core.error("Invalid line number or unmatched string")
|
||||||
core.error("Invalid line number or unmatched string")
|
return
|
||||||
return
|
|
||||||
end
|
|
||||||
dv.doc:set_selection(line, 1 )
|
|
||||||
dv:scroll_to_line(line, true)
|
|
||||||
end,
|
|
||||||
suggest = function(text)
|
|
||||||
if not text:find("^%d*$") then
|
|
||||||
init_items()
|
|
||||||
return common.fuzzy_match(items, text)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
})
|
dv.doc:set_selection(line, 1 )
|
||||||
|
dv:scroll_to_line(line, true)
|
||||||
|
|
||||||
|
end, function(text)
|
||||||
|
if not text:find("^%d*$") then
|
||||||
|
init_items()
|
||||||
|
return common.fuzzy_match(items, text)
|
||||||
|
end
|
||||||
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:toggle-line-ending"] = function(dv)
|
["doc:toggle-line-ending"] = function()
|
||||||
dv.doc.crlf = not dv.doc.crlf
|
doc().crlf = not doc().crlf
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:save-as"] = function(dv)
|
["doc:save-as"] = function()
|
||||||
local last_doc = core.last_active_view and core.last_active_view.doc
|
local last_doc = core.last_active_view and core.last_active_view.doc
|
||||||
local text
|
if doc().filename then
|
||||||
if dv.doc.filename then
|
core.command_view:set_text(doc().filename)
|
||||||
text = dv.doc.filename
|
|
||||||
elseif last_doc and last_doc.filename then
|
elseif last_doc and last_doc.filename then
|
||||||
local dirname, filename = core.last_active_view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
local dirname, filename = core.last_active_view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
||||||
text = core.normalize_to_project_dir(dirname) .. PATHSEP
|
core.command_view:set_text(core.normalize_to_project_dir(dirname) .. PATHSEP)
|
||||||
end
|
end
|
||||||
core.command_view:enter("Save As", {
|
core.command_view:enter("Save As", function(filename)
|
||||||
text = text,
|
save(common.home_expand(filename))
|
||||||
submit = function(filename)
|
end, function (text)
|
||||||
save(common.home_expand(filename))
|
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||||
end,
|
end)
|
||||||
suggest = function (text)
|
|
||||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:save"] = function(dv)
|
["doc:save"] = function()
|
||||||
if dv.doc.filename then
|
if doc().filename then
|
||||||
save()
|
save()
|
||||||
else
|
else
|
||||||
command.perform("doc:save-as")
|
command.perform("doc:save-as")
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:reload"] = function(dv)
|
["doc:rename"] = function()
|
||||||
dv.doc:reload()
|
local old_filename = doc().filename
|
||||||
end,
|
|
||||||
|
|
||||||
["file:rename"] = function(dv)
|
|
||||||
local old_filename = dv.doc.filename
|
|
||||||
if not old_filename then
|
if not old_filename then
|
||||||
core.error("Cannot rename unsaved doc")
|
core.error("Cannot rename unsaved doc")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
core.command_view:enter("Rename", {
|
core.command_view:set_text(old_filename)
|
||||||
text = old_filename,
|
core.command_view:enter("Rename", function(filename)
|
||||||
submit = function(filename)
|
doc():save(filename)
|
||||||
save(common.home_expand(filename))
|
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
if filename ~= old_filename then
|
||||||
if filename ~= old_filename then
|
os.remove(old_filename)
|
||||||
os.remove(old_filename)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
suggest = function (text)
|
|
||||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
|
||||||
end
|
end
|
||||||
})
|
end, common.path_suggest)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
|
||||||
["file:delete"] = function(dv)
|
|
||||||
local filename = dv.doc.abs_filename
|
|
||||||
if not filename then
|
|
||||||
core.error("Cannot remove unsaved doc")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
for i,docview in ipairs(core.get_views_referencing_doc(dv.doc)) do
|
|
||||||
local node = core.root_view.root_node:get_node_for_view(docview)
|
|
||||||
node:close_view(core.root_view.root_node, docview)
|
|
||||||
end
|
|
||||||
os.remove(filename)
|
|
||||||
core.log("Removed \"%s\"", filename)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:select-to-cursor"] = function(dv, x, y, clicks)
|
|
||||||
local line1, col1 = select(3, doc():get_selection())
|
|
||||||
local line2, col2 = dv:resolve_screen_position(x, y)
|
|
||||||
dv.mouse_selecting = { line1, col1, nil }
|
|
||||||
dv.doc:set_selection(line2, col2, line1, col1)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:create-cursor-previous-line"] = function(dv)
|
|
||||||
split_cursor(-1)
|
|
||||||
dv.doc:merge_cursors()
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:create-cursor-next-line"] = function(dv)
|
|
||||||
split_cursor(1)
|
|
||||||
dv.doc:merge_cursors()
|
|
||||||
end
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
command.add(function(x, y)
|
|
||||||
if x == nil or y == nil or not core.active_view:is(DocView) then return false end
|
|
||||||
local dv = core.active_view
|
|
||||||
local x1,y1,x2,y2 = dv.position.x, dv.position.y, dv.position.x + dv.size.x, dv.position.y + dv.size.y
|
|
||||||
return x >= x1 + dv:get_gutter_width() and x < x2 and y >= y1 and y < y2, dv, x, y
|
|
||||||
end, {
|
|
||||||
["doc:set-cursor"] = function(dv, x, y)
|
|
||||||
set_cursor(dv, x, y, "set")
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:set-cursor-word"] = function(dv, x, y)
|
|
||||||
set_cursor(dv, x, y, "word")
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:set-cursor-line"] = function(dv, x, y, clicks)
|
|
||||||
set_cursor(dv, x, y, "lines")
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:split-cursor"] = function(dv, x, y, clicks)
|
|
||||||
local line, col = dv:resolve_screen_position(x, y)
|
|
||||||
local removal_target = nil
|
|
||||||
for idx, line1, col1 in dv.doc:get_selections(true) do
|
|
||||||
if line1 == line and col1 == col and #doc().selections > 4 then
|
|
||||||
removal_target = idx
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if removal_target then
|
|
||||||
dv.doc:remove_selection(removal_target)
|
|
||||||
else
|
|
||||||
dv.doc:add_selection(line, col, line, col)
|
|
||||||
end
|
|
||||||
dv.mouse_selecting = { line, col, "set" }
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
local translations = {
|
local translations = {
|
||||||
["previous-char"] = translate,
|
["previous-char"] = translate.previous_char,
|
||||||
["next-char"] = translate,
|
["next-char"] = translate.next_char,
|
||||||
["previous-word-start"] = translate,
|
["previous-word-start"] = translate.previous_word_start,
|
||||||
["next-word-end"] = translate,
|
["next-word-end"] = translate.next_word_end,
|
||||||
["previous-block-start"] = translate,
|
["previous-block-start"] = translate.previous_block_start,
|
||||||
["next-block-end"] = translate,
|
["next-block-end"] = translate.next_block_end,
|
||||||
["start-of-doc"] = translate,
|
["start-of-doc"] = translate.start_of_doc,
|
||||||
["end-of-doc"] = translate,
|
["end-of-doc"] = translate.end_of_doc,
|
||||||
["start-of-line"] = translate,
|
["start-of-line"] = translate.start_of_line,
|
||||||
["end-of-line"] = translate,
|
["end-of-line"] = translate.end_of_line,
|
||||||
["start-of-word"] = translate,
|
["start-of-word"] = translate.start_of_word,
|
||||||
["start-of-indentation"] = translate,
|
["end-of-word"] = translate.end_of_word,
|
||||||
["end-of-word"] = translate,
|
["previous-line"] = DocView.translate.previous_line,
|
||||||
["previous-line"] = DocView.translate,
|
["next-line"] = DocView.translate.next_line,
|
||||||
["next-line"] = DocView.translate,
|
["previous-page"] = DocView.translate.previous_page,
|
||||||
["previous-page"] = DocView.translate,
|
["next-page"] = DocView.translate.next_page,
|
||||||
["next-page"] = DocView.translate,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, obj in pairs(translations) do
|
for name, fn in pairs(translations) do
|
||||||
commands["doc:move-to-" .. name] = function(dv) dv.doc:move_to(obj[name:gsub("-", "_")], dv) end
|
commands["doc:move-to-" .. name] = function() doc():move_to(fn, dv()) end
|
||||||
commands["doc:select-to-" .. name] = function(dv) dv.doc:select_to(obj[name:gsub("-", "_")], dv) end
|
commands["doc:select-to-" .. name] = function() doc():select_to(fn, dv()) end
|
||||||
commands["doc:delete-to-" .. name] = function(dv) dv.doc:delete_to(obj[name:gsub("-", "_")], dv) end
|
commands["doc:delete-to-" .. name] = function() doc():delete_to(fn, dv()) end
|
||||||
end
|
end
|
||||||
|
|
||||||
commands["doc:move-to-previous-char"] = function(dv)
|
commands["doc:move-to-previous-char"] = function()
|
||||||
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true) do
|
if doc():has_selection() then
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
local line, col = doc():get_selection(true)
|
||||||
dv.doc:set_selections(idx, line1, col1)
|
doc():set_selection(line, col)
|
||||||
else
|
else
|
||||||
dv.doc:move_to_cursor(idx, translate.previous_char)
|
doc():move_to(translate.previous_char)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
commands["doc:move-to-next-char"] = function(dv)
|
commands["doc:move-to-next-char"] = function()
|
||||||
for idx, line1, col1, line2, col2 in dv.doc:get_selections(true) do
|
if doc():has_selection() then
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
local _, _, line, col = doc():get_selection(true)
|
||||||
dv.doc:set_selections(idx, line2, col2)
|
doc():set_selection(line, col)
|
||||||
else
|
else
|
||||||
dv.doc:move_to_cursor(idx, translate.next_char)
|
doc():move_to(translate.next_char)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local common = require "core.common"
|
|
||||||
|
|
||||||
command.add(nil, {
|
command.add(nil, {
|
||||||
["files:create-directory"] = function()
|
["files:create-directory"] = function()
|
||||||
core.command_view:enter("New directory name", {
|
core.command_view:enter("New directory name", function(text)
|
||||||
submit = function(text)
|
local success, err = system.mkdir(text)
|
||||||
local success, err, path = common.mkdirp(text)
|
if not success then
|
||||||
if not success then
|
core.error("cannot create directory %q: %s", text, err)
|
||||||
core.error("cannot create directory %q: %s", path, err)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
})
|
end)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,221 +2,151 @@ 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 search = require "core.doc.search"
|
local search = require "core.doc.search"
|
||||||
local keymap = require "core.keymap"
|
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
local CommandView = require "core.commandview"
|
|
||||||
local StatusView = require "core.statusview"
|
|
||||||
|
|
||||||
local last_view, last_fn, last_text, last_sel
|
local max_previous_finds = 50
|
||||||
|
|
||||||
local case_sensitive = config.find_case_sensitive or false
|
|
||||||
local find_regex = config.find_regex or false
|
|
||||||
local found_expression
|
|
||||||
|
|
||||||
local function doc()
|
local function doc()
|
||||||
local is_DocView = core.active_view:is(DocView) and not core.active_view:is(CommandView)
|
return core.active_view.doc
|
||||||
return is_DocView and core.active_view.doc or (last_view and last_view.doc)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_find_tooltip()
|
|
||||||
local rf = keymap.get_binding("find-replace:repeat-find")
|
|
||||||
local ti = keymap.get_binding("find-replace:toggle-sensitivity")
|
|
||||||
local tr = keymap.get_binding("find-replace:toggle-regex")
|
|
||||||
return (find_regex and "[Regex] " or "") ..
|
|
||||||
(case_sensitive and "[Sensitive] " or "") ..
|
|
||||||
(rf and ("Press " .. rf .. " to select the next match.") or "") ..
|
|
||||||
(ti and (" " .. ti .. " toggles case sensitivity.") or "") ..
|
|
||||||
(tr and (" " .. tr .. " toggles regex find.") or "")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function update_preview(sel, search_fn, text)
|
local previous_finds
|
||||||
local ok, line1, col1, line2, col2 = pcall(search_fn, last_view.doc,
|
local last_doc
|
||||||
sel[1], sel[2], text, case_sensitive, find_regex)
|
local last_fn, last_text
|
||||||
if ok and line1 and text ~= "" then
|
|
||||||
last_view.doc:set_selection(line2, col2, line1, col1)
|
|
||||||
last_view:scroll_to_line(line2, true)
|
local function push_previous_find(doc, sel)
|
||||||
found_expression = true
|
if last_doc ~= doc then
|
||||||
else
|
last_doc = doc
|
||||||
last_view.doc:set_selection(table.unpack(sel))
|
previous_finds = {}
|
||||||
found_expression = false
|
|
||||||
end
|
end
|
||||||
end
|
if #previous_finds >= max_previous_finds then
|
||||||
|
table.remove(previous_finds, 1)
|
||||||
|
|
||||||
local function insert_unique(t, v)
|
|
||||||
local n = #t
|
|
||||||
for i = 1, n do
|
|
||||||
if t[i] == v then
|
|
||||||
table.remove(t, i)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
table.insert(t, 1, v)
|
table.insert(previous_finds, sel or { doc:get_selection() })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function find(label, search_fn)
|
local function find(label, search_fn)
|
||||||
last_view, last_sel = core.active_view,
|
local dv = core.active_view
|
||||||
{ core.active_view.doc:get_selection() }
|
local sel = { dv.doc:get_selection() }
|
||||||
local text = last_view.doc:get_text(table.unpack(last_sel))
|
local text = dv.doc:get_text(table.unpack(sel))
|
||||||
found_expression = false
|
local found = false
|
||||||
|
|
||||||
core.status_view:show_tooltip(get_find_tooltip())
|
core.command_view:set_text(text, true)
|
||||||
|
|
||||||
core.command_view:enter(label, {
|
core.command_view:enter(label, function(text)
|
||||||
text = text,
|
if found then
|
||||||
select_text = true,
|
|
||||||
show_suggestions = false,
|
|
||||||
submit = function(text, item)
|
|
||||||
insert_unique(core.previous_find, text)
|
|
||||||
core.status_view:remove_tooltip()
|
|
||||||
if found_expression then
|
|
||||||
last_fn, last_text = search_fn, text
|
|
||||||
else
|
|
||||||
core.error("Couldn't find %q", text)
|
|
||||||
last_view.doc:set_selection(table.unpack(last_sel))
|
|
||||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
suggest = function(text)
|
|
||||||
update_preview(last_sel, search_fn, text)
|
|
||||||
last_fn, last_text = search_fn, text
|
last_fn, last_text = search_fn, text
|
||||||
return core.previous_find
|
previous_finds = {}
|
||||||
end,
|
push_previous_find(dv.doc, sel)
|
||||||
cancel = function(explicit)
|
else
|
||||||
core.status_view:remove_tooltip()
|
core.error("Couldn't find %q", text)
|
||||||
if explicit then
|
dv.doc:set_selection(table.unpack(sel))
|
||||||
last_view.doc:set_selection(table.unpack(last_sel))
|
dv:scroll_to_make_visible(sel[1], sel[2])
|
||||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
})
|
|
||||||
|
end, function(text)
|
||||||
|
local ok, line1, col1, line2, col2 = pcall(search_fn, dv.doc, sel[1], sel[2], text)
|
||||||
|
if ok and line1 and text ~= "" then
|
||||||
|
dv.doc:set_selection(line2, col2, line1, col1)
|
||||||
|
dv:scroll_to_line(line2, true)
|
||||||
|
found = true
|
||||||
|
else
|
||||||
|
dv.doc:set_selection(table.unpack(sel))
|
||||||
|
found = false
|
||||||
|
end
|
||||||
|
|
||||||
|
end, function(explicit)
|
||||||
|
if explicit then
|
||||||
|
dv.doc:set_selection(table.unpack(sel))
|
||||||
|
dv:scroll_to_make_visible(sel[1], sel[2])
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function replace(kind, default, fn)
|
local function replace(kind, default, fn)
|
||||||
core.status_view:show_tooltip(get_find_tooltip())
|
core.command_view:set_text(default, true)
|
||||||
core.command_view:enter("Find To Replace " .. kind, {
|
|
||||||
text = default,
|
|
||||||
select_text = true,
|
|
||||||
show_suggestions = false,
|
|
||||||
submit = function(old)
|
|
||||||
insert_unique(core.previous_find, old)
|
|
||||||
|
|
||||||
local s = string.format("Replace %s %q With", kind, old)
|
core.command_view:enter("Find To Replace " .. kind, function(old)
|
||||||
core.command_view:enter(s, {
|
core.command_view:set_text(old, true)
|
||||||
text = old,
|
|
||||||
select_text = true,
|
local s = string.format("Replace %s %q With", kind, old)
|
||||||
show_suggestions = false,
|
core.command_view:enter(s, function(new)
|
||||||
submit = function(new)
|
local n = doc():replace(function(text)
|
||||||
core.status_view:remove_tooltip()
|
return fn(text, old, new)
|
||||||
insert_unique(core.previous_replace, new)
|
end)
|
||||||
local results = doc():replace(function(text)
|
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
|
||||||
return fn(text, old, new)
|
end)
|
||||||
end)
|
end)
|
||||||
local n = 0
|
|
||||||
for _,v in pairs(results) do
|
|
||||||
n = n + v
|
|
||||||
end
|
|
||||||
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
|
|
||||||
end,
|
|
||||||
suggest = function() return core.previous_replace end,
|
|
||||||
cancel = function()
|
|
||||||
core.status_view:remove_tooltip()
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
suggest = function() return core.previous_find end,
|
|
||||||
cancel = function()
|
|
||||||
core.status_view:remove_tooltip()
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function has_selection()
|
local function has_selection()
|
||||||
return core.active_view:is(DocView) and core.active_view.doc:has_selection()
|
return core.active_view:is(DocView)
|
||||||
|
and core.active_view.doc:has_selection()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function has_unique_selection()
|
command.add(has_selection, {
|
||||||
if not doc() then return false end
|
["find-replace:select-next"] = function()
|
||||||
local text = nil
|
local l1, c1, l2, c2 = doc():get_selection(true)
|
||||||
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do
|
|
||||||
if line1 == line2 and col1 == col2 then return false end
|
|
||||||
local selection = doc():get_text(line1, col1, line2, col2)
|
|
||||||
if text ~= nil and text ~= selection then return false end
|
|
||||||
text = selection
|
|
||||||
end
|
|
||||||
return text ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_in_selection(line, col, l1, c1, l2, c2)
|
|
||||||
if line < l1 or line > l2 then return false end
|
|
||||||
if line == l1 and col <= c1 then return false end
|
|
||||||
if line == l2 and col > c2 then return false end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_in_any_selection(line, col)
|
|
||||||
for idx, l1, c1, l2, c2 in doc():get_selections(true, false) do
|
|
||||||
if is_in_selection(line, col, l1, c1, l2, c2) then return true end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function select_add_next(all)
|
|
||||||
local il1, ic1 = doc():get_selection(true)
|
|
||||||
for idx, l1, c1, l2, c2 in doc():get_selections(true, true) do
|
|
||||||
local text = doc():get_text(l1, c1, l2, c2)
|
local text = doc():get_text(l1, c1, l2, c2)
|
||||||
repeat
|
local l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||||
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
if l2 then doc():set_selection(l2, c2, l1, c1) end
|
||||||
if l1 == il1 and c1 == ic1 then break end
|
|
||||||
if l2 and (all or not is_in_any_selection(l2, c2)) then
|
|
||||||
doc():add_selection(l2, c2, l1, c1)
|
|
||||||
if not all then
|
|
||||||
core.active_view:scroll_to_make_visible(l2, c2)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
until not all or not l2
|
|
||||||
if all then break end
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
local function select_next(reverse)
|
|
||||||
local l1, c1, l2, c2 = doc():get_selection(true)
|
|
||||||
local text = doc():get_text(l1, c1, l2, c2)
|
|
||||||
if reverse then
|
|
||||||
l1, c1, l2, c2 = search.find(doc(), l1, c1, text, { wrap = true, reverse = true })
|
|
||||||
else
|
|
||||||
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
|
||||||
end
|
|
||||||
if l2 then doc():set_selection(l2, c2, l1, c1) end
|
|
||||||
end
|
|
||||||
|
|
||||||
command.add(has_unique_selection, {
|
|
||||||
["find-replace:select-next"] = select_next,
|
|
||||||
["find-replace:select-previous"] = function() select_next(true) end,
|
|
||||||
["find-replace:select-add-next"] = select_add_next,
|
|
||||||
["find-replace:select-add-all"] = function() select_add_next(true) end
|
|
||||||
})
|
})
|
||||||
|
|
||||||
command.add("core.docview!", {
|
command.add("core.docview", {
|
||||||
["find-replace:find"] = function()
|
["find-replace:find"] = function()
|
||||||
find("Find Text", function(doc, line, col, text, case_sensitive, find_regex, find_reverse)
|
find("Find Text", function(doc, line, col, text)
|
||||||
local opt = { wrap = true, no_case = not case_sensitive, regex = find_regex, reverse = find_reverse }
|
local opt = { wrap = true, no_case = true }
|
||||||
return search.find(doc, line, col, text, opt)
|
return search.find(doc, line, col, text, opt)
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["find-replace:replace"] = function()
|
["find-replace:find-pattern"] = function()
|
||||||
local l1, c1, l2, c2 = doc():get_selection()
|
find("Find Text Pattern", function(doc, line, col, text)
|
||||||
local selected_text = doc():get_text(l1, c1, l2, c2)
|
local opt = { wrap = true, no_case = true, pattern = true }
|
||||||
replace("Text", l1 == l2 and selected_text or "", function(text, old, new)
|
return search.find(doc, line, col, text, opt)
|
||||||
if not find_regex then
|
end)
|
||||||
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
end,
|
||||||
|
|
||||||
|
["find-replace:repeat-find"] = function()
|
||||||
|
if not last_fn then
|
||||||
|
core.error("No find to continue from")
|
||||||
|
else
|
||||||
|
local line, col = doc():get_selection()
|
||||||
|
local line1, col1, line2, col2 = last_fn(doc(), line, col, last_text)
|
||||||
|
if line1 then
|
||||||
|
push_previous_find(doc())
|
||||||
|
doc():set_selection(line2, col2, line1, col1)
|
||||||
|
core.active_view:scroll_to_line(line2, true)
|
||||||
end
|
end
|
||||||
local result, matches = regex.gsub(regex.compile(old, "m"), text, new)
|
end
|
||||||
return result, #matches
|
end,
|
||||||
|
|
||||||
|
["find-replace:previous-find"] = function()
|
||||||
|
local sel = table.remove(previous_finds)
|
||||||
|
if not sel or doc() ~= last_doc then
|
||||||
|
core.error("No previous finds")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
doc():set_selection(table.unpack(sel))
|
||||||
|
core.active_view:scroll_to_line(sel[3], true)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["find-replace:replace"] = function()
|
||||||
|
replace("Text", "", function(text, old, new)
|
||||||
|
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["find-replace:replace-pattern"] = function()
|
||||||
|
replace("Pattern", "", function(text, old, new)
|
||||||
|
return text:gsub(old, new)
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -238,53 +168,3 @@ command.add("core.docview!", {
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
local function valid_for_finding()
|
|
||||||
return core.active_view:is(DocView) or core.active_view:is(CommandView)
|
|
||||||
end
|
|
||||||
|
|
||||||
command.add(valid_for_finding, {
|
|
||||||
["find-replace:repeat-find"] = function()
|
|
||||||
if not last_fn then
|
|
||||||
core.error("No find to continue from")
|
|
||||||
else
|
|
||||||
local sl1, sc1, sl2, sc2 = doc():get_selection(true)
|
|
||||||
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc2, last_text, case_sensitive, find_regex, false)
|
|
||||||
if line1 then
|
|
||||||
doc():set_selection(line2, col2, line1, col1)
|
|
||||||
last_view:scroll_to_line(line2, true)
|
|
||||||
else
|
|
||||||
core.error("Couldn't find %q", last_text)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["find-replace:previous-find"] = function()
|
|
||||||
if not last_fn then
|
|
||||||
core.error("No find to continue from")
|
|
||||||
else
|
|
||||||
local sl1, sc1, sl2, sc2 = doc():get_selection(true)
|
|
||||||
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc1, last_text, case_sensitive, find_regex, true)
|
|
||||||
if line1 then
|
|
||||||
doc():set_selection(line2, col2, line1, col1)
|
|
||||||
last_view:scroll_to_line(line2, true)
|
|
||||||
else
|
|
||||||
core.error("Couldn't find %q", last_text)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
command.add("core.commandview", {
|
|
||||||
["find-replace:toggle-sensitivity"] = function()
|
|
||||||
case_sensitive = not case_sensitive
|
|
||||||
core.status_view:show_tooltip(get_find_tooltip())
|
|
||||||
if last_sel then update_preview(last_sel, last_fn, last_text) end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["find-replace:toggle-regex"] = function()
|
|
||||||
find_regex = not find_regex
|
|
||||||
core.status_view:show_tooltip(get_find_tooltip())
|
|
||||||
if last_sel then update_preview(last_sel, last_fn, last_text) end
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
local core = require "core"
|
|
||||||
local command = require "core.command"
|
|
||||||
|
|
||||||
|
|
||||||
command.add(nil, {
|
|
||||||
["log:open-as-doc"] = function()
|
|
||||||
local doc = core.open_doc("logs.txt")
|
|
||||||
core.root_view:open_doc(doc)
|
|
||||||
doc:insert(1, 1, core.get_log())
|
|
||||||
doc.new_file = false
|
|
||||||
doc:clean()
|
|
||||||
end,
|
|
||||||
["log:copy-to-clipboard"] = function()
|
|
||||||
system.set_clipboard(core.get_log())
|
|
||||||
end
|
|
||||||
})
|
|
|
@ -3,47 +3,36 @@ local style = require "core.style"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local config = require "core.config"
|
|
||||||
|
|
||||||
|
|
||||||
local t = {
|
local t = {
|
||||||
["root:close"] = function(node)
|
["root:close"] = function()
|
||||||
|
local node = core.root_view:get_active_node()
|
||||||
node:close_active_view(core.root_view.root_node)
|
node:close_active_view(core.root_view.root_node)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["root:close-or-quit"] = function(node)
|
|
||||||
if node and (not node:is_empty() or not node.is_primary_node) then
|
|
||||||
node:close_active_view(core.root_view.root_node)
|
|
||||||
else
|
|
||||||
core.quit()
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["root:close-all"] = function()
|
["root:close-all"] = function()
|
||||||
core.confirm_close_docs(core.docs, core.root_view.close_all_docviews, core.root_view)
|
core.confirm_close_all(core.root_view.close_all_docviews, core.root_view)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["root:close-all-others"] = function()
|
["root:switch-to-previous-tab"] = function()
|
||||||
local active_doc, docs = core.active_view and core.active_view.doc, {}
|
local node = core.root_view:get_active_node()
|
||||||
for i, v in ipairs(core.docs) do if v ~= active_doc then table.insert(docs, v) end end
|
|
||||||
core.confirm_close_docs(docs, core.root_view.close_all_docviews, core.root_view, true)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["root:switch-to-previous-tab"] = function(node)
|
|
||||||
local idx = node:get_view_idx(core.active_view)
|
local idx = node:get_view_idx(core.active_view)
|
||||||
idx = idx - 1
|
idx = idx - 1
|
||||||
if idx < 1 then idx = #node.views end
|
if idx < 1 then idx = #node.views end
|
||||||
node:set_active_view(node.views[idx])
|
node:set_active_view(node.views[idx])
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["root:switch-to-next-tab"] = function(node)
|
["root:switch-to-next-tab"] = function()
|
||||||
|
local node = core.root_view:get_active_node()
|
||||||
local idx = node:get_view_idx(core.active_view)
|
local idx = node:get_view_idx(core.active_view)
|
||||||
idx = idx + 1
|
idx = idx + 1
|
||||||
if idx > #node.views then idx = 1 end
|
if idx > #node.views then idx = 1 end
|
||||||
node:set_active_view(node.views[idx])
|
node:set_active_view(node.views[idx])
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["root:move-tab-left"] = function(node)
|
["root:move-tab-left"] = function()
|
||||||
|
local node = core.root_view:get_active_node()
|
||||||
local idx = node:get_view_idx(core.active_view)
|
local idx = node:get_view_idx(core.active_view)
|
||||||
if idx > 1 then
|
if idx > 1 then
|
||||||
table.remove(node.views, idx)
|
table.remove(node.views, idx)
|
||||||
|
@ -51,7 +40,8 @@ local t = {
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["root:move-tab-right"] = function(node)
|
["root:move-tab-right"] = function()
|
||||||
|
local node = core.root_view:get_active_node()
|
||||||
local idx = node:get_view_idx(core.active_view)
|
local idx = node:get_view_idx(core.active_view)
|
||||||
if idx < #node.views then
|
if idx < #node.views then
|
||||||
table.remove(node.views, idx)
|
table.remove(node.views, idx)
|
||||||
|
@ -59,22 +49,25 @@ local t = {
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["root:shrink"] = function(node)
|
["root:shrink"] = function()
|
||||||
|
local node = core.root_view:get_active_node()
|
||||||
local parent = node:get_parent_node(core.root_view.root_node)
|
local parent = node:get_parent_node(core.root_view.root_node)
|
||||||
local n = (parent.a == node) and -0.1 or 0.1
|
local n = (parent.a == node) and -0.1 or 0.1
|
||||||
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
|
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["root:grow"] = function(node)
|
["root:grow"] = function()
|
||||||
|
local node = core.root_view:get_active_node()
|
||||||
local parent = node:get_parent_node(core.root_view.root_node)
|
local parent = node:get_parent_node(core.root_view.root_node)
|
||||||
local n = (parent.a == node) and 0.1 or -0.1
|
local n = (parent.a == node) and 0.1 or -0.1
|
||||||
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
|
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
|
||||||
end
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for i = 1, 9 do
|
for i = 1, 9 do
|
||||||
t["root:switch-to-tab-" .. i] = function(node)
|
t["root:switch-to-tab-" .. i] = function()
|
||||||
|
local node = core.root_view:get_active_node()
|
||||||
local view = node.views[i]
|
local view = node.views[i]
|
||||||
if view then
|
if view then
|
||||||
node:set_active_view(view)
|
node:set_active_view(view)
|
||||||
|
@ -84,7 +77,8 @@ end
|
||||||
|
|
||||||
|
|
||||||
for _, dir in ipairs { "left", "right", "up", "down" } do
|
for _, dir in ipairs { "left", "right", "up", "down" } do
|
||||||
t["root:split-" .. dir] = function(node)
|
t["root:split-" .. dir] = function()
|
||||||
|
local node = core.root_view:get_active_node()
|
||||||
local av = node.active_view
|
local av = node.active_view
|
||||||
node:split(dir)
|
node:split(dir)
|
||||||
if av:is(DocView) then
|
if av:is(DocView) then
|
||||||
|
@ -92,7 +86,8 @@ for _, dir in ipairs { "left", "right", "up", "down" } do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
t["root:switch-to-" .. dir] = function(node)
|
t["root:switch-to-" .. dir] = function()
|
||||||
|
local node = core.root_view:get_active_node()
|
||||||
local x, y
|
local x, y
|
||||||
if dir == "left" or dir == "right" then
|
if dir == "left" or dir == "right" then
|
||||||
y = node.position.y + node.size.y / 2
|
y = node.position.y + node.size.y / 2
|
||||||
|
@ -102,8 +97,7 @@ for _, dir in ipairs { "left", "right", "up", "down" } do
|
||||||
y = node.position.y + (dir == "up" and -1 or node.size.y + style.divider_size)
|
y = node.position.y + (dir == "up" and -1 or node.size.y + style.divider_size)
|
||||||
end
|
end
|
||||||
local node = core.root_view.root_node:get_child_overlapping_point(x, y)
|
local node = core.root_view.root_node:get_child_overlapping_point(x, y)
|
||||||
local sx, sy = node:get_locked_size()
|
if not node:get_locked_size() then
|
||||||
if not sx and not sy then
|
|
||||||
core.set_active_view(node.active_view)
|
core.set_active_view(node.active_view)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -111,17 +105,5 @@ end
|
||||||
|
|
||||||
command.add(function()
|
command.add(function()
|
||||||
local node = core.root_view:get_active_node()
|
local node = core.root_view:get_active_node()
|
||||||
local sx, sy = node:get_locked_size()
|
return not node:get_locked_size()
|
||||||
return not sx and not sy, node
|
|
||||||
end, t)
|
end, t)
|
||||||
|
|
||||||
command.add(nil, {
|
|
||||||
["root:scroll"] = function(delta)
|
|
||||||
local view = (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) or core.active_view
|
|
||||||
if view and view.scrollable then
|
|
||||||
view.scroll.to.y = view.scroll.to.y + delta * -config.mouse_wheel_scroll
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
local core = require "core"
|
|
||||||
local command = require "core.command"
|
|
||||||
local common = require "core.common"
|
|
||||||
local style = require "core.style"
|
|
||||||
local StatusView = require "core.statusview"
|
|
||||||
|
|
||||||
local function status_view_item_names()
|
|
||||||
local items = core.status_view:get_items_list()
|
|
||||||
local names = {}
|
|
||||||
for _, item in ipairs(items) do
|
|
||||||
table.insert(names, item.name)
|
|
||||||
end
|
|
||||||
return names
|
|
||||||
end
|
|
||||||
|
|
||||||
local function status_view_items_data(names)
|
|
||||||
local data = {}
|
|
||||||
for _, name in ipairs(names) do
|
|
||||||
local item = core.status_view:get_item(name)
|
|
||||||
table.insert(data, {
|
|
||||||
text = command.prettify_name(item.name),
|
|
||||||
info = item.alignment == StatusView.Item.LEFT and "Left" or "Right",
|
|
||||||
name = item.name
|
|
||||||
})
|
|
||||||
end
|
|
||||||
return data
|
|
||||||
end
|
|
||||||
|
|
||||||
local function status_view_get_items(text)
|
|
||||||
local names = status_view_item_names()
|
|
||||||
local results = common.fuzzy_match(names, text)
|
|
||||||
results = status_view_items_data(results)
|
|
||||||
return results
|
|
||||||
end
|
|
||||||
|
|
||||||
command.add(nil, {
|
|
||||||
["status-bar:toggle"] = function()
|
|
||||||
core.status_view:toggle()
|
|
||||||
end,
|
|
||||||
["status-bar:show"] = function()
|
|
||||||
core.status_view:show()
|
|
||||||
end,
|
|
||||||
["status-bar:hide"] = function()
|
|
||||||
core.status_view:hide()
|
|
||||||
end,
|
|
||||||
["status-bar:disable-messages"] = function()
|
|
||||||
core.status_view:display_messages(false)
|
|
||||||
end,
|
|
||||||
["status-bar:enable-messages"] = function()
|
|
||||||
core.status_view:display_messages(true)
|
|
||||||
end,
|
|
||||||
["status-bar:hide-item"] = function()
|
|
||||||
core.command_view:enter("Status bar item to hide", {
|
|
||||||
submit = function(text, item)
|
|
||||||
core.status_view:hide_items(item.name)
|
|
||||||
end,
|
|
||||||
suggest = status_view_get_items
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
["status-bar:show-item"] = function()
|
|
||||||
core.command_view:enter("Status bar item to show", {
|
|
||||||
submit = function(text, item)
|
|
||||||
core.status_view:show_items(item.name)
|
|
||||||
end,
|
|
||||||
suggest = status_view_get_items
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
["status-bar:reset-items"] = function()
|
|
||||||
core.status_view:show_items()
|
|
||||||
end,
|
|
||||||
})
|
|
|
@ -6,44 +6,24 @@ local DocView = require "core.docview"
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
|
|
||||||
|
|
||||||
---@class core.commandview.input : core.doc
|
|
||||||
---@field super core.doc
|
|
||||||
local SingleLineDoc = Doc:extend()
|
local SingleLineDoc = Doc:extend()
|
||||||
|
|
||||||
function SingleLineDoc:insert(line, col, text)
|
function SingleLineDoc:insert(line, col, text)
|
||||||
SingleLineDoc.super.insert(self, line, col, text:gsub("\n", ""))
|
SingleLineDoc.super.insert(self, line, col, text:gsub("\n", ""))
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class core.commandview : core.docview
|
|
||||||
---@field super core.docview
|
|
||||||
local CommandView = DocView:extend()
|
|
||||||
|
|
||||||
CommandView.context = "application"
|
local CommandView = DocView:extend()
|
||||||
|
|
||||||
local max_suggestions = 10
|
local max_suggestions = 10
|
||||||
|
|
||||||
local noop = function() end
|
local noop = function() end
|
||||||
|
|
||||||
---@class core.commandview.state
|
|
||||||
---@field submit function
|
|
||||||
---@field suggest function
|
|
||||||
---@field cancel function
|
|
||||||
---@field validate function
|
|
||||||
---@field text string
|
|
||||||
---@field select_text boolean
|
|
||||||
---@field show_suggestions boolean
|
|
||||||
---@field typeahead boolean
|
|
||||||
---@field wrap boolean
|
|
||||||
local default_state = {
|
local default_state = {
|
||||||
submit = noop,
|
submit = noop,
|
||||||
suggest = noop,
|
suggest = noop,
|
||||||
cancel = noop,
|
cancel = noop,
|
||||||
validate = function() return true end,
|
validate = function() return true end
|
||||||
text = "",
|
|
||||||
select_text = false,
|
|
||||||
show_suggestions = true,
|
|
||||||
typeahead = true,
|
|
||||||
wrap = true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,7 +33,6 @@ function CommandView:new()
|
||||||
self.suggestions = {}
|
self.suggestions = {}
|
||||||
self.suggestions_height = 0
|
self.suggestions_height = 0
|
||||||
self.last_change_id = 0
|
self.last_change_id = 0
|
||||||
self.last_text = ""
|
|
||||||
self.gutter_width = 0
|
self.gutter_width = 0
|
||||||
self.gutter_text_brightness = 0
|
self.gutter_text_brightness = 0
|
||||||
self.selection_offset = 0
|
self.selection_offset = 0
|
||||||
|
@ -64,20 +43,13 @@ function CommandView:new()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@deprecated
|
|
||||||
function CommandView:set_hidden_suggestions()
|
|
||||||
core.warn("Using deprecated function CommandView:set_hidden_suggestions")
|
|
||||||
self.state.show_suggestions = false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function CommandView:get_name()
|
function CommandView:get_name()
|
||||||
return View.get_name(self)
|
return View.get_name(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function CommandView:get_line_screen_position(line, col)
|
function CommandView:get_line_screen_position()
|
||||||
local x = CommandView.super.get_line_screen_position(self, 1, col)
|
local x = CommandView.super.get_line_screen_position(self, 1)
|
||||||
local _, y = self:get_content_offset()
|
local _, y = self:get_content_offset()
|
||||||
local lh = self:get_line_height()
|
local lh = self:get_line_height()
|
||||||
return x, y + (self.size.y - lh) / 2
|
return x, y + (self.size.y - lh) / 2
|
||||||
|
@ -100,7 +72,6 @@ end
|
||||||
|
|
||||||
|
|
||||||
function CommandView:set_text(text, select)
|
function CommandView:set_text(text, select)
|
||||||
self.last_text = text
|
|
||||||
self.doc:remove(1, 1, math.huge, math.huge)
|
self.doc:remove(1, 1, math.huge, math.huge)
|
||||||
self.doc:text_input(text)
|
self.doc:text_input(text)
|
||||||
if select then
|
if select then
|
||||||
|
@ -110,38 +81,10 @@ end
|
||||||
|
|
||||||
|
|
||||||
function CommandView:move_suggestion_idx(dir)
|
function CommandView:move_suggestion_idx(dir)
|
||||||
local function overflow_suggestion_idx(n, count)
|
local n = self.suggestion_idx + dir
|
||||||
if count == 0 then return 0 end
|
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||||
if self.state.wrap then
|
self:complete()
|
||||||
return (n - 1) % count + 1
|
self.last_change_id = self.doc:get_change_id()
|
||||||
else
|
|
||||||
return common.clamp(n, 1, count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.state.show_suggestions then
|
|
||||||
local n = self.suggestion_idx + dir
|
|
||||||
self.suggestion_idx = overflow_suggestion_idx(n, #self.suggestions)
|
|
||||||
self:complete()
|
|
||||||
self.last_change_id = self.doc:get_change_id()
|
|
||||||
else
|
|
||||||
local current_suggestion = #self.suggestions > 0 and self.suggestions[self.suggestion_idx].text
|
|
||||||
local text = self:get_text()
|
|
||||||
if text == current_suggestion then
|
|
||||||
local n = self.suggestion_idx + dir
|
|
||||||
if n == 0 and self.save_suggestion then
|
|
||||||
self:set_text(self.save_suggestion)
|
|
||||||
else
|
|
||||||
self.suggestion_idx = overflow_suggestion_idx(n, #self.suggestions)
|
|
||||||
self:complete()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.save_suggestion = text
|
|
||||||
self:complete()
|
|
||||||
end
|
|
||||||
self.last_change_id = self.doc:get_change_id()
|
|
||||||
self.state.suggest(self:get_text())
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -162,53 +105,21 @@ function CommandView:submit()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param label string
|
|
||||||
---@varargs any
|
function CommandView:enter(text, submit, suggest, cancel, validate)
|
||||||
---@overload fun(label:string, options: core.commandview.state)
|
|
||||||
function CommandView:enter(label, ...)
|
|
||||||
if self.state ~= default_state then
|
if self.state ~= default_state then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local options = select(1, ...)
|
self.state = {
|
||||||
|
submit = submit or noop,
|
||||||
if type(options) ~= "table" then
|
suggest = suggest or noop,
|
||||||
core.warn("Using CommandView:enter in a deprecated way")
|
cancel = cancel or noop,
|
||||||
local submit, suggest, cancel, validate = ...
|
validate = validate or function() return true end
|
||||||
options = {
|
}
|
||||||
submit = submit,
|
|
||||||
suggest = suggest,
|
|
||||||
cancel = cancel,
|
|
||||||
validate = validate,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Support deprecated CommandView:set_hidden_suggestions
|
|
||||||
-- Remove this when set_hidden_suggestions is not supported anymore
|
|
||||||
if options.show_suggestions == nil then
|
|
||||||
options.show_suggestions = self.state.show_suggestions
|
|
||||||
end
|
|
||||||
|
|
||||||
self.state = common.merge(default_state, options)
|
|
||||||
|
|
||||||
-- We need to keep the text entered with CommandView:set_text to
|
|
||||||
-- maintain compatibility with deprecated usage, but still allow
|
|
||||||
-- overwriting with options.text
|
|
||||||
local old_text = self:get_text()
|
|
||||||
if old_text ~= "" then
|
|
||||||
core.warn("Using deprecated function CommandView:set_text")
|
|
||||||
end
|
|
||||||
if options.text or options.select_text then
|
|
||||||
local text = options.text or old_text
|
|
||||||
self:set_text(text, self.state.select_text)
|
|
||||||
end
|
|
||||||
-- Replace with a simple
|
|
||||||
-- self:set_text(self.state.text, self.state.select_text)
|
|
||||||
-- once old usage is removed
|
|
||||||
|
|
||||||
core.set_active_view(self)
|
core.set_active_view(self)
|
||||||
self:update_suggestions()
|
self:update_suggestions()
|
||||||
self.gutter_text_brightness = 100
|
self.gutter_text_brightness = 100
|
||||||
self.label = label .. ": "
|
self.label = text .. ": "
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -221,13 +132,6 @@ function CommandView:exit(submitted, inexplicit)
|
||||||
self.doc:reset()
|
self.doc:reset()
|
||||||
self.suggestions = {}
|
self.suggestions = {}
|
||||||
if not submitted then cancel(not inexplicit) end
|
if not submitted then cancel(not inexplicit) end
|
||||||
self.save_suggestion = nil
|
|
||||||
self.last_text = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function CommandView:get_line_height()
|
|
||||||
return math.floor(self:get_font():get_height() * 1.2)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -265,45 +169,35 @@ function CommandView:update()
|
||||||
-- update suggestions if text has changed
|
-- update suggestions if text has changed
|
||||||
if self.last_change_id ~= self.doc:get_change_id() then
|
if self.last_change_id ~= self.doc:get_change_id() then
|
||||||
self:update_suggestions()
|
self:update_suggestions()
|
||||||
if self.state.typeahead and self.suggestions[self.suggestion_idx] then
|
|
||||||
local current_text = self:get_text()
|
|
||||||
local suggested_text = self.suggestions[self.suggestion_idx].text or ""
|
|
||||||
if #self.last_text < #current_text and
|
|
||||||
string.find(suggested_text, current_text, 1, true) == 1 then
|
|
||||||
self:set_text(suggested_text)
|
|
||||||
self.doc:set_selection(1, #current_text + 1, 1, math.huge)
|
|
||||||
end
|
|
||||||
self.last_text = current_text
|
|
||||||
end
|
|
||||||
self.last_change_id = self.doc:get_change_id()
|
self.last_change_id = self.doc:get_change_id()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update gutter text color brightness
|
-- update gutter text color brightness
|
||||||
self:move_towards("gutter_text_brightness", 0, 0.1, "commandview")
|
self:move_towards("gutter_text_brightness", 0, 0.1)
|
||||||
|
|
||||||
-- update gutter width
|
-- update gutter width
|
||||||
local dest = self:get_font():get_width(self.label) + style.padding.x
|
local dest = self:get_font():get_width(self.label) + style.padding.x
|
||||||
if self.size.y <= 0 then
|
if self.size.y <= 0 then
|
||||||
self.gutter_width = dest
|
self.gutter_width = dest
|
||||||
else
|
else
|
||||||
self:move_towards("gutter_width", dest, nil, "commandview")
|
self:move_towards("gutter_width", dest)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update suggestions box height
|
-- update suggestions box height
|
||||||
local lh = self:get_suggestion_line_height()
|
local lh = self:get_suggestion_line_height()
|
||||||
local dest = self.state.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0
|
local dest = math.min(#self.suggestions, max_suggestions) * lh
|
||||||
self:move_towards("suggestions_height", dest, nil, "commandview")
|
self:move_towards("suggestions_height", dest)
|
||||||
|
|
||||||
-- update suggestion cursor offset
|
-- update suggestion cursor offset
|
||||||
local dest = math.min(self.suggestion_idx, max_suggestions) * self:get_suggestion_line_height()
|
local dest = math.min(self.suggestion_idx, max_suggestions) * self:get_suggestion_line_height()
|
||||||
self:move_towards("selection_offset", dest, nil, "commandview")
|
self:move_towards("selection_offset", dest)
|
||||||
|
|
||||||
-- update size based on whether this is the active_view
|
-- update size based on whether this is the active_view
|
||||||
local dest = 0
|
local dest = 0
|
||||||
if self == core.active_view then
|
if self == core.active_view then
|
||||||
dest = style.font:get_height() + style.padding.y * 2
|
dest = style.font:get_height() + style.padding.y * 2
|
||||||
end
|
end
|
||||||
self:move_towards(self.size, "y", dest, nil, "commandview")
|
self:move_towards(self.size, "y", dest)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -320,7 +214,6 @@ function CommandView:draw_line_gutter(idx, x, y)
|
||||||
x = x + style.padding.x
|
x = x + style.padding.x
|
||||||
renderer.draw_text(self:get_font(), self.label, x, y + yoffset, color)
|
renderer.draw_text(self:get_font(), self.label, x, y + yoffset, color)
|
||||||
core.pop_clip_rect()
|
core.pop_clip_rect()
|
||||||
return self:get_line_height()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -340,20 +233,20 @@ local function draw_suggestions_box(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- draw suggestion text
|
-- draw suggestion text
|
||||||
local offset = math.max(self.suggestion_idx - max_suggestions, 0)
|
local suggestion_offset = math.max(self.suggestion_idx - max_suggestions, 0)
|
||||||
local last = math.min(offset + max_suggestions, #self.suggestions)
|
|
||||||
core.push_clip_rect(rx, ry, rw, rh)
|
core.push_clip_rect(rx, ry, rw, rh)
|
||||||
local first = 1 + offset
|
local i = 1 + suggestion_offset
|
||||||
for i=first, last do
|
while i <= #self.suggestions do
|
||||||
local item = self.suggestions[i]
|
local item = self.suggestions[i]
|
||||||
local color = (i == self.suggestion_idx) and style.accent or style.text
|
local color = (i == self.suggestion_idx) and style.accent or style.text
|
||||||
local y = self.position.y - (i - offset) * lh - dh
|
local y = self.position.y - (i - suggestion_offset) * lh - dh
|
||||||
common.draw_text(self:get_font(), color, item.text, nil, x, y, 0, lh)
|
common.draw_text(self:get_font(), color, item.text, nil, x, y, 0, lh)
|
||||||
|
|
||||||
if item.info then
|
if item.info then
|
||||||
local w = self.size.x - x - style.padding.x
|
local w = self.size.x - x - style.padding.x
|
||||||
common.draw_text(self:get_font(), style.dim, item.info, "right", x, y, w, lh)
|
common.draw_text(self:get_font(), style.dim, item.info, "right", x, y, w, lh)
|
||||||
end
|
end
|
||||||
|
i = i + 1
|
||||||
end
|
end
|
||||||
core.pop_clip_rect()
|
core.pop_clip_rect()
|
||||||
end
|
end
|
||||||
|
@ -361,9 +254,7 @@ end
|
||||||
|
|
||||||
function CommandView:draw()
|
function CommandView:draw()
|
||||||
CommandView.super.draw(self)
|
CommandView.super.draw(self)
|
||||||
if self.state.show_suggestions then
|
core.root_view:defer_draw(draw_suggestions_box, self)
|
||||||
core.root_view:defer_draw(draw_suggestions_box, self)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
local common = {}
|
local common = {}
|
||||||
|
|
||||||
function common.is_utf8_cont(s, offset)
|
|
||||||
local byte = s:byte(offset or 1)
|
function common.is_utf8_cont(char)
|
||||||
|
local byte = char:byte()
|
||||||
return byte >= 0x80 and byte < 0xc0
|
return byte >= 0x80 and byte < 0xc0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -16,33 +17,11 @@ function common.clamp(n, lo, hi)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.merge(a, b)
|
|
||||||
a = type(a) == "table" and a or {}
|
|
||||||
local t = {}
|
|
||||||
for k, v in pairs(a) do
|
|
||||||
t[k] = v
|
|
||||||
end
|
|
||||||
if b and type(b) == "table" then
|
|
||||||
for k, v in pairs(b) do
|
|
||||||
t[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.round(n)
|
function common.round(n)
|
||||||
return n >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5)
|
return n >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.find_index(tbl, prop)
|
|
||||||
for i, o in ipairs(tbl) do
|
|
||||||
if o[prop] then return i end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -55,51 +34,26 @@ function common.lerp(a, b, t)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.distance(x1, y1, x2, y2)
|
|
||||||
return math.sqrt(((x2-x1) ^ 2)+((y2-y1) ^ 2))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.color(str)
|
function common.color(str)
|
||||||
local r, g, b, a = str:match("^#(%x%x)(%x%x)(%x%x)(%x?%x?)$")
|
local r, g, b, a = str:match("#(%x%x)(%x%x)(%x%x)")
|
||||||
if r then
|
if r then
|
||||||
r = tonumber(r, 16)
|
r = tonumber(r, 16)
|
||||||
g = tonumber(g, 16)
|
g = tonumber(g, 16)
|
||||||
b = tonumber(b, 16)
|
b = tonumber(b, 16)
|
||||||
a = tonumber(a, 16) or 0xff
|
a = 1
|
||||||
elseif str:match("rgba?%s*%([%d%s%.,]+%)") then
|
elseif str:match("rgba?%s*%([%d%s%.,]+%)") then
|
||||||
local f = str:gmatch("[%d.]+")
|
local f = str:gmatch("[%d.]+")
|
||||||
r = (f() or 0)
|
r = (f() or 0)
|
||||||
g = (f() or 0)
|
g = (f() or 0)
|
||||||
b = (f() or 0)
|
b = (f() or 0)
|
||||||
a = (f() or 1) * 0xff
|
a = f() or 1
|
||||||
else
|
else
|
||||||
error(string.format("bad color string '%s'", str))
|
error(string.format("bad color string '%s'", str))
|
||||||
end
|
end
|
||||||
return r, g, b, a
|
return r, g, b, a * 0xff
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.splice(t, at, remove, insert)
|
|
||||||
insert = insert or {}
|
|
||||||
local offset = #insert - remove
|
|
||||||
local old_len = #t
|
|
||||||
if offset < 0 then
|
|
||||||
for i = at - offset, old_len - offset do
|
|
||||||
t[i + offset] = t[i]
|
|
||||||
end
|
|
||||||
elseif offset > 0 then
|
|
||||||
for i = old_len, at, -1 do
|
|
||||||
t[i + offset] = t[i]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for i, item in ipairs(insert) do
|
|
||||||
t[at + i - 1] = item
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local function compare_score(a, b)
|
local function compare_score(a, b)
|
||||||
return a.score > b.score
|
return a.score > b.score
|
||||||
end
|
end
|
||||||
|
@ -146,29 +100,9 @@ function common.fuzzy_match_with_recents(haystack, recents, needle)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.path_suggest(text, root)
|
function common.path_suggest(text)
|
||||||
if root and root:sub(-1) ~= PATHSEP then
|
local path, name = text:match("^(.-)([^/\\]*)$")
|
||||||
root = root .. PATHSEP
|
local files = system.list_dir(path == "" and "." or path) or {}
|
||||||
end
|
|
||||||
local path, name = text:match("^(.-)([^:/\\]*)$")
|
|
||||||
local clean_dotslash = false
|
|
||||||
-- ignore root if path is absolute
|
|
||||||
local is_absolute = common.is_absolute_path(text)
|
|
||||||
if not is_absolute then
|
|
||||||
if path == "" then
|
|
||||||
path = root or "."
|
|
||||||
clean_dotslash = not root
|
|
||||||
else
|
|
||||||
path = (root or "") .. path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Only in Windows allow using both styles of PATHSEP
|
|
||||||
if (PATHSEP == "\\" and not string.match(path:sub(-1), "[\\/]")) or
|
|
||||||
(PATHSEP ~= "\\" and path:sub(-1) ~= PATHSEP) then
|
|
||||||
path = path .. PATHSEP
|
|
||||||
end
|
|
||||||
local files = system.list_dir(path) or {}
|
|
||||||
local res = {}
|
local res = {}
|
||||||
for _, file in ipairs(files) do
|
for _, file in ipairs(files) do
|
||||||
file = path .. file
|
file = path .. file
|
||||||
|
@ -177,19 +111,6 @@ function common.path_suggest(text, root)
|
||||||
if info.type == "dir" then
|
if info.type == "dir" then
|
||||||
file = file .. PATHSEP
|
file = file .. PATHSEP
|
||||||
end
|
end
|
||||||
if root then
|
|
||||||
-- remove root part from file path
|
|
||||||
local s, e = file:find(root, nil, true)
|
|
||||||
if s == 1 then
|
|
||||||
file = file:sub(e + 1)
|
|
||||||
end
|
|
||||||
elseif clean_dotslash then
|
|
||||||
-- remove added dot slash
|
|
||||||
local s, e = file:find("." .. PATHSEP, nil, true)
|
|
||||||
if s == 1 then
|
|
||||||
file = file:sub(e + 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if file:lower():find(text:lower(), nil, true) == 1 then
|
if file:lower():find(text:lower(), nil, true) == 1 then
|
||||||
table.insert(res, file)
|
table.insert(res, file)
|
||||||
end
|
end
|
||||||
|
@ -200,7 +121,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
function common.dir_path_suggest(text)
|
function common.dir_path_suggest(text)
|
||||||
local path, name = text:match("^(.-)([^:/\\]*)$")
|
local path, name = text:match("^(.-)([^/\\]*)$")
|
||||||
local files = system.list_dir(path == "" and "." or path) or {}
|
local files = system.list_dir(path == "" and "." or path) or {}
|
||||||
local res = {}
|
local res = {}
|
||||||
for _, file in ipairs(files) do
|
for _, file in ipairs(files) do
|
||||||
|
@ -215,7 +136,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
function common.dir_list_suggest(text, dir_list)
|
function common.dir_list_suggest(text, dir_list)
|
||||||
local path, name = text:match("^(.-)([^:/\\]*)$")
|
local path, name = text:match("^(.-)([^/\\]*)$")
|
||||||
local res = {}
|
local res = {}
|
||||||
for _, dir_path in ipairs(dir_list) do
|
for _, dir_path in ipairs(dir_list) do
|
||||||
if dir_path:lower():find(text:lower(), nil, true) == 1 then
|
if dir_path:lower():find(text:lower(), nil, true) == 1 then
|
||||||
|
@ -261,58 +182,19 @@ function common.bench(name, fn, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function serialize(val, pretty, indent_str, escape, sort, limit, level)
|
function common.serialize(val)
|
||||||
local space = pretty and " " or ""
|
|
||||||
local indent = pretty and string.rep(indent_str, level) or ""
|
|
||||||
local newline = pretty and "\n" or ""
|
|
||||||
if type(val) == "string" then
|
if type(val) == "string" then
|
||||||
local out = string.format("%q", val)
|
return string.format("%q", val)
|
||||||
if escape then
|
|
||||||
out = string.gsub(out, "\\\n", "\\n")
|
|
||||||
out = string.gsub(out, "\\7", "\\a")
|
|
||||||
out = string.gsub(out, "\\8", "\\b")
|
|
||||||
out = string.gsub(out, "\\9", "\\t")
|
|
||||||
out = string.gsub(out, "\\11", "\\v")
|
|
||||||
out = string.gsub(out, "\\12", "\\f")
|
|
||||||
out = string.gsub(out, "\\13", "\\r")
|
|
||||||
end
|
|
||||||
return out
|
|
||||||
elseif type(val) == "table" then
|
elseif type(val) == "table" then
|
||||||
-- early exit
|
|
||||||
if level >= limit then return tostring(val) end
|
|
||||||
local next_indent = pretty and (indent .. indent_str) or ""
|
|
||||||
local t = {}
|
local t = {}
|
||||||
for k, v in pairs(val) do
|
for k, v in pairs(val) do
|
||||||
table.insert(t,
|
table.insert(t, "[" .. common.serialize(k) .. "]=" .. common.serialize(v))
|
||||||
next_indent .. "[" ..
|
|
||||||
serialize(k, pretty, indent_str, escape, sort, limit, level + 1) ..
|
|
||||||
"]" .. space .. "=" .. space .. serialize(v, pretty, indent_str, escape, sort, limit, level + 1))
|
|
||||||
end
|
end
|
||||||
if #t == 0 then return "{}" end
|
return "{" .. table.concat(t, ",") .. "}"
|
||||||
if sort then table.sort(t) end
|
|
||||||
return "{" .. newline .. table.concat(t, "," .. newline) .. newline .. indent .. "}"
|
|
||||||
end
|
end
|
||||||
return tostring(val)
|
return tostring(val)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Serialize `val` into a parsable string.
|
|
||||||
-- Available options
|
|
||||||
-- * pretty: enable pretty printing
|
|
||||||
-- * indent_str: indent to use (" " by default)
|
|
||||||
-- * escape: use normal escape characters instead of the ones used by string.format("%q", ...)
|
|
||||||
-- * sort: sort the keys inside tables
|
|
||||||
-- * limit: limit how deep to serialize
|
|
||||||
-- * initial_indent: the initial indentation level
|
|
||||||
function common.serialize(val, opts)
|
|
||||||
opts = opts or {}
|
|
||||||
local indent_str = opts.indent_str or " "
|
|
||||||
local initial_indent = opts.initial_indent or 0
|
|
||||||
local indent = opts.pretty and string.rep(indent_str, initial_indent) or ""
|
|
||||||
local limit = (opts.limit or math.huge) + initial_indent
|
|
||||||
return indent .. serialize(val, opts.pretty, indent_str,
|
|
||||||
opts.escape, opts.sort, limit, initial_indent)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.basename(path)
|
function common.basename(path)
|
||||||
-- a path should never end by / or \ except if it is '/' (unix root) or
|
-- a path should never end by / or \ except if it is '/' (unix root) or
|
||||||
|
@ -321,12 +203,6 @@ function common.basename(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- can return nil if there is no directory part in the path
|
|
||||||
function common.dirname(path)
|
|
||||||
return path:match("(.+)[:\\/][^\\/]+$")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.home_encode(text)
|
function common.home_encode(text)
|
||||||
if HOME and string.find(text, HOME, 1, true) == 1 then
|
if HOME and string.find(text, HOME, 1, true) == 1 then
|
||||||
local dir_pos = #HOME + 1
|
local dir_pos = #HOME + 1
|
||||||
|
@ -350,18 +226,22 @@ end
|
||||||
|
|
||||||
|
|
||||||
function common.home_expand(text)
|
function common.home_expand(text)
|
||||||
if text == nil then
|
|
||||||
return HOME
|
|
||||||
end
|
|
||||||
return HOME and text:gsub("^~", HOME) or text
|
return HOME and text:gsub("^~", HOME) or text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function common.normalize_path(filename)
|
||||||
|
if filename and PATHSEP == '\\' then
|
||||||
|
filename = filename:gsub('[/\\]', '\\')
|
||||||
|
local drive, rem = filename:match('^([a-zA-Z])(:.*)')
|
||||||
|
return drive and drive:upper() .. rem or filename
|
||||||
|
end
|
||||||
|
return filename
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
local function split_on_slash(s, sep_pattern)
|
local function split_on_slash(s, sep_pattern)
|
||||||
local t = {}
|
local t = {}
|
||||||
if s:match("^[/\\]") then
|
|
||||||
t[#t + 1] = ""
|
|
||||||
end
|
|
||||||
for fragment in string.gmatch(s, "([^/\\]+)") do
|
for fragment in string.gmatch(s, "([^/\\]+)") do
|
||||||
t[#t + 1] = fragment
|
t[#t + 1] = fragment
|
||||||
end
|
end
|
||||||
|
@ -369,81 +249,12 @@ local function split_on_slash(s, sep_pattern)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- The filename argument given to the function is supposed to
|
|
||||||
-- come from system.absolute_path and as such should be an
|
|
||||||
-- absolute path without . or .. elements.
|
|
||||||
-- This function exists because on Windows the drive letter returned
|
|
||||||
-- by system.absolute_path is sometimes with a lower case and sometimes
|
|
||||||
-- with an upper case so we normalize to upper case.
|
|
||||||
function common.normalize_volume(filename)
|
|
||||||
if not filename then return end
|
|
||||||
if PATHSEP == '\\' then
|
|
||||||
local drive, rem = filename:match('^([a-zA-Z]:\\)(.-)'..PATHSEP..'?$')
|
|
||||||
if drive then
|
|
||||||
return drive:upper() .. rem
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return filename
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.normalize_path(filename)
|
|
||||||
if not filename then return end
|
|
||||||
local volume
|
|
||||||
if PATHSEP == '\\' then
|
|
||||||
filename = filename:gsub('[/\\]', '\\')
|
|
||||||
local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
|
|
||||||
if drive then
|
|
||||||
volume, filename = drive:upper(), rem
|
|
||||||
else
|
|
||||||
drive, rem = filename:match('^(\\\\[^\\]+\\[^\\]+\\)(.*)')
|
|
||||||
if drive then
|
|
||||||
volume, filename = drive, rem
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local relpath = filename:match('^/(.+)')
|
|
||||||
if relpath then
|
|
||||||
volume, filename = "/", relpath
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local parts = split_on_slash(filename, PATHSEP)
|
|
||||||
local accu = {}
|
|
||||||
for _, part in ipairs(parts) do
|
|
||||||
if part == '..' then
|
|
||||||
if #accu > 0 and accu[#accu] ~= ".." then
|
|
||||||
table.remove(accu)
|
|
||||||
elseif volume then
|
|
||||||
error("invalid path " .. volume .. filename)
|
|
||||||
else
|
|
||||||
table.insert(accu, part)
|
|
||||||
end
|
|
||||||
elseif part ~= '.' then
|
|
||||||
table.insert(accu, part)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local npath = table.concat(accu, PATHSEP)
|
|
||||||
return (volume or "") .. (npath == "" and PATHSEP or npath)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.is_absolute_path(path)
|
|
||||||
return path:sub(1, 1) == PATHSEP or path:match("^(%a):\\") or path:match('^(%w*):')
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.path_belongs_to(filename, path)
|
function common.path_belongs_to(filename, path)
|
||||||
return string.find(filename, path .. PATHSEP, 1, true) == 1
|
return filename and string.find(filename, path .. PATHSEP, 1, true) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.relative_path(ref_dir, dir)
|
function common.relative_path(ref_dir, dir)
|
||||||
local drive_pattern = "^(%a):\\"
|
|
||||||
local drive, ref_drive = dir:match(drive_pattern), ref_dir:match(drive_pattern)
|
|
||||||
if drive and ref_drive and drive ~= ref_drive then
|
|
||||||
-- Windows, different drives, system.absolute_path fails for C:\..\D:\
|
|
||||||
return dir
|
|
||||||
end
|
|
||||||
local ref_ls = split_on_slash(ref_dir)
|
local ref_ls = split_on_slash(ref_dir)
|
||||||
local dir_ls = split_on_slash(dir)
|
local dir_ls = split_on_slash(dir)
|
||||||
local i = 1
|
local i = 1
|
||||||
|
@ -462,75 +273,4 @@ function common.relative_path(ref_dir, dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.mkdirp(path)
|
|
||||||
local stat = system.get_file_info(path)
|
|
||||||
if stat and stat.type then
|
|
||||||
return false, "path exists", path
|
|
||||||
end
|
|
||||||
local subdirs = {}
|
|
||||||
while path and path ~= "" do
|
|
||||||
local success_mkdir = system.mkdir(path)
|
|
||||||
if success_mkdir then break end
|
|
||||||
local updir, basedir = path:match("(.*)[/\\](.+)$")
|
|
||||||
table.insert(subdirs, 1, basedir or path)
|
|
||||||
path = updir
|
|
||||||
end
|
|
||||||
for _, dirname in ipairs(subdirs) do
|
|
||||||
path = path and path .. PATHSEP .. dirname or dirname
|
|
||||||
if not system.mkdir(path) then
|
|
||||||
return false, "cannot create directory", path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function common.rm(path, recursively)
|
|
||||||
local stat = system.get_file_info(path)
|
|
||||||
if not stat or (stat.type ~= "file" and stat.type ~= "dir") then
|
|
||||||
return false, "invalid path given", path
|
|
||||||
end
|
|
||||||
|
|
||||||
if stat.type == "file" then
|
|
||||||
local removed, error = os.remove(path)
|
|
||||||
if not removed then
|
|
||||||
return false, error, path
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local contents = system.list_dir(path)
|
|
||||||
if #contents > 0 and not recursively then
|
|
||||||
return false, "directory is not empty", path
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, item in pairs(contents) do
|
|
||||||
local item_path = path .. PATHSEP .. item
|
|
||||||
local item_stat = system.get_file_info(item_path)
|
|
||||||
|
|
||||||
if not item_stat then
|
|
||||||
return false, "invalid file encountered", item_path
|
|
||||||
end
|
|
||||||
|
|
||||||
if item_stat.type == "dir" then
|
|
||||||
local deleted, error, ipath = common.rm(item_path, recursively)
|
|
||||||
if not deleted then
|
|
||||||
return false, error, ipath
|
|
||||||
end
|
|
||||||
elseif item_stat.type == "file" then
|
|
||||||
local removed, error = os.remove(item_path)
|
|
||||||
if not removed then
|
|
||||||
return false, error, item_path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local removed, error = system.rmdir(path)
|
|
||||||
if not removed then
|
|
||||||
return false, error, path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return common
|
return common
|
||||||
|
|
||||||
|
|
|
@ -1,59 +1,33 @@
|
||||||
local config = {}
|
local config = {}
|
||||||
|
|
||||||
|
config.project_scan_rate = 5
|
||||||
config.fps = 60
|
config.fps = 60
|
||||||
config.max_log_items = 800
|
config.max_log_items = 80
|
||||||
config.message_timeout = 5
|
config.message_timeout = 5
|
||||||
config.mouse_wheel_scroll = 50 * SCALE
|
config.mouse_wheel_scroll = 50 * SCALE
|
||||||
config.animate_drag_scroll = false
|
|
||||||
config.scroll_past_end = true
|
|
||||||
config.file_size_limit = 10
|
config.file_size_limit = 10
|
||||||
config.ignore_files = { "^%." }
|
config.scroll_past_end = true
|
||||||
|
config.ignore_files = "^%."
|
||||||
config.symbol_pattern = "[%a_][%w_]*"
|
config.symbol_pattern = "[%a_][%w_]*"
|
||||||
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||||
config.undo_merge_timeout = 0.3
|
config.undo_merge_timeout = 0.3
|
||||||
config.max_undos = 10000
|
config.max_undos = 10000
|
||||||
config.max_tabs = 8
|
config.max_tabs = 10
|
||||||
config.always_show_tabs = true
|
|
||||||
-- Possible values: false, true, "no_selection"
|
|
||||||
config.highlight_current_line = true
|
config.highlight_current_line = true
|
||||||
config.line_height = 1.2
|
config.line_height = 1.2
|
||||||
config.indent_size = 2
|
config.indent_size = 2
|
||||||
config.tab_type = "soft"
|
config.tab_type = "soft"
|
||||||
config.line_limit = 80
|
config.line_limit = 80
|
||||||
|
config.max_symbols = 4000
|
||||||
config.max_project_files = 2000
|
config.max_project_files = 2000
|
||||||
config.transitions = true
|
config.transitions = true
|
||||||
config.disabled_transitions = {
|
|
||||||
scroll = false,
|
|
||||||
commandview = false,
|
|
||||||
contextmenu = false,
|
|
||||||
logview = false,
|
|
||||||
nagbar = false,
|
|
||||||
tabs = false,
|
|
||||||
tab_drag = false,
|
|
||||||
statusbar = false,
|
|
||||||
}
|
|
||||||
config.animation_rate = 1.0
|
config.animation_rate = 1.0
|
||||||
config.blink_period = 0.8
|
config.blink_period = 0.8
|
||||||
config.disable_blink = false
|
|
||||||
config.draw_whitespace = false
|
config.draw_whitespace = false
|
||||||
config.borderless = false
|
config.borderless = false
|
||||||
config.tab_close_button = true
|
|
||||||
config.max_clicks = 3
|
|
||||||
|
|
||||||
-- set as true to be able to test non supported plugins
|
-- Disable plugin loading setting to false the config entry
|
||||||
config.skip_plugins_version = false
|
-- of the same name.
|
||||||
|
config.trimwhitespace = false
|
||||||
config.plugins = {}
|
|
||||||
-- Allow you to set plugin configs even if we haven't seen the plugin before.
|
|
||||||
setmetatable(config.plugins, {
|
|
||||||
__index = function(t, k)
|
|
||||||
if rawget(t, k) == nil then rawset(t, k, {}) end
|
|
||||||
return rawget(t, k)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
-- Disable these plugins by default.
|
|
||||||
config.plugins.trimwhitespace = false
|
|
||||||
config.plugins.drawwhitespace = false
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
|
@ -1,241 +0,0 @@
|
||||||
local core = require "core"
|
|
||||||
local common = require "core.common"
|
|
||||||
local command = require "core.command"
|
|
||||||
local config = require "core.config"
|
|
||||||
local keymap = require "core.keymap"
|
|
||||||
local style = require "core.style"
|
|
||||||
local Object = require "core.object"
|
|
||||||
local View = require "core.view"
|
|
||||||
|
|
||||||
local border_width = 1
|
|
||||||
local divider_width = 1
|
|
||||||
local DIVIDER = {}
|
|
||||||
|
|
||||||
---@class core.contextmenu : core.object
|
|
||||||
local ContextMenu = Object:extend()
|
|
||||||
|
|
||||||
ContextMenu.DIVIDER = DIVIDER
|
|
||||||
|
|
||||||
function ContextMenu:new()
|
|
||||||
self.itemset = {}
|
|
||||||
self.show_context_menu = false
|
|
||||||
self.selected = -1
|
|
||||||
self.height = 0
|
|
||||||
self.position = { x = 0, y = 0 }
|
|
||||||
self.current_scale = SCALE
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_item_size(item)
|
|
||||||
local lw, lh
|
|
||||||
if item == DIVIDER then
|
|
||||||
lw = 0
|
|
||||||
lh = divider_width
|
|
||||||
else
|
|
||||||
lw = style.font:get_width(item.text)
|
|
||||||
if item.info then
|
|
||||||
lw = lw + style.padding.x + style.font:get_width(item.info)
|
|
||||||
end
|
|
||||||
lh = style.font:get_height() + style.padding.y
|
|
||||||
end
|
|
||||||
return lw, lh
|
|
||||||
end
|
|
||||||
|
|
||||||
local function update_items_size(items, update_binding)
|
|
||||||
local width, height = 0, 0
|
|
||||||
for _, item in ipairs(items) do
|
|
||||||
if update_binding and item ~= DIVIDER then
|
|
||||||
item.info = keymap.get_binding(item.command)
|
|
||||||
end
|
|
||||||
local lw, lh = get_item_size(item)
|
|
||||||
width = math.max(width, lw)
|
|
||||||
height = height + lh
|
|
||||||
end
|
|
||||||
width = width + style.padding.x * 2
|
|
||||||
items.width, items.height = width, height
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:register(predicate, items)
|
|
||||||
predicate = command.generate_predicate(predicate)
|
|
||||||
update_items_size(items, true)
|
|
||||||
table.insert(self.itemset, { predicate = predicate, items = items })
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:show(x, y)
|
|
||||||
self.items = nil
|
|
||||||
local items_list = { width = 0, height = 0 }
|
|
||||||
for _, items in ipairs(self.itemset) do
|
|
||||||
if items.predicate(x, y) then
|
|
||||||
items_list.width = math.max(items_list.width, items.items.width)
|
|
||||||
items_list.height = items_list.height
|
|
||||||
for _, subitems in ipairs(items.items) do
|
|
||||||
if not subitems.command or command.is_valid(subitems.command) then
|
|
||||||
local lw, lh = get_item_size(subitems)
|
|
||||||
items_list.height = items_list.height + lh
|
|
||||||
table.insert(items_list, subitems)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if #items_list > 0 then
|
|
||||||
self.items = items_list
|
|
||||||
local w, h = self.items.width, self.items.height
|
|
||||||
|
|
||||||
-- by default the box is opened on the right and below
|
|
||||||
if x + w >= core.root_view.size.x then
|
|
||||||
x = x - w
|
|
||||||
end
|
|
||||||
if y + h >= core.root_view.size.y then
|
|
||||||
y = y - h
|
|
||||||
end
|
|
||||||
|
|
||||||
self.position.x, self.position.y = x, y
|
|
||||||
self.show_context_menu = true
|
|
||||||
core.request_cursor("arrow")
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:hide()
|
|
||||||
self.show_context_menu = false
|
|
||||||
self.items = nil
|
|
||||||
self.selected = -1
|
|
||||||
self.height = 0
|
|
||||||
core.request_cursor(core.active_view.cursor)
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:each_item()
|
|
||||||
local x, y, w = self.position.x, self.position.y, self.items.width
|
|
||||||
local oy = y
|
|
||||||
return coroutine.wrap(function()
|
|
||||||
for i, item in ipairs(self.items) do
|
|
||||||
local _, lh = get_item_size(item)
|
|
||||||
if y - oy > self.height then break end
|
|
||||||
coroutine.yield(i, item, x, y, w, lh)
|
|
||||||
y = y + lh
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:on_mouse_moved(px, py)
|
|
||||||
if not self.show_context_menu then return end
|
|
||||||
|
|
||||||
self.selected = -1
|
|
||||||
for i, item, x, y, w, h in self:each_item() do
|
|
||||||
if px > x and px <= x + w and py > y and py <= y + h then
|
|
||||||
self.selected = i
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:on_selected(item)
|
|
||||||
if type(item.command) == "string" then
|
|
||||||
command.perform(item.command)
|
|
||||||
else
|
|
||||||
item.command()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function change_value(value, change)
|
|
||||||
return value + change
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:focus_previous()
|
|
||||||
self.selected = (self.selected == -1 or self.selected == 1) and #self.items or change_value(self.selected, -1)
|
|
||||||
if self:get_item_selected() == DIVIDER then
|
|
||||||
self.selected = change_value(self.selected, -1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:focus_next()
|
|
||||||
self.selected = (self.selected == -1 or self.selected == #self.items) and 1 or change_value(self.selected, 1)
|
|
||||||
if self:get_item_selected() == DIVIDER then
|
|
||||||
self.selected = change_value(self.selected, 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:get_item_selected()
|
|
||||||
return (self.items or {})[self.selected]
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:call_selected_item()
|
|
||||||
local selected = self:get_item_selected()
|
|
||||||
self:hide()
|
|
||||||
if selected then
|
|
||||||
self:on_selected(selected)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:on_mouse_pressed(button, px, py, clicks)
|
|
||||||
local caught = false
|
|
||||||
|
|
||||||
if self.show_context_menu then
|
|
||||||
if button == "left" then
|
|
||||||
local selected = self:get_item_selected()
|
|
||||||
if selected then
|
|
||||||
self:on_selected(selected)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:hide()
|
|
||||||
caught = true
|
|
||||||
else
|
|
||||||
if button == "right" then
|
|
||||||
caught = self:show(px, py)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return caught
|
|
||||||
end
|
|
||||||
|
|
||||||
ContextMenu.move_towards = View.move_towards
|
|
||||||
|
|
||||||
function ContextMenu:update()
|
|
||||||
if self.show_context_menu then
|
|
||||||
self:move_towards("height", self.items.height, nil, "contextmenu")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:draw()
|
|
||||||
if not self.show_context_menu then return end
|
|
||||||
if self.current_scale ~= SCALE then
|
|
||||||
update_items_size(self.items)
|
|
||||||
for _, set in ipairs(self.itemset) do
|
|
||||||
update_items_size(set.items)
|
|
||||||
end
|
|
||||||
self.current_scale = SCALE
|
|
||||||
end
|
|
||||||
core.root_view:defer_draw(self.draw_context_menu, self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:draw_context_menu()
|
|
||||||
if not self.items then return end
|
|
||||||
local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height
|
|
||||||
|
|
||||||
renderer.draw_rect(
|
|
||||||
bx - border_width,
|
|
||||||
by - border_width,
|
|
||||||
bw + (border_width * 2),
|
|
||||||
bh + (border_width * 2),
|
|
||||||
style.divider
|
|
||||||
)
|
|
||||||
renderer.draw_rect(bx, by, bw, bh, style.background3)
|
|
||||||
|
|
||||||
for i, item, x, y, w, h in self:each_item() do
|
|
||||||
if item == DIVIDER then
|
|
||||||
renderer.draw_rect(x, y, w, h, style.caret)
|
|
||||||
else
|
|
||||||
if i == self.selected then
|
|
||||||
renderer.draw_rect(x, y, w, h, style.selection)
|
|
||||||
end
|
|
||||||
|
|
||||||
common.draw_text(style.font, style.text, item.text, "left", x + style.padding.x, y, w, h)
|
|
||||||
if item.info then
|
|
||||||
common.draw_text(style.font, style.dim, item.info, "right", x, y, w - style.padding.x, h)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return ContextMenu
|
|
|
@ -1,232 +0,0 @@
|
||||||
local common = require "core.common"
|
|
||||||
local config = require "core.config"
|
|
||||||
local dirwatch = {}
|
|
||||||
|
|
||||||
function dirwatch:__index(idx)
|
|
||||||
local value = rawget(self, idx)
|
|
||||||
if value ~= nil then return value end
|
|
||||||
return dirwatch[idx]
|
|
||||||
end
|
|
||||||
|
|
||||||
function dirwatch.new()
|
|
||||||
local t = {
|
|
||||||
scanned = {},
|
|
||||||
watched = {},
|
|
||||||
reverse_watched = {},
|
|
||||||
monitor = dirmonitor.new(),
|
|
||||||
windows_watch_top = nil,
|
|
||||||
windows_watch_count = 0
|
|
||||||
}
|
|
||||||
setmetatable(t, dirwatch)
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function dirwatch:scan(directory, bool)
|
|
||||||
if bool == false then return self:unwatch(directory) end
|
|
||||||
self.scanned[directory] = system.get_file_info(directory).modified
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Should be called on every directory in a subdirectory.
|
|
||||||
-- In windows, this is a no-op for anything underneath a top-level directory,
|
|
||||||
-- but code should be called anyway, so we can ensure that we have a proper
|
|
||||||
-- experience across all platforms. Should be an absolute path.
|
|
||||||
-- Can also be called on individual files, though this should be used sparingly,
|
|
||||||
-- so as not to run into system limits (like in the autoreload plugin).
|
|
||||||
function dirwatch:watch(directory, bool)
|
|
||||||
if bool == false then return self:unwatch(directory) end
|
|
||||||
local info = system.get_file_info(directory)
|
|
||||||
if not info then return end
|
|
||||||
if not self.watched[directory] and not self.scanned[directory] then
|
|
||||||
if PLATFORM == "Windows" then
|
|
||||||
if info.type ~= "dir" then return self:scan(directory) end
|
|
||||||
if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then
|
|
||||||
-- Get the highest level of directory that is common to this directory, and the original.
|
|
||||||
local target = directory
|
|
||||||
while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do
|
|
||||||
target = common.dirname(target)
|
|
||||||
end
|
|
||||||
if target ~= self.windows_watch_top then
|
|
||||||
local value = self.monitor:watch(target)
|
|
||||||
if value and value < 0 then
|
|
||||||
return self:scan(directory)
|
|
||||||
end
|
|
||||||
self.windows_watch_top = target
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.windows_watch_count = self.windows_watch_count + 1
|
|
||||||
self.watched[directory] = true
|
|
||||||
else
|
|
||||||
local value = self.monitor:watch(directory)
|
|
||||||
-- If for whatever reason, we can't watch this directory, revert back to scanning.
|
|
||||||
-- Don't bother trying to find out why, for now.
|
|
||||||
if value and value < 0 then
|
|
||||||
return self:scan(directory)
|
|
||||||
end
|
|
||||||
self.watched[directory] = value
|
|
||||||
self.reverse_watched[value] = directory
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- this should be an absolute path
|
|
||||||
function dirwatch:unwatch(directory)
|
|
||||||
if self.watched[directory] then
|
|
||||||
if PLATFORM ~= "Windows" then
|
|
||||||
self.monitor:unwatch(self.watched[directory])
|
|
||||||
self.reverse_watched[directory] = nil
|
|
||||||
else
|
|
||||||
self.windows_watch_count = self.windows_watch_count - 1
|
|
||||||
if self.windows_watch_count == 0 then
|
|
||||||
self.windows_watch_top = nil
|
|
||||||
self.monitor:unwatch(directory)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.watched[directory] = nil
|
|
||||||
elseif self.scanned[directory] then
|
|
||||||
self.scanned[directory] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- designed to be run inside a coroutine.
|
|
||||||
function dirwatch:check(change_callback, scan_time, wait_time)
|
|
||||||
local had_change = false
|
|
||||||
self.monitor:check(function(id)
|
|
||||||
had_change = true
|
|
||||||
if PLATFORM == "Windows" then
|
|
||||||
change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id))
|
|
||||||
elseif self.reverse_watched[id] then
|
|
||||||
change_callback(self.reverse_watched[id])
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
local start_time = system.get_time()
|
|
||||||
for directory, old_modified in pairs(self.scanned) do
|
|
||||||
if old_modified then
|
|
||||||
local info = system.get_file_info(directory)
|
|
||||||
local new_modified = info and info.modified
|
|
||||||
if old_modified ~= new_modified then
|
|
||||||
change_callback(directory)
|
|
||||||
had_change = true
|
|
||||||
self.scanned[directory] = new_modified
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if system.get_time() - start_time > (scan_time or 0.01) then
|
|
||||||
coroutine.yield(wait_time or 0.01)
|
|
||||||
start_time = system.get_time()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return had_change
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- inspect config.ignore_files patterns and prepare ready to use entries.
|
|
||||||
local function compile_ignore_files()
|
|
||||||
local ipatterns = config.ignore_files
|
|
||||||
local compiled = {}
|
|
||||||
-- config.ignore_files could be a simple string...
|
|
||||||
if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end
|
|
||||||
for i, pattern in ipairs(ipatterns) do
|
|
||||||
-- we ignore malformed pattern that raise an error
|
|
||||||
if pcall(string.match, "a", pattern) then
|
|
||||||
table.insert(compiled, {
|
|
||||||
use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end
|
|
||||||
-- An '/' or '/$' at the end means we want to match a directory.
|
|
||||||
match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value
|
|
||||||
pattern = pattern -- get the actual pattern
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return compiled
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function fileinfo_pass_filter(info, ignore_compiled)
|
|
||||||
if info.size >= config.file_size_limit * 1e6 then return false end
|
|
||||||
local basename = common.basename(info.filename)
|
|
||||||
-- replace '\' with '/' for Windows where PATHSEP = '\'
|
|
||||||
local fullname = "/" .. info.filename:gsub("\\", "/")
|
|
||||||
for _, compiled in ipairs(ignore_compiled) do
|
|
||||||
local test = compiled.use_path and fullname or basename
|
|
||||||
if compiled.match_dir then
|
|
||||||
if info.type == "dir" and string.match(test .. "/", compiled.pattern) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if string.match(test, compiled.pattern) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function compare_file(a, b)
|
|
||||||
return a.filename < b.filename
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- compute a file's info entry completed with "filename" to be used
|
|
||||||
-- in project scan or falsy if it shouldn't appear in the list.
|
|
||||||
local function get_project_file_info(root, file, ignore_compiled)
|
|
||||||
local info = system.get_file_info(root .. PATHSEP .. file)
|
|
||||||
-- info can be not nil but info.type may be nil if is neither a file neither
|
|
||||||
-- a directory, for example for /dev/* entries on linux.
|
|
||||||
if info and info.type then
|
|
||||||
info.filename = file
|
|
||||||
return fileinfo_pass_filter(info, ignore_compiled) and info
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- "root" will by an absolute path without trailing '/'
|
|
||||||
-- "path" will be a path starting without '/' and without trailing '/'
|
|
||||||
-- or the empty string.
|
|
||||||
-- It will identifies a sub-path within "root.
|
|
||||||
-- The current path location will therefore always be: root .. path.
|
|
||||||
-- When recursing "root" will always be the same, only "path" will change.
|
|
||||||
-- Returns a list of file "items". In each item the "filename" will be the
|
|
||||||
-- complete file path relative to "root" *without* the trailing '/', and without the starting '/'.
|
|
||||||
function dirwatch.get_directory_files(dir, root, path, t, entries_count, recurse_pred)
|
|
||||||
local t0 = system.get_time()
|
|
||||||
local t_elapsed = system.get_time() - t0
|
|
||||||
local dirs, files = {}, {}
|
|
||||||
local ignore_compiled = compile_ignore_files()
|
|
||||||
|
|
||||||
|
|
||||||
local all = system.list_dir(root .. PATHSEP .. path)
|
|
||||||
if not all then return nil end
|
|
||||||
|
|
||||||
for _, file in ipairs(all or {}) do
|
|
||||||
local info = get_project_file_info(root, (path ~= "" and (path .. PATHSEP) or "") .. file, ignore_compiled)
|
|
||||||
if info then
|
|
||||||
table.insert(info.type == "dir" and dirs or files, info)
|
|
||||||
entries_count = entries_count + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local recurse_complete = true
|
|
||||||
table.sort(dirs, compare_file)
|
|
||||||
for _, f in ipairs(dirs) do
|
|
||||||
table.insert(t, f)
|
|
||||||
if recurse_pred(dir, f.filename, entries_count, t_elapsed) then
|
|
||||||
local _, complete, n = dirwatch.get_directory_files(dir, root, f.filename, t, entries_count, recurse_pred)
|
|
||||||
recurse_complete = recurse_complete and complete
|
|
||||||
if n ~= nil then
|
|
||||||
entries_count = n
|
|
||||||
end
|
|
||||||
else
|
|
||||||
recurse_complete = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.sort(files, compare_file)
|
|
||||||
for _, f in ipairs(files) do
|
|
||||||
table.insert(t, f)
|
|
||||||
end
|
|
||||||
|
|
||||||
return t, recurse_complete, entries_count
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return dirwatch
|
|
|
@ -1,5 +1,4 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local tokenizer = require "core.tokenizer"
|
local tokenizer = require "core.tokenizer"
|
||||||
local Object = require "core.object"
|
local Object = require "core.object"
|
||||||
|
@ -22,21 +21,13 @@ function Highlighter:new(doc)
|
||||||
else
|
else
|
||||||
local max = math.min(self.first_invalid_line + 40, self.max_wanted_line)
|
local max = math.min(self.first_invalid_line + 40, self.max_wanted_line)
|
||||||
|
|
||||||
local retokenized_from
|
|
||||||
for i = self.first_invalid_line, max do
|
for i = self.first_invalid_line, max do
|
||||||
local state = (i > 1) and self.lines[i - 1].state
|
local state = (i > 1) and self.lines[i - 1].state
|
||||||
local line = self.lines[i]
|
local line = self.lines[i]
|
||||||
if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
|
if not (line and line.init_state == state) then
|
||||||
retokenized_from = retokenized_from or i
|
|
||||||
self.lines[i] = self:tokenize_line(i, state)
|
self.lines[i] = self:tokenize_line(i, state)
|
||||||
elseif retokenized_from then
|
|
||||||
self:update_notify(retokenized_from, i - retokenized_from - 1)
|
|
||||||
retokenized_from = nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if retokenized_from then
|
|
||||||
self:update_notify(retokenized_from, max - retokenized_from)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.first_invalid_line = max + 1
|
self.first_invalid_line = max + 1
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
|
@ -49,40 +40,16 @@ end
|
||||||
|
|
||||||
function Highlighter:reset()
|
function Highlighter:reset()
|
||||||
self.lines = {}
|
self.lines = {}
|
||||||
self:soft_reset()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Highlighter:soft_reset()
|
|
||||||
for i=1,#self.lines do
|
|
||||||
self.lines[i] = false
|
|
||||||
end
|
|
||||||
self.first_invalid_line = 1
|
self.first_invalid_line = 1
|
||||||
self.max_wanted_line = 0
|
self.max_wanted_line = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Highlighter:invalidate(idx)
|
function Highlighter:invalidate(idx)
|
||||||
self.first_invalid_line = math.min(self.first_invalid_line, idx)
|
self.first_invalid_line = math.min(self.first_invalid_line, idx)
|
||||||
self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines)
|
self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Highlighter:insert_notify(line, n)
|
|
||||||
self:invalidate(line)
|
|
||||||
local blanks = { }
|
|
||||||
for i = 1, n do
|
|
||||||
blanks[i] = false
|
|
||||||
end
|
|
||||||
common.splice(self.lines, line, 0, blanks)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Highlighter:remove_notify(line, n)
|
|
||||||
self:invalidate(line)
|
|
||||||
common.splice(self.lines, line, n)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Highlighter:update_notify(line, n)
|
|
||||||
-- plugins can hook here to be notified that lines have been retokenized
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Highlighter:tokenize_line(idx, state)
|
function Highlighter:tokenize_line(idx, state)
|
||||||
local res = {}
|
local res = {}
|
||||||
|
@ -99,7 +66,6 @@ function Highlighter:get_line(idx)
|
||||||
local prev = self.lines[idx - 1]
|
local prev = self.lines[idx - 1]
|
||||||
line = self:tokenize_line(idx, prev and prev.state)
|
line = self:tokenize_line(idx, prev and prev.state)
|
||||||
self.lines[idx] = line
|
self.lines[idx] = line
|
||||||
self:update_notify(idx, 0)
|
|
||||||
end
|
end
|
||||||
self.max_wanted_line = math.max(self.max_wanted_line, idx)
|
self.max_wanted_line = math.max(self.max_wanted_line, idx)
|
||||||
return line
|
return line
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
local Object = require "core.object"
|
local Object = require "core.object"
|
||||||
local Highlighter = require "core.doc.highlighter"
|
local Highlighter = require "core.doc.highlighter"
|
||||||
local core = require "core"
|
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
|
|
||||||
---@class core.doc : core.object
|
|
||||||
local Doc = Object:extend()
|
local Doc = Object:extend()
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,21 +17,36 @@ local function split_lines(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:new(filename, abs_filename, new_file)
|
local function splice(t, at, remove, insert)
|
||||||
self.new_file = new_file
|
insert = insert or {}
|
||||||
|
local offset = #insert - remove
|
||||||
|
local old_len = #t
|
||||||
|
if offset < 0 then
|
||||||
|
for i = at - offset, old_len - offset do
|
||||||
|
t[i + offset] = t[i]
|
||||||
|
end
|
||||||
|
elseif offset > 0 then
|
||||||
|
for i = old_len, at, -1 do
|
||||||
|
t[i + offset] = t[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for i, item in ipairs(insert) do
|
||||||
|
t[at + i - 1] = item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Doc:new(filename)
|
||||||
self:reset()
|
self:reset()
|
||||||
if filename then
|
if filename then
|
||||||
self:set_filename(filename, abs_filename)
|
self:load(filename)
|
||||||
if not new_file then
|
|
||||||
self:load(filename)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:reset()
|
function Doc:reset()
|
||||||
self.lines = { "\n" }
|
self.lines = { "\n" }
|
||||||
self.selections = { 1, 1, 1, 1 }
|
self.selection = { a = { line=1, col=1 }, b = { line=1, col=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
|
||||||
|
@ -46,31 +60,28 @@ function Doc:reset_syntax()
|
||||||
local syn = syntax.get(self.filename or "", header)
|
local syn = syntax.get(self.filename or "", header)
|
||||||
if self.syntax ~= syn then
|
if self.syntax ~= syn then
|
||||||
self.syntax = syn
|
self.syntax = syn
|
||||||
self.highlighter:soft_reset()
|
self.highlighter:reset()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:set_filename(filename, abs_filename)
|
function Doc:set_filename(filename)
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.abs_filename = abs_filename
|
self.abs_filename = system.absolute_path(filename)
|
||||||
self:reset_syntax()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:load(filename)
|
function Doc:load(filename)
|
||||||
local fp = assert( io.open(filename, "rb") )
|
local fp = assert( io.open(filename, "rb") )
|
||||||
self:reset()
|
self:reset()
|
||||||
|
self:set_filename(filename)
|
||||||
self.lines = {}
|
self.lines = {}
|
||||||
local i = 1
|
|
||||||
for line in fp:lines() do
|
for line in fp:lines() do
|
||||||
if line:byte(-1) == 13 then
|
if line:byte(-1) == 13 then
|
||||||
line = line:sub(1, -2)
|
line = line:sub(1, -2)
|
||||||
self.crlf = true
|
self.crlf = true
|
||||||
end
|
end
|
||||||
table.insert(self.lines, line .. "\n")
|
table.insert(self.lines, line .. "\n")
|
||||||
self.highlighter.lines[i] = false
|
|
||||||
i = i + 1
|
|
||||||
end
|
end
|
||||||
if #self.lines == 0 then
|
if #self.lines == 0 then
|
||||||
table.insert(self.lines, "\n")
|
table.insert(self.lines, "\n")
|
||||||
|
@ -80,32 +91,18 @@ function Doc:load(filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:reload()
|
function Doc:save(filename)
|
||||||
if self.filename then
|
filename = filename or assert(self.filename, "no filename set to default to")
|
||||||
local sel = { self:get_selection() }
|
|
||||||
self:load(self.filename)
|
|
||||||
self:clean()
|
|
||||||
self:set_selection(table.unpack(sel))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Doc:save(filename, abs_filename)
|
|
||||||
if not filename then
|
|
||||||
assert(self.filename, "no filename set to default to")
|
|
||||||
filename = self.filename
|
|
||||||
abs_filename = self.abs_filename
|
|
||||||
else
|
|
||||||
assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path")
|
|
||||||
end
|
|
||||||
local fp = assert( io.open(filename, "wb") )
|
local fp = assert( io.open(filename, "wb") )
|
||||||
for _, line in ipairs(self.lines) do
|
for _, line in ipairs(self.lines) do
|
||||||
if self.crlf then line = line:gsub("\n", "\r\n") end
|
if self.crlf then line = line:gsub("\n", "\r\n") end
|
||||||
fp:write(line)
|
fp:write(line)
|
||||||
end
|
end
|
||||||
fp:close()
|
fp:close()
|
||||||
self:set_filename(filename, abs_filename)
|
if filename then
|
||||||
self.new_file = false
|
self:set_filename(filename)
|
||||||
|
end
|
||||||
|
self:reset_syntax()
|
||||||
self:clean()
|
self:clean()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -116,11 +113,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
function Doc:is_dirty()
|
function Doc:is_dirty()
|
||||||
if self.new_file then
|
return self.clean_change_id ~= self:get_change_id()
|
||||||
return #self.lines > 1 or #self.lines[1] > 1
|
|
||||||
else
|
|
||||||
return self.clean_change_id ~= self:get_change_id()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,125 +122,49 @@ function Doc:clean()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:get_indent_info()
|
|
||||||
if not self.indent_info then return config.tab_type, config.indent_size, false end
|
|
||||||
return self.indent_info.type or config.tab_type,
|
|
||||||
self.indent_info.size or config.indent_size,
|
|
||||||
self.indent_info.confirmed
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Doc:get_change_id()
|
function Doc:get_change_id()
|
||||||
return self.undo_stack.idx
|
return self.undo_stack.idx
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Cursor section. Cursor indices are *only* valid during a get_selections() call.
|
|
||||||
-- Cursors will always be iterated in order from top to bottom. Through normal operation
|
function Doc:set_selection(line1, col1, line2, col2, swap)
|
||||||
-- curors can never swap positions; only merge or split, or change their position in cursor
|
assert(not line2 == not col2, "expected 2 or 4 arguments")
|
||||||
-- order.
|
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
|
||||||
function Doc:get_selection(sort)
|
line1, col1 = self:sanitize_position(line1, col1)
|
||||||
local idx, line1, col1, line2, col2, swap = self:get_selections(sort)({ self.selections, sort }, 0)
|
line2, col2 = self:sanitize_position(line2 or line1, col2 or col1)
|
||||||
return line1, col1, line2, col2, swap
|
self.selection.a.line, self.selection.a.col = line1, col1
|
||||||
|
self.selection.b.line, self.selection.b.col = line2, col2
|
||||||
end
|
end
|
||||||
|
|
||||||
function Doc:get_selection_text(limit)
|
|
||||||
limit = limit or math.huge
|
|
||||||
local result = {}
|
|
||||||
for idx, line1, col1, line2, col2 in self:get_selections() do
|
|
||||||
if idx > limit then break end
|
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
|
||||||
local text = self:get_text(line1, col1, line2, col2)
|
|
||||||
if text ~= "" then result[#result + 1] = text end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return table.concat(result, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
function Doc:has_selection()
|
|
||||||
local line1, col1, line2, col2 = self:get_selection(false)
|
|
||||||
return line1 ~= line2 or col1 ~= col2
|
|
||||||
end
|
|
||||||
|
|
||||||
function Doc:has_any_selection()
|
|
||||||
for idx, line1, col1, line2, col2 in self:get_selections() do
|
|
||||||
if line1 ~= line2 or col1 ~= col2 then return true end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function Doc:sanitize_selection()
|
|
||||||
for idx, line1, col1, line2, col2 in self:get_selections() do
|
|
||||||
self:set_selections(idx, line1, col1, line2, col2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function sort_positions(line1, col1, line2, col2)
|
local function sort_positions(line1, col1, line2, col2)
|
||||||
if line1 > line2 or line1 == line2 and col1 > col2 then
|
if line1 > line2
|
||||||
|
or line1 == line2 and col1 > col2 then
|
||||||
return line2, col2, line1, col1, true
|
return line2, col2, line1, col1, true
|
||||||
end
|
end
|
||||||
return line1, col1, line2, col2, false
|
return line1, col1, line2, col2, false
|
||||||
end
|
end
|
||||||
|
|
||||||
function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm)
|
|
||||||
assert(not line2 == not col2, "expected 3 or 5 arguments")
|
|
||||||
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
|
|
||||||
line1, col1 = self:sanitize_position(line1, col1)
|
|
||||||
line2, col2 = self:sanitize_position(line2 or line1, col2 or col1)
|
|
||||||
common.splice(self.selections, (idx - 1)*4 + 1, rm == nil and 4 or rm, { line1, col1, line2, col2 })
|
|
||||||
end
|
|
||||||
|
|
||||||
function Doc:add_selection(line1, col1, line2, col2, swap)
|
function Doc:get_selection(sort)
|
||||||
local l1, c1 = sort_positions(line1, col1, line2 or line1, col2 or col1)
|
local a, b = self.selection.a, self.selection.b
|
||||||
local target = #self.selections / 4 + 1
|
if sort then
|
||||||
for idx, tl1, tc1 in self:get_selections(true) do
|
return sort_positions(a.line, a.col, b.line, b.col)
|
||||||
if l1 < tl1 or l1 == tl1 and c1 < tc1 then
|
|
||||||
target = idx
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
self:set_selections(target, line1, col1, line2, col2, swap, 0)
|
return a.line, a.col, b.line, b.col
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:remove_selection(idx)
|
function Doc:has_selection()
|
||||||
common.splice(self.selections, (idx - 1) * 4 + 1, 4)
|
local a, b = self.selection.a, self.selection.b
|
||||||
|
return not (a.line == b.line and a.col == b.col)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:set_selection(line1, col1, line2, col2, swap)
|
function Doc:sanitize_selection()
|
||||||
self.selections = {}
|
self:set_selection(self:get_selection())
|
||||||
self:set_selections(1, line1, col1, line2, col2, swap)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Doc:merge_cursors(idx)
|
|
||||||
for i = (idx or (#self.selections - 3)), (idx or 5), -4 do
|
|
||||||
for j = 1, i - 4, 4 do
|
|
||||||
if self.selections[i] == self.selections[j] and
|
|
||||||
self.selections[i+1] == self.selections[j+1] then
|
|
||||||
common.splice(self.selections, i, 4)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function selection_iterator(invariant, idx)
|
|
||||||
local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1)
|
|
||||||
if target > #invariant[1] or target <= 0 or (type(invariant[3]) == "number" and invariant[3] ~= idx - 1) then return end
|
|
||||||
if invariant[2] then
|
|
||||||
return idx+(invariant[3] and -1 or 1), sort_positions(table.unpack(invariant[1], target, target+4))
|
|
||||||
else
|
|
||||||
return idx+(invariant[3] and -1 or 1), table.unpack(invariant[1], target, target+4)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If idx_reverse is true, it'll reverse iterate. If nil, or false, regular iterate.
|
|
||||||
-- If a number, runs for exactly that iteration.
|
|
||||||
function Doc:get_selections(sort_intra, idx_reverse)
|
|
||||||
return selection_iterator, { self.selections, sort_intra, idx_reverse },
|
|
||||||
idx_reverse == true and ((#self.selections / 4) + 1) or ((idx_reverse or -1)+1)
|
|
||||||
end
|
|
||||||
-- End of cursor seciton.
|
|
||||||
|
|
||||||
function Doc:sanitize_position(line, col)
|
function Doc:sanitize_position(line, col)
|
||||||
line = common.clamp(line, 1, #self.lines)
|
line = common.clamp(line, 1, #self.lines)
|
||||||
|
@ -334,12 +251,14 @@ local function pop_undo(self, undo_stack, redo_stack, modified)
|
||||||
if cmd.type == "insert" then
|
if cmd.type == "insert" then
|
||||||
local line, col, text = table.unpack(cmd)
|
local line, col, text = table.unpack(cmd)
|
||||||
self:raw_insert(line, col, text, redo_stack, cmd.time)
|
self:raw_insert(line, col, text, redo_stack, cmd.time)
|
||||||
|
|
||||||
elseif cmd.type == "remove" then
|
elseif cmd.type == "remove" then
|
||||||
local line1, col1, line2, col2 = table.unpack(cmd)
|
local line1, col1, line2, col2 = table.unpack(cmd)
|
||||||
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
|
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
|
||||||
|
|
||||||
elseif cmd.type == "selection" then
|
elseif cmd.type == "selection" then
|
||||||
self.selections = { table.unpack(cmd) }
|
self.selection.a.line, self.selection.a.col = cmd[1], cmd[2]
|
||||||
self:sanitize_selection()
|
self.selection.b.line, self.selection.b.col = cmd[3], cmd[4]
|
||||||
end
|
end
|
||||||
|
|
||||||
modified = modified or (cmd.type ~= "selection")
|
modified = modified or (cmd.type ~= "selection")
|
||||||
|
@ -360,7 +279,6 @@ end
|
||||||
function Doc:raw_insert(line, col, text, undo_stack, time)
|
function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||||
-- split text into lines and merge with line at insertion point
|
-- split text into lines and merge with line at insertion point
|
||||||
local lines = split_lines(text)
|
local lines = split_lines(text)
|
||||||
local len = #lines[#lines]
|
|
||||||
local before = self.lines[line]:sub(1, col - 1)
|
local before = self.lines[line]:sub(1, col - 1)
|
||||||
local after = self.lines[line]:sub(col)
|
local after = self.lines[line]:sub(col)
|
||||||
for i = 1, #lines - 1 do
|
for i = 1, #lines - 1 do
|
||||||
|
@ -370,23 +288,15 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||||
lines[#lines] = lines[#lines] .. after
|
lines[#lines] = lines[#lines] .. after
|
||||||
|
|
||||||
-- splice lines into line array
|
-- splice lines into line array
|
||||||
common.splice(self.lines, line, 1, lines)
|
splice(self.lines, line, 1, lines)
|
||||||
|
|
||||||
-- keep cursors where they should be
|
|
||||||
for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
|
|
||||||
if cline1 < line then break end
|
|
||||||
local line_addition = (line < cline1 or col < ccol1) and #lines - 1 or 0
|
|
||||||
local column_addition = line == cline1 and ccol1 > col and len or 0
|
|
||||||
self:set_selections(idx, cline1 + line_addition, ccol1 + column_addition, cline2 + line_addition, ccol2 + column_addition)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- push undo
|
-- push undo
|
||||||
local line2, col2 = self:position_offset(line, col, #text)
|
local line2, col2 = self:position_offset(line, col, #text)
|
||||||
push_undo(undo_stack, time, "selection", table.unpack(self.selections))
|
push_undo(undo_stack, time, "selection", self:get_selection())
|
||||||
push_undo(undo_stack, time, "remove", line, col, line2, col2)
|
push_undo(undo_stack, time, "remove", line, col, line2, col2)
|
||||||
|
|
||||||
-- update highlighter and assure selection is in bounds
|
-- update highlighter and assure selection is in bounds
|
||||||
self.highlighter:insert_notify(line, #lines - 1)
|
self.highlighter:invalidate(line)
|
||||||
self:sanitize_selection()
|
self:sanitize_selection()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -394,7 +304,7 @@ end
|
||||||
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||||
-- push undo
|
-- push undo
|
||||||
local text = self:get_text(line1, col1, line2, col2)
|
local text = self:get_text(line1, col1, line2, col2)
|
||||||
push_undo(undo_stack, time, "selection", table.unpack(self.selections))
|
push_undo(undo_stack, time, "selection", self:get_selection())
|
||||||
push_undo(undo_stack, time, "insert", line1, col1, text)
|
push_undo(undo_stack, time, "insert", line1, col1, text)
|
||||||
|
|
||||||
-- get line content before/after removed text
|
-- get line content before/after removed text
|
||||||
|
@ -402,18 +312,10 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||||
local after = self.lines[line2]:sub(col2)
|
local after = self.lines[line2]:sub(col2)
|
||||||
|
|
||||||
-- splice line into line array
|
-- splice line into line array
|
||||||
common.splice(self.lines, line1, line2 - line1 + 1, { before .. after })
|
splice(self.lines, line1, line2 - line1 + 1, { before .. after })
|
||||||
|
|
||||||
-- move all cursors back if they share a line with the removed text
|
|
||||||
for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
|
|
||||||
if cline1 < line2 then break end
|
|
||||||
local line_removal = line2 - line1
|
|
||||||
local column_removal = line2 == cline2 and col2 < ccol1 and (line2 == line1 and col2 - col1 or col2) or 0
|
|
||||||
self:set_selections(idx, cline1 - line_removal, ccol1 - column_removal, cline2 - line_removal, ccol2 - column_removal)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- update highlighter and assure selection is in bounds
|
-- update highlighter and assure selection is in bounds
|
||||||
self.highlighter:remove_notify(line1, line2 - line1)
|
self.highlighter:invalidate(line1)
|
||||||
self:sanitize_selection()
|
self:sanitize_selection()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -446,149 +348,66 @@ function Doc:redo()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:text_input(text, idx)
|
function Doc:text_input(text)
|
||||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do
|
if self:has_selection() then
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
self:delete_to()
|
||||||
self:delete_to_cursor(sidx)
|
|
||||||
end
|
|
||||||
self:insert(line1, col1, text)
|
|
||||||
self:move_to_cursor(sidx, #text)
|
|
||||||
end
|
end
|
||||||
|
local line, col = self:get_selection()
|
||||||
|
self:insert(line, col, text)
|
||||||
|
self:move_to(#text)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
|
|
||||||
|
function Doc:replace(fn)
|
||||||
|
local line1, col1, line2, col2, swap
|
||||||
|
local had_selection = self:has_selection()
|
||||||
|
if had_selection then
|
||||||
|
line1, col1, line2, col2, swap = self:get_selection(true)
|
||||||
|
else
|
||||||
|
line1, col1, line2, col2 = 1, 1, #self.lines, #self.lines[#self.lines]
|
||||||
|
end
|
||||||
local old_text = self:get_text(line1, col1, line2, col2)
|
local old_text = self:get_text(line1, col1, line2, col2)
|
||||||
local new_text, res = fn(old_text)
|
local new_text, n = fn(old_text)
|
||||||
if old_text ~= new_text then
|
if old_text ~= new_text then
|
||||||
self:insert(line2, col2, new_text)
|
self:insert(line2, col2, new_text)
|
||||||
self:remove(line1, col1, line2, col2)
|
self:remove(line1, col1, line2, col2)
|
||||||
if line1 == line2 and col1 == col2 then
|
if had_selection then
|
||||||
line2, col2 = self:position_offset(line1, col1, #new_text)
|
line2, col2 = self:position_offset(line1, col1, #new_text)
|
||||||
self:set_selections(idx, line1, col1, line2, col2)
|
self:set_selection(line1, col1, line2, col2, swap)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return res
|
return n
|
||||||
end
|
|
||||||
|
|
||||||
function Doc:replace(fn)
|
|
||||||
local has_selection, results = false, { }
|
|
||||||
for idx, line1, col1, line2, col2 in self:get_selections(true) do
|
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
|
||||||
results[idx] = self:replace_cursor(idx, line1, col1, line2, col2, fn)
|
|
||||||
has_selection = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not has_selection then
|
|
||||||
self:set_selection(table.unpack(self.selections))
|
|
||||||
results[1] = self:replace_cursor(1, 1, 1, #self.lines, #self.lines[#self.lines], fn)
|
|
||||||
end
|
|
||||||
return results
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:delete_to_cursor(idx, ...)
|
function Doc:delete_to(...)
|
||||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
|
local line, col = self:get_selection(true)
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
if self:has_selection() then
|
||||||
self:remove(line1, col1, line2, col2)
|
self:remove(self:get_selection())
|
||||||
else
|
|
||||||
local l2, c2 = self:position_offset(line1, col1, ...)
|
|
||||||
self:remove(line1, col1, l2, c2)
|
|
||||||
line1, col1 = sort_positions(line1, col1, l2, c2)
|
|
||||||
end
|
|
||||||
self:set_selections(sidx, line1, col1)
|
|
||||||
end
|
|
||||||
self:merge_cursors(idx)
|
|
||||||
end
|
|
||||||
function Doc:delete_to(...) return self:delete_to_cursor(nil, ...) end
|
|
||||||
|
|
||||||
function Doc:move_to_cursor(idx, ...)
|
|
||||||
for sidx, line, col in self:get_selections(false, idx) do
|
|
||||||
self:set_selections(sidx, self:position_offset(line, col, ...))
|
|
||||||
end
|
|
||||||
self:merge_cursors(idx)
|
|
||||||
end
|
|
||||||
function Doc:move_to(...) return self:move_to_cursor(nil, ...) end
|
|
||||||
|
|
||||||
|
|
||||||
function Doc:select_to_cursor(idx, ...)
|
|
||||||
for sidx, line, col, line2, col2 in self:get_selections(false, idx) do
|
|
||||||
line, col = self:position_offset(line, col, ...)
|
|
||||||
self:set_selections(sidx, line, col, line2, col2)
|
|
||||||
end
|
|
||||||
self:merge_cursors(idx)
|
|
||||||
end
|
|
||||||
function Doc:select_to(...) return self:select_to_cursor(nil, ...) end
|
|
||||||
|
|
||||||
|
|
||||||
function Doc:get_indent_string()
|
|
||||||
local indent_type, indent_size = self:get_indent_info()
|
|
||||||
if indent_type == "hard" then
|
|
||||||
return "\t"
|
|
||||||
end
|
|
||||||
return string.rep(" ", indent_size)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- returns the size of the original indent, and the indent
|
|
||||||
-- in your config format, rounded either up or down
|
|
||||||
function Doc:get_line_indent(line, rnd_up)
|
|
||||||
local _, e = line:find("^[ \t]+")
|
|
||||||
local indent_type, indent_size = self:get_indent_info()
|
|
||||||
local soft_tab = string.rep(" ", indent_size)
|
|
||||||
if indent_type == "hard" then
|
|
||||||
local indent = e and line:sub(1, e):gsub(soft_tab, "\t") or ""
|
|
||||||
return e, indent:gsub(" +", rnd_up and "\t" or "")
|
|
||||||
else
|
else
|
||||||
local indent = e and line:sub(1, e):gsub("\t", soft_tab) or ""
|
local line2, col2 = self:position_offset(line, col, ...)
|
||||||
local number = #indent / #soft_tab
|
self:remove(line, col, line2, col2)
|
||||||
return e, indent:sub(1,
|
line, col = sort_positions(line, col, line2, col2)
|
||||||
(rnd_up and math.ceil(number) or math.floor(number))*#soft_tab)
|
|
||||||
end
|
end
|
||||||
|
self:set_selection(line, col)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- un/indents text; behaviour varies based on selection and un/indent.
|
|
||||||
-- * if there's a selection, it will stay static around the
|
function Doc:move_to(...)
|
||||||
-- text for both indenting and unindenting.
|
local line, col = self:get_selection()
|
||||||
-- * if you are in the beginning whitespace of a line, and are indenting, the
|
self:set_selection(self:position_offset(line, col, ...))
|
||||||
-- cursor will insert the exactly appropriate amount of spaces, and jump the
|
end
|
||||||
-- cursor to the beginning of first non whitespace characters
|
|
||||||
-- * if you are not in the beginning whitespace of a line, and you indent, it
|
|
||||||
-- inserts the appropriate whitespace, as if you typed them normally.
|
function Doc:select_to(...)
|
||||||
-- * if you are unindenting, the cursor will jump to the start of the line,
|
local line, col, line2, col2 = self:get_selection()
|
||||||
-- and remove the appropriate amount of spaces (or a tab).
|
line, col = self:position_offset(line, col, ...)
|
||||||
function Doc:indent_text(unindent, line1, col1, line2, col2)
|
self:set_selection(line, col, line2, col2)
|
||||||
local text = self:get_indent_string()
|
|
||||||
local _, se = self.lines[line1]:find("^[ \t]+")
|
|
||||||
local in_beginning_whitespace = col1 == 1 or (se and col1 <= se + 1)
|
|
||||||
local has_selection = line1 ~= line2 or col1 ~= col2
|
|
||||||
if unindent or has_selection or in_beginning_whitespace then
|
|
||||||
local l1d, l2d = #self.lines[line1], #self.lines[line2]
|
|
||||||
for line = line1, line2 do
|
|
||||||
if not has_selection or #self.lines[line] > 1 then -- don't indent empty lines in a selection
|
|
||||||
local e, rnded = self:get_line_indent(self.lines[line], unindent)
|
|
||||||
self:remove(line, 1, line, (e or 0) + 1)
|
|
||||||
self:insert(line, 1,
|
|
||||||
unindent and rnded:sub(1, #rnded - #text) or rnded .. text)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
l1d, l2d = #self.lines[line1] - l1d, #self.lines[line2] - l2d
|
|
||||||
if (unindent or in_beginning_whitespace) and not has_selection then
|
|
||||||
local start_cursor = (se and se + 1 or 1) + l1d or #(self.lines[line1])
|
|
||||||
return line1, start_cursor, line2, start_cursor
|
|
||||||
end
|
|
||||||
return line1, col1 + l1d, line2, col2 + l2d
|
|
||||||
end
|
|
||||||
self:insert(line1, col1, text)
|
|
||||||
return line1, col1 + #text, line1, col1 + #text
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- For plugins to add custom actions of document change
|
-- For plugins to add custom actions of document change
|
||||||
function Doc:on_text_change(type)
|
function Doc:on_text_change(type)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- For plugins to get notified when a document is closed
|
|
||||||
function Doc:on_close()
|
|
||||||
core.log_quiet("Closed doc \"%s\"", self:get_name())
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return Doc
|
return Doc
|
||||||
|
|
|
@ -15,69 +15,36 @@ local function init_args(doc, line, col, text, opt)
|
||||||
opt = opt or default_opt
|
opt = opt or default_opt
|
||||||
line, col = doc:sanitize_position(line, col)
|
line, col = doc:sanitize_position(line, col)
|
||||||
|
|
||||||
if opt.no_case and not opt.regex then
|
if opt.no_case then
|
||||||
text = text:lower()
|
if opt.pattern then
|
||||||
|
text = text:gsub("%%?.", pattern_lower)
|
||||||
|
else
|
||||||
|
text = text:lower()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return doc, line, col, text, opt
|
return doc, line, col, text, opt
|
||||||
end
|
end
|
||||||
|
|
||||||
-- This function is needed to uniform the behavior of
|
|
||||||
-- `regex:cmatch` and `string.find`.
|
|
||||||
local function regex_func(text, re, index, _)
|
|
||||||
local s, e = re:cmatch(text, index)
|
|
||||||
return s, e and e - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local function rfind(func, text, pattern, index, plain)
|
|
||||||
local s, e = func(text, pattern, 1, plain)
|
|
||||||
local last_s, last_e
|
|
||||||
if index < 0 then index = #text - index + 1 end
|
|
||||||
while e and e <= index do
|
|
||||||
last_s, last_e = s, e
|
|
||||||
s, e = func(text, pattern, s + 1, plain)
|
|
||||||
end
|
|
||||||
return last_s, last_e
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function search.find(doc, line, col, text, opt)
|
function search.find(doc, line, col, text, opt)
|
||||||
doc, line, col, text, opt = init_args(doc, line, col, text, opt)
|
doc, line, col, text, opt = init_args(doc, line, col, text, opt)
|
||||||
local plain = not opt.pattern
|
|
||||||
local pattern = text
|
for line = line, #doc.lines do
|
||||||
local search_func = string.find
|
|
||||||
if opt.regex then
|
|
||||||
pattern = regex.compile(text, opt.no_case and "i" or "")
|
|
||||||
search_func = regex_func
|
|
||||||
end
|
|
||||||
local start, finish, step = line, #doc.lines, 1
|
|
||||||
if opt.reverse then
|
|
||||||
start, finish, step = line, 1, -1
|
|
||||||
end
|
|
||||||
for line = start, finish, step do
|
|
||||||
local line_text = doc.lines[line]
|
local line_text = doc.lines[line]
|
||||||
if opt.no_case and not opt.regex then
|
if opt.no_case then
|
||||||
line_text = line_text:lower()
|
line_text = line_text:lower()
|
||||||
end
|
end
|
||||||
local s, e
|
local s, e = line_text:find(text, col, not opt.pattern)
|
||||||
if opt.reverse then
|
|
||||||
s, e = rfind(search_func, line_text, pattern, col - 1, plain)
|
|
||||||
else
|
|
||||||
s, e = search_func(line_text, pattern, col, plain)
|
|
||||||
end
|
|
||||||
if s then
|
if s then
|
||||||
return line, s, line, e + 1
|
return line, s, line, e + 1
|
||||||
end
|
end
|
||||||
col = opt.reverse and -1 or 1
|
col = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if opt.wrap then
|
if opt.wrap then
|
||||||
opt = { no_case = opt.no_case, regex = opt.regex, reverse = opt.reverse }
|
opt = { no_case = opt.no_case, pattern = opt.pattern }
|
||||||
if opt.reverse then
|
return search.find(doc, 1, 1, text, opt)
|
||||||
return search.find(doc, #doc.lines, #doc.lines[#doc.lines], text, opt)
|
|
||||||
else
|
|
||||||
return search.find(doc, 1, 1, text, opt)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -117,10 +117,6 @@ function translate.start_of_line(doc, line, col)
|
||||||
return line, 1
|
return line, 1
|
||||||
end
|
end
|
||||||
|
|
||||||
function translate.start_of_indentation(doc, line, col)
|
|
||||||
local s, e = doc.lines[line]:find("^%s*")
|
|
||||||
return line, col > e + 1 and e + 1 or 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function translate.end_of_line(doc, line, col)
|
function translate.end_of_line(doc, line, col)
|
||||||
return line, math.huge
|
return line, math.huge
|
||||||
|
|
|
@ -6,11 +6,9 @@ local keymap = require "core.keymap"
|
||||||
local translate = require "core.doc.translate"
|
local translate = require "core.doc.translate"
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
|
|
||||||
---@class core.docview : core.view
|
|
||||||
---@field super core.view
|
|
||||||
local DocView = View:extend()
|
local DocView = View:extend()
|
||||||
|
|
||||||
DocView.context = "session"
|
|
||||||
|
|
||||||
local function move_to_line_offset(dv, line, col, offset)
|
local function move_to_line_offset(dv, line, col, offset)
|
||||||
local xo = dv.last_x_offset
|
local xo = dv.last_x_offset
|
||||||
|
@ -30,9 +28,6 @@ DocView.translate = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["next_page"] = function(doc, line, col, dv)
|
["next_page"] = function(doc, line, col, dv)
|
||||||
if line == #doc.lines then
|
|
||||||
return #doc.lines, #doc.lines[line]
|
|
||||||
end
|
|
||||||
local min, max = dv:get_visible_line_range()
|
local min, max = dv:get_visible_line_range()
|
||||||
return line + (max - min), 1
|
return line + (max - min), 1
|
||||||
end,
|
end,
|
||||||
|
@ -66,22 +61,19 @@ end
|
||||||
function DocView:try_close(do_close)
|
function DocView:try_close(do_close)
|
||||||
if self.doc:is_dirty()
|
if self.doc:is_dirty()
|
||||||
and #core.get_views_referencing_doc(self.doc) == 1 then
|
and #core.get_views_referencing_doc(self.doc) == 1 then
|
||||||
core.command_view:enter("Unsaved Changes; Confirm Close", {
|
core.command_view:enter("Unsaved Changes; Confirm Close", function(_, item)
|
||||||
submit = function(_, item)
|
if item.text:match("^[cC]") then
|
||||||
if item.text:match("^[cC]") then
|
do_close()
|
||||||
do_close()
|
elseif item.text:match("^[sS]") then
|
||||||
elseif item.text:match("^[sS]") then
|
self.doc:save()
|
||||||
self.doc:save()
|
do_close()
|
||||||
do_close()
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
suggest = function(text)
|
|
||||||
local items = {}
|
|
||||||
if not text:find("^[^cC]") then table.insert(items, "Close Without Saving") end
|
|
||||||
if not text:find("^[^sS]") then table.insert(items, "Save And Close") end
|
|
||||||
return items
|
|
||||||
end
|
end
|
||||||
})
|
end, function(text)
|
||||||
|
local items = {}
|
||||||
|
if not text:find("^[^cC]") then table.insert(items, "Close Without Saving") end
|
||||||
|
if not text:find("^[^sS]") then table.insert(items, "Save And Close") end
|
||||||
|
return items
|
||||||
|
end)
|
||||||
else
|
else
|
||||||
do_close()
|
do_close()
|
||||||
end
|
end
|
||||||
|
@ -104,7 +96,7 @@ function DocView:get_filename()
|
||||||
end
|
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
|
||||||
return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2
|
return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2
|
||||||
end
|
end
|
||||||
|
@ -123,23 +115,18 @@ end
|
||||||
|
|
||||||
|
|
||||||
function DocView:get_gutter_width()
|
function DocView:get_gutter_width()
|
||||||
local padding = style.padding.x * 2
|
return self:get_font():get_width(#self.doc.lines) + style.padding.x * 2
|
||||||
return self:get_font():get_width(#self.doc.lines) + padding, padding
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:get_line_screen_position(line, col)
|
function DocView:get_line_screen_position(idx)
|
||||||
local x, y = self:get_content_offset()
|
local x, y = self:get_content_offset()
|
||||||
local lh = self:get_line_height()
|
local lh = self:get_line_height()
|
||||||
local gw = self:get_gutter_width()
|
local gw = self:get_gutter_width()
|
||||||
y = y + (line-1) * lh + style.padding.y
|
return x + gw, y + (idx-1) * lh + style.padding.y
|
||||||
if col then
|
|
||||||
return x + gw + self:get_col_x_offset(line, col), y
|
|
||||||
else
|
|
||||||
return x + gw, y
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:get_line_text_y_offset()
|
function DocView:get_line_text_y_offset()
|
||||||
local lh = self:get_line_height()
|
local lh = self:get_line_height()
|
||||||
local th = self:get_font():get_height()
|
local th = self:get_font():get_height()
|
||||||
|
@ -164,14 +151,14 @@ function DocView:get_col_x_offset(line, col)
|
||||||
local font = style.syntax_fonts[type] or default_font
|
local font = style.syntax_fonts[type] or default_font
|
||||||
for char in common.utf8_chars(text) do
|
for char in common.utf8_chars(text) do
|
||||||
if column == col then
|
if column == col then
|
||||||
return xoffset
|
return xoffset / font:subpixel_scale()
|
||||||
end
|
end
|
||||||
xoffset = xoffset + font:get_width(char)
|
xoffset = xoffset + font:get_width_subpixel(char)
|
||||||
column = column + #char
|
column = column + #char
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return xoffset
|
return xoffset / default_font:subpixel_scale()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -180,12 +167,14 @@ function DocView:get_x_offset_col(line, x)
|
||||||
|
|
||||||
local xoffset, last_i, i = 0, 1, 1
|
local xoffset, last_i, i = 0, 1, 1
|
||||||
local default_font = self:get_font()
|
local default_font = self:get_font()
|
||||||
|
local subpixel_scale = default_font:subpixel_scale()
|
||||||
|
local x_subpixel = subpixel_scale * x + subpixel_scale / 2
|
||||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||||
local font = style.syntax_fonts[type] or default_font
|
local font = style.syntax_fonts[type] or default_font
|
||||||
for char in common.utf8_chars(text) do
|
for char in common.utf8_chars(text) do
|
||||||
local w = font:get_width(char)
|
local w = font:get_width_subpixel(char)
|
||||||
if xoffset >= x then
|
if xoffset >= subpixel_scale * x then
|
||||||
return (xoffset - x > w / 2) and last_i or i
|
return (xoffset - x_subpixel > w / 2) and last_i or i
|
||||||
end
|
end
|
||||||
xoffset = xoffset + w
|
xoffset = xoffset + w
|
||||||
last_i = i
|
last_i = i
|
||||||
|
@ -209,9 +198,8 @@ end
|
||||||
function DocView:scroll_to_line(line, ignore_if_visible, instant)
|
function DocView:scroll_to_line(line, ignore_if_visible, instant)
|
||||||
local min, max = self:get_visible_line_range()
|
local min, max = self:get_visible_line_range()
|
||||||
if not (ignore_if_visible and line > min and line < max) then
|
if not (ignore_if_visible and line > min and line < max) then
|
||||||
local x, y = self:get_line_screen_position(line)
|
local lh = self:get_line_height()
|
||||||
local ox, oy = self:get_content_offset()
|
self.scroll.to.y = math.max(0, lh * (line - 1) - self.size.y / 2)
|
||||||
self.scroll.to.y = math.max(0, y - oy - self.size.y / 2)
|
|
||||||
if instant then
|
if instant then
|
||||||
self.scroll.y = self.scroll.to.y
|
self.scroll.y = self.scroll.to.y
|
||||||
end
|
end
|
||||||
|
@ -220,10 +208,10 @@ end
|
||||||
|
|
||||||
|
|
||||||
function DocView:scroll_to_make_visible(line, col)
|
function DocView:scroll_to_make_visible(line, col)
|
||||||
local ox, oy = self:get_content_offset()
|
local min = self:get_line_height() * (line - 1)
|
||||||
local _, ly = self:get_line_screen_position(line, col)
|
local max = self:get_line_height() * (line + 2) - self.size.y
|
||||||
local lh = self:get_line_height()
|
self.scroll.to.y = math.min(self.scroll.to.y, min)
|
||||||
self.scroll.to.y = common.clamp(self.scroll.to.y, ly - oy - self.size.y + lh * 2, ly - oy - lh)
|
self.scroll.to.y = math.max(self.scroll.to.y, max)
|
||||||
local gw = self:get_gutter_width()
|
local gw = self:get_gutter_width()
|
||||||
local xoffset = self:get_col_x_offset(line, col)
|
local xoffset = self:get_col_x_offset(line, col)
|
||||||
local xmargin = 3 * self:get_font():get_width(' ')
|
local xmargin = 3 * self:get_font():get_width(' ')
|
||||||
|
@ -236,44 +224,20 @@ function DocView:scroll_to_make_visible(line, col)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function DocView:on_mouse_moved(x, y, ...)
|
|
||||||
DocView.super.on_mouse_moved(self, x, y, ...)
|
|
||||||
|
|
||||||
if self.hovered_scrollbar_track or self.dragging_scrollbar then
|
local function mouse_selection(doc, clicks, line1, col1, line2, col2)
|
||||||
self.cursor = "arrow"
|
|
||||||
else
|
|
||||||
self.cursor = "ibeam"
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.mouse_selecting then
|
|
||||||
local l1, c1 = self:resolve_screen_position(x, y)
|
|
||||||
local l2, c2, snap_type = table.unpack(self.mouse_selecting)
|
|
||||||
if keymap.modkeys["ctrl"] then
|
|
||||||
if l1 > l2 then l1, l2 = l2, l1 end
|
|
||||||
self.doc.selections = { }
|
|
||||||
for i = l1, l2 do
|
|
||||||
self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c2, #self.doc.lines[i]))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if snap_type then
|
|
||||||
l1, c1, l2, c2 = self:mouse_selection(self.doc, snap_type, l1, c1, l2, c2)
|
|
||||||
end
|
|
||||||
self.doc:set_selection(l1, c1, l2, c2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function DocView:mouse_selection(doc, snap_type, line1, col1, line2, col2)
|
|
||||||
local swap = line2 < line1 or line2 == line1 and col2 <= col1
|
local swap = line2 < line1 or line2 == line1 and col2 <= col1
|
||||||
if swap then
|
if swap then
|
||||||
line1, col1, line2, col2 = line2, col2, line1, col1
|
line1, col1, line2, col2 = line2, col2, line1, col1
|
||||||
end
|
end
|
||||||
if snap_type == "word" then
|
if clicks % 4 == 2 then
|
||||||
line1, col1 = translate.start_of_word(doc, line1, col1)
|
line1, col1 = translate.start_of_word(doc, line1, col1)
|
||||||
line2, col2 = translate.end_of_word(doc, line2, col2)
|
line2, col2 = translate.end_of_word(doc, line2, col2)
|
||||||
elseif snap_type == "lines" then
|
elseif clicks % 4 == 3 then
|
||||||
col1, col2 = 1, math.huge
|
if line2 == #doc.lines and doc.lines[#doc.lines] ~= "\n" then
|
||||||
|
doc:insert(math.huge, math.huge, "\n")
|
||||||
|
end
|
||||||
|
line1, col1, line2, col2 = line1, 1, line2 + 1, 1
|
||||||
end
|
end
|
||||||
if swap then
|
if swap then
|
||||||
return line2, col2, line1, col1
|
return line2, col2, line1, col1
|
||||||
|
@ -282,8 +246,46 @@ function DocView:mouse_selection(doc, snap_type, line1, col1, line2, col2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:on_mouse_released(...)
|
function DocView:on_mouse_pressed(button, x, y, clicks)
|
||||||
DocView.super.on_mouse_released(self, ...)
|
local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||||
|
if caught then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if keymap.modkeys["shift"] then
|
||||||
|
if clicks % 2 == 1 then
|
||||||
|
local line1, col1 = select(3, self.doc:get_selection())
|
||||||
|
local line2, col2 = self:resolve_screen_position(x, y)
|
||||||
|
self.doc:set_selection(line2, col2, line1, col1)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local line, col = self:resolve_screen_position(x, y)
|
||||||
|
self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col))
|
||||||
|
self.mouse_selecting = { line, col, clicks = clicks }
|
||||||
|
end
|
||||||
|
core.blink_reset()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function DocView:on_mouse_moved(x, y, ...)
|
||||||
|
DocView.super.on_mouse_moved(self, x, y, ...)
|
||||||
|
|
||||||
|
if self:scrollbar_overlaps_point(x, y) or self.dragging_scrollbar then
|
||||||
|
self.cursor = "arrow"
|
||||||
|
else
|
||||||
|
self.cursor = "ibeam"
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.mouse_selecting then
|
||||||
|
local l1, c1 = self:resolve_screen_position(x, y)
|
||||||
|
local l2, c2 = table.unpack(self.mouse_selecting)
|
||||||
|
local clicks = self.mouse_selecting.clicks
|
||||||
|
self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function DocView:on_mouse_released(button)
|
||||||
|
DocView.super.on_mouse_released(self, button)
|
||||||
self.mouse_selecting = nil
|
self.mouse_selecting = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -295,15 +297,13 @@ end
|
||||||
|
|
||||||
function DocView:update()
|
function DocView:update()
|
||||||
-- scroll to make caret visible and reset blink timer if it moved
|
-- scroll to make caret visible and reset blink timer if it moved
|
||||||
local line1, col1, line2, col2 = self.doc:get_selection()
|
local line, col = self.doc:get_selection()
|
||||||
if (line1 ~= self.last_line1 or col1 ~= self.last_col1 or
|
if (line ~= self.last_line or col ~= self.last_col) and self.size.x > 0 then
|
||||||
line2 ~= self.last_line2 or col2 ~= self.last_col2) and self.size.x > 0 then
|
|
||||||
if core.active_view == self then
|
if core.active_view == self then
|
||||||
self:scroll_to_make_visible(line1, col1)
|
self:scroll_to_make_visible(line, col)
|
||||||
end
|
end
|
||||||
core.blink_reset()
|
core.blink_reset()
|
||||||
self.last_line1, self.last_col1 = line1, col1
|
self.last_line, self.last_col = line, col
|
||||||
self.last_line2, self.last_col2 = line2, col2
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update blink timer
|
-- update blink timer
|
||||||
|
@ -326,119 +326,94 @@ function DocView:draw_line_highlight(x, y)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:draw_line_text(line, x, y)
|
function DocView:draw_line_text(idx, x, y)
|
||||||
local default_font = self:get_font()
|
local default_font = self:get_font()
|
||||||
local tx, ty = x, y + self:get_line_text_y_offset()
|
local subpixel_scale = default_font:subpixel_scale()
|
||||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset()
|
||||||
|
for _, type, text in self.doc.highlighter:each_token(idx) do
|
||||||
local color = style.syntax[type]
|
local color = style.syntax[type]
|
||||||
local font = style.syntax_fonts[type] or default_font
|
local font = style.syntax_fonts[type] or default_font
|
||||||
tx = renderer.draw_text(font, text, tx, ty, color)
|
if config.draw_whitespace then
|
||||||
end
|
tx = renderer.draw_text_subpixel(font, text, tx, ty, color, core.replacements, style.syntax.comment)
|
||||||
return self:get_line_height()
|
else
|
||||||
end
|
tx = renderer.draw_text_subpixel(font, text, tx, ty, color)
|
||||||
|
|
||||||
function DocView:draw_caret(x, y)
|
|
||||||
local lh = self:get_line_height()
|
|
||||||
renderer.draw_rect(x, y, style.caret_width, lh, style.caret)
|
|
||||||
end
|
|
||||||
|
|
||||||
function DocView:draw_line_body(line, x, y)
|
|
||||||
-- draw highlight if any selection ends on this line
|
|
||||||
local draw_highlight = false
|
|
||||||
local hcl = config.highlight_current_line
|
|
||||||
if hcl ~= false then
|
|
||||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
|
|
||||||
if line1 == line then
|
|
||||||
if hcl == "no_selection" then
|
|
||||||
if (line1 ~= line2) or (col1 ~= col2) then
|
|
||||||
draw_highlight = false
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
draw_highlight = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if draw_highlight and core.active_view == self then
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function DocView:draw_line_body(idx, x, y)
|
||||||
|
local line, col = self.doc:get_selection()
|
||||||
|
|
||||||
|
-- draw selection if it overlaps this line
|
||||||
|
local line1, col1, line2, col2 = self.doc:get_selection(true)
|
||||||
|
if idx >= line1 and idx <= line2 then
|
||||||
|
local text = self.doc.lines[idx]
|
||||||
|
if line1 ~= idx then col1 = 1 end
|
||||||
|
if line2 ~= idx then col2 = #text + 1 end
|
||||||
|
local x1 = x + self:get_col_x_offset(idx, col1)
|
||||||
|
local x2 = x + self:get_col_x_offset(idx, col2)
|
||||||
|
local lh = self:get_line_height()
|
||||||
|
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- draw line highlight if caret is on this line
|
||||||
|
if config.highlight_current_line and not self.doc:has_selection()
|
||||||
|
and line == idx and core.active_view == self then
|
||||||
self:draw_line_highlight(x + self.scroll.x, y)
|
self:draw_line_highlight(x + self.scroll.x, y)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- draw selection if it overlaps this line
|
|
||||||
local lh = self:get_line_height()
|
|
||||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
|
||||||
if line >= line1 and line <= line2 then
|
|
||||||
local text = self.doc.lines[line]
|
|
||||||
if line1 ~= line then col1 = 1 end
|
|
||||||
if line2 ~= line then col2 = #text + 1 end
|
|
||||||
local x1 = x + self:get_col_x_offset(line, col1)
|
|
||||||
local x2 = x + self:get_col_x_offset(line, col2)
|
|
||||||
if x1 ~= x2 then
|
|
||||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- draw line's text
|
-- draw line's text
|
||||||
return self:draw_line_text(line, x, y)
|
self:draw_line_text(idx, x, y)
|
||||||
|
|
||||||
|
-- draw caret if it overlaps this line
|
||||||
|
local T = config.blink_period
|
||||||
|
if line == idx and core.active_view == self
|
||||||
|
and (core.blink_timer - core.blink_start) % T < T / 2
|
||||||
|
and system.window_has_focus() then
|
||||||
|
local lh = self:get_line_height()
|
||||||
|
local x1 = x + self:get_col_x_offset(line, col)
|
||||||
|
renderer.draw_rect(x1, y, style.caret_width, lh, style.caret)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:draw_line_gutter(line, x, y, width)
|
function DocView:draw_line_gutter(idx, x, y)
|
||||||
local color = style.line_number
|
local color = style.line_number
|
||||||
for _, line1, _, line2 in self.doc:get_selections(true) do
|
local line1, _, line2, _ = self.doc:get_selection(true)
|
||||||
if line >= line1 and line <= line2 then
|
if idx >= line1 and idx <= line2 then
|
||||||
color = style.line_number2
|
color = style.line_number2
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
local yoffset = self:get_line_text_y_offset()
|
||||||
x = x + style.padding.x
|
x = x + style.padding.x
|
||||||
local lh = self:get_line_height()
|
renderer.draw_text(self:get_font(), idx, x, y + yoffset, color)
|
||||||
common.draw_text(self:get_font(), color, line, "right", x, y, width, lh)
|
|
||||||
return lh
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:draw_overlay()
|
|
||||||
if core.active_view == self then
|
|
||||||
local minline, maxline = self:get_visible_line_range()
|
|
||||||
-- draw caret if it overlaps this line
|
|
||||||
local T = config.blink_period
|
|
||||||
for _, line, col in self.doc:get_selections() do
|
|
||||||
if line >= minline and line <= maxline
|
|
||||||
and system.window_has_focus() then
|
|
||||||
if config.disable_blink
|
|
||||||
or (core.blink_timer - core.blink_start) % T < T / 2 then
|
|
||||||
self:draw_caret(self:get_line_screen_position(line, col))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function DocView:draw()
|
function DocView:draw()
|
||||||
self:draw_background(style.background)
|
self:draw_background(style.background)
|
||||||
local _, indent_size = self.doc:get_indent_info()
|
|
||||||
self:get_font():set_tab_size(indent_size)
|
local font = self:get_font()
|
||||||
|
font:set_tab_size(config.indent_size)
|
||||||
|
|
||||||
local minline, maxline = self:get_visible_line_range()
|
local minline, maxline = self:get_visible_line_range()
|
||||||
local lh = self:get_line_height()
|
local lh = self:get_line_height()
|
||||||
|
|
||||||
local x, y = self:get_line_screen_position(minline)
|
local _, y = self:get_line_screen_position(minline)
|
||||||
local gw, gpad = self:get_gutter_width()
|
local x = self.position.x
|
||||||
for i = minline, maxline do
|
for i = minline, maxline do
|
||||||
y = y + (self:draw_line_gutter(i, self.position.x, y, gpad and gw - gpad or gw) or lh)
|
self:draw_line_gutter(i, x, y)
|
||||||
|
y = y + lh
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local x, y = self:get_line_screen_position(minline)
|
||||||
|
local gw = self:get_gutter_width()
|
||||||
local pos = self.position
|
local pos = self.position
|
||||||
x, y = self:get_line_screen_position(minline)
|
core.push_clip_rect(pos.x + gw, pos.y, self.size.x, self.size.y)
|
||||||
-- the clip below ensure we don't write on the gutter region. On the
|
|
||||||
-- right side it is redundant with the Node's clip.
|
|
||||||
core.push_clip_rect(pos.x + gw, pos.y, self.size.x - gw, self.size.y)
|
|
||||||
for i = minline, maxline do
|
for i = minline, maxline do
|
||||||
y = y + (self:draw_line_body(i, x, y) or lh)
|
self:draw_line_body(i, x, y)
|
||||||
|
y = y + lh
|
||||||
end
|
end
|
||||||
self:draw_overlay()
|
|
||||||
core.pop_clip_rect()
|
core.pop_clip_rect()
|
||||||
|
|
||||||
self:draw_scrollbar()
|
self:draw_scrollbar()
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
local style = require "core.style"
|
|
||||||
local keymap = require "core.keymap"
|
|
||||||
local View = require "core.view"
|
|
||||||
|
|
||||||
---@class core.emptyview : core.view
|
|
||||||
---@field super core.view
|
|
||||||
local EmptyView = View:extend()
|
|
||||||
|
|
||||||
local function draw_text(x, y, color)
|
|
||||||
local th = style.big_font:get_height()
|
|
||||||
local dh = 2 * th + style.padding.y * 2
|
|
||||||
local x1, y1 = x, y + (dh - th) / 2
|
|
||||||
local xv = x1
|
|
||||||
local title = "Lite XL"
|
|
||||||
local version = "version " .. VERSION
|
|
||||||
local title_width = style.big_font:get_width(title)
|
|
||||||
local version_width = style.font:get_width(version)
|
|
||||||
if version_width > title_width then
|
|
||||||
version = VERSION
|
|
||||||
version_width = style.font:get_width(version)
|
|
||||||
xv = x1 - (version_width - title_width)
|
|
||||||
end
|
|
||||||
x = renderer.draw_text(style.big_font, title, x1, y1, color)
|
|
||||||
renderer.draw_text(style.font, version, xv, y1 + th, color)
|
|
||||||
x = x + style.padding.x
|
|
||||||
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
|
|
||||||
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()
|
|
||||||
y = y + (dh - (th + style.padding.y) * #lines) / 2
|
|
||||||
local w = 0
|
|
||||||
for _, line in ipairs(lines) do
|
|
||||||
local text = string.format(line.fmt, keymap.get_binding(line.cmd))
|
|
||||||
w = math.max(w, renderer.draw_text(style.font, text, x + style.padding.x, y, color))
|
|
||||||
y = y + th + style.padding.y
|
|
||||||
end
|
|
||||||
return w, dh
|
|
||||||
end
|
|
||||||
|
|
||||||
function EmptyView:draw()
|
|
||||||
self:draw_background(style.background)
|
|
||||||
local w, h = draw_text(0, 0, { 0, 0, 0, 0 })
|
|
||||||
local x = self.position.x + math.max(style.padding.x, (self.size.x - w) / 2)
|
|
||||||
local y = self.position.y + (self.size.y - h) / 2
|
|
||||||
draw_text(x, y, style.dim)
|
|
||||||
end
|
|
||||||
|
|
||||||
return EmptyView
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,7 +6,6 @@ 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+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",
|
||||||
|
@ -18,8 +17,7 @@ local function keymap_macos(keymap)
|
||||||
["cmd+ctrl+i"] = "root:switch-to-up",
|
["cmd+ctrl+i"] = "root:switch-to-up",
|
||||||
["cmd+ctrl+k"] = "root:switch-to-down",
|
["cmd+ctrl+k"] = "root:switch-to-down",
|
||||||
|
|
||||||
|
["ctrl+w"] = "root:close",
|
||||||
["cmd+w"] = "root:close-or-quit",
|
|
||||||
["ctrl+tab"] = "root:switch-to-next-tab",
|
["ctrl+tab"] = "root:switch-to-next-tab",
|
||||||
["ctrl+shift+tab"] = "root:switch-to-previous-tab",
|
["ctrl+shift+tab"] = "root:switch-to-previous-tab",
|
||||||
["cmd+pageup"] = "root:move-tab-left",
|
["cmd+pageup"] = "root:move-tab-left",
|
||||||
|
@ -33,8 +31,7 @@ local function keymap_macos(keymap)
|
||||||
["cmd+7"] = "root:switch-to-tab-7",
|
["cmd+7"] = "root:switch-to-tab-7",
|
||||||
["cmd+8"] = "root:switch-to-tab-8",
|
["cmd+8"] = "root:switch-to-tab-8",
|
||||||
["cmd+9"] = "root:switch-to-tab-9",
|
["cmd+9"] = "root:switch-to-tab-9",
|
||||||
["wheel"] = "root:scroll",
|
|
||||||
|
|
||||||
["cmd+f"] = "find-replace:find",
|
["cmd+f"] = "find-replace:find",
|
||||||
["cmd+r"] = "find-replace:replace",
|
["cmd+r"] = "find-replace:replace",
|
||||||
["f3"] = "find-replace:repeat-find",
|
["f3"] = "find-replace:repeat-find",
|
||||||
|
@ -55,27 +52,23 @@ local function keymap_macos(keymap)
|
||||||
["shift+tab"] = "doc:unindent",
|
["shift+tab"] = "doc:unindent",
|
||||||
["backspace"] = "doc:backspace",
|
["backspace"] = "doc:backspace",
|
||||||
["shift+backspace"] = "doc:backspace",
|
["shift+backspace"] = "doc:backspace",
|
||||||
["option+backspace"] = "doc:delete-to-previous-word-start",
|
["cmd+backspace"] = "doc:delete-to-previous-word-start",
|
||||||
["cmd+shift+backspace"] = "doc:delete-to-previous-word-start",
|
["cmd+shift+backspace"] = "doc:delete-to-previous-word-start",
|
||||||
["cmd+backspace"] = "doc:delete-to-start-of-indentation",
|
|
||||||
["delete"] = "doc:delete",
|
["delete"] = "doc:delete",
|
||||||
["shift+delete"] = "doc:delete",
|
["shift+delete"] = "doc:delete",
|
||||||
["option+delete"] = "doc:delete-to-next-word-end",
|
["cmd+delete"] = "doc:delete-to-next-word-end",
|
||||||
["cmd+shift+delete"] = "doc:delete-to-next-word-end",
|
["cmd+shift+delete"] = "doc:delete-to-next-word-end",
|
||||||
["cmd+delete"] = "doc:delete-to-end-of-line",
|
|
||||||
["return"] = { "command:submit", "doc:newline", "dialog:select" },
|
["return"] = { "command:submit", "doc:newline", "dialog:select" },
|
||||||
["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" },
|
["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" },
|
||||||
["cmd+return"] = "doc:newline-below",
|
["cmd+return"] = "doc:newline-below",
|
||||||
["cmd+shift+return"] = "doc:newline-above",
|
["cmd+shift+return"] = "doc:newline-above",
|
||||||
["cmd+j"] = "doc:join-lines",
|
["cmd+j"] = "doc:join-lines",
|
||||||
["cmd+a"] = "doc:select-all",
|
["cmd+a"] = "doc:select-all",
|
||||||
["cmd+d"] = { "find-replace:select-add-next", "doc:select-word" },
|
["cmd+d"] = { "find-replace:select-next", "doc:select-word" },
|
||||||
["cmd+f3"] = "find-replace:select-next",
|
|
||||||
["cmd+l"] = "doc:select-lines",
|
["cmd+l"] = "doc:select-lines",
|
||||||
["cmd+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
|
||||||
["cmd+/"] = "doc:toggle-line-comments",
|
["cmd+/"] = "doc:toggle-line-comments",
|
||||||
["option+up"] = "doc:move-lines-up",
|
["cmd+up"] = "doc:move-lines-up",
|
||||||
["option+down"] = "doc:move-lines-down",
|
["cmd+down"] = "doc:move-lines-down",
|
||||||
["cmd+shift+d"] = "doc:duplicate-lines",
|
["cmd+shift+d"] = "doc:duplicate-lines",
|
||||||
["cmd+shift+k"] = "doc:delete-lines",
|
["cmd+shift+k"] = "doc:delete-lines",
|
||||||
|
|
||||||
|
@ -83,42 +76,31 @@ local function keymap_macos(keymap)
|
||||||
["right"] = { "doc:move-to-next-char", "dialog:next-entry"},
|
["right"] = { "doc:move-to-next-char", "dialog:next-entry"},
|
||||||
["up"] = { "command:select-previous", "doc:move-to-previous-line" },
|
["up"] = { "command:select-previous", "doc:move-to-previous-line" },
|
||||||
["down"] = { "command:select-next", "doc:move-to-next-line" },
|
["down"] = { "command:select-next", "doc:move-to-next-line" },
|
||||||
["option+left"] = "doc:move-to-previous-word-start",
|
["cmd+left"] = "doc:move-to-previous-word-start",
|
||||||
["option+right"] = "doc:move-to-next-word-end",
|
["cmd+right"] = "doc:move-to-next-word-end",
|
||||||
["cmd+left"] = "doc:move-to-start-of-indentation",
|
|
||||||
["cmd+right"] = "doc:move-to-end-of-line",
|
|
||||||
["cmd+["] = "doc:move-to-previous-block-start",
|
["cmd+["] = "doc:move-to-previous-block-start",
|
||||||
["cmd+]"] = "doc:move-to-next-block-end",
|
["cmd+]"] = "doc:move-to-next-block-end",
|
||||||
["home"] = "doc:move-to-start-of-indentation",
|
["home"] = "doc:move-to-start-of-line",
|
||||||
["end"] = "doc:move-to-end-of-line",
|
["end"] = "doc:move-to-end-of-line",
|
||||||
["cmd+up"] = "doc:move-to-start-of-doc",
|
["cmd+home"] = "doc:move-to-start-of-doc",
|
||||||
["cmd+down"] = "doc:move-to-end-of-doc",
|
["cmd+end"] = "doc:move-to-end-of-doc",
|
||||||
["pageup"] = "doc:move-to-previous-page",
|
["pageup"] = "doc:move-to-previous-page",
|
||||||
["pagedown"] = "doc:move-to-next-page",
|
["pagedown"] = "doc:move-to-next-page",
|
||||||
|
|
||||||
["shift+1lclick"] = "doc:select-to-cursor",
|
|
||||||
["ctrl+1lclick"] = "doc:split-cursor",
|
|
||||||
["1lclick"] = "doc:set-cursor",
|
|
||||||
["2lclick"] = "doc:set-cursor-word",
|
|
||||||
["3lclick"] = "doc:set-cursor-line",
|
|
||||||
["shift+left"] = "doc:select-to-previous-char",
|
["shift+left"] = "doc:select-to-previous-char",
|
||||||
["shift+right"] = "doc:select-to-next-char",
|
["shift+right"] = "doc:select-to-next-char",
|
||||||
["shift+up"] = "doc:select-to-previous-line",
|
["shift+up"] = "doc:select-to-previous-line",
|
||||||
["shift+down"] = "doc:select-to-next-line",
|
["shift+down"] = "doc:select-to-next-line",
|
||||||
["option+shift+left"] = "doc:select-to-previous-word-start",
|
["cmd+shift+left"] = "doc:select-to-previous-word-start",
|
||||||
["option+shift+right"] = "doc:select-to-next-word-end",
|
["cmd+shift+right"] = "doc:select-to-next-word-end",
|
||||||
["cmd+shift+left"] = "doc:select-to-start-of-indentation",
|
|
||||||
["cmd+shift+right"] = "doc:select-to-end-of-line",
|
|
||||||
["cmd+shift+["] = "doc:select-to-previous-block-start",
|
["cmd+shift+["] = "doc:select-to-previous-block-start",
|
||||||
["cmd+shift+]"] = "doc:select-to-next-block-end",
|
["cmd+shift+]"] = "doc:select-to-next-block-end",
|
||||||
["shift+home"] = "doc:select-to-start-of-indentation",
|
["shift+home"] = "doc:select-to-start-of-line",
|
||||||
["shift+end"] = "doc:select-to-end-of-line",
|
["shift+end"] = "doc:select-to-end-of-line",
|
||||||
["cmd+shift+up"] = "doc:select-to-start-of-doc",
|
["cmd+shift+home"] = "doc:select-to-start-of-doc",
|
||||||
["cmd+shift+down"] = "doc:select-to-end-of-doc",
|
["cmd+shift+end"] = "doc:select-to-end-of-doc",
|
||||||
["shift+pageup"] = "doc:select-to-previous-page",
|
["shift+pageup"] = "doc:select-to-previous-page",
|
||||||
["shift+pagedown"] = "doc:select-to-next-page",
|
["shift+pagedown"] = "doc:select-to-next-page",
|
||||||
["cmd+option+up"] = "doc:create-cursor-previous-line",
|
|
||||||
["cmd+option+down"] = "doc:create-cursor-next-line"
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,140 +1,52 @@
|
||||||
local core = require "core"
|
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local config = require "core.config"
|
|
||||||
local keymap = {}
|
local keymap = {}
|
||||||
|
|
||||||
---@alias keymap.shortcut string
|
|
||||||
---@alias keymap.command string
|
|
||||||
---@alias keymap.modkey string
|
|
||||||
---@alias keymap.pressed boolean
|
|
||||||
---@alias keymap.map table<keymap.shortcut,keymap.command|keymap.command[]>
|
|
||||||
---@alias keymap.rmap table<keymap.command, keymap.shortcut|keymap.shortcut[]>
|
|
||||||
|
|
||||||
---Pressed status of mod keys.
|
|
||||||
---@type table<keymap.modkey, keymap.pressed>
|
|
||||||
keymap.modkeys = {}
|
keymap.modkeys = {}
|
||||||
|
|
||||||
---List of commands assigned to a shortcut been the key of the map the shortcut.
|
|
||||||
---@type keymap.map
|
|
||||||
keymap.map = {}
|
keymap.map = {}
|
||||||
|
|
||||||
---List of shortcuts assigned to a command been the key of the map the command.
|
|
||||||
---@type keymap.rmap
|
|
||||||
keymap.reverse_map = {}
|
keymap.reverse_map = {}
|
||||||
|
|
||||||
local macos = PLATFORM == "Mac OS X"
|
local macos = rawget(_G, "MACOS_RESOURCES")
|
||||||
local os4 = PLATFORM == "AmigaOS 4"
|
local os4 = false
|
||||||
local mos = PLATFORM == "MORPHOS"
|
if PLATFORM == "AmigaOS 4" then
|
||||||
|
os4 = true
|
||||||
|
end
|
||||||
-- Thanks to mathewmariani, taken from his lite-macos github repository.
|
-- Thanks to mathewmariani, taken from his lite-macos github repository.
|
||||||
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or os4 and "os4" or mos and "mos" or "generic"))
|
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or os4 and "os4" or "generic"))
|
||||||
|
|
||||||
---@type table<keymap.modkey, keymap.modkey>
|
|
||||||
local modkey_map = modkeys_os.map
|
local modkey_map = modkeys_os.map
|
||||||
|
|
||||||
---@type keymap.modkey[]
|
|
||||||
local modkeys = modkeys_os.keys
|
local modkeys = modkeys_os.keys
|
||||||
|
|
||||||
|
local function key_to_stroke(k)
|
||||||
---Generates a stroke sequence including currently pressed mod keys.
|
|
||||||
---@param key string
|
|
||||||
---@return string
|
|
||||||
local function key_to_stroke(key)
|
|
||||||
local stroke = ""
|
local stroke = ""
|
||||||
for _, mk in ipairs(modkeys) do
|
for _, mk in ipairs(modkeys) do
|
||||||
if keymap.modkeys[mk] then
|
if keymap.modkeys[mk] then
|
||||||
stroke = stroke .. mk .. "+"
|
stroke = stroke .. mk .. "+"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return stroke .. key
|
return stroke .. k
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---Remove the given value from an array associated to a key in a table.
|
|
||||||
---@param tbl table<string, string> The table containing the key
|
|
||||||
---@param k string The key containing the array
|
|
||||||
---@param v? string The value to remove from the array
|
|
||||||
local function remove_only(tbl, k, v)
|
|
||||||
if tbl[k] then
|
|
||||||
if v then
|
|
||||||
local j = 0
|
|
||||||
for i=1, #tbl[k] do
|
|
||||||
while tbl[k][i + j] == v do
|
|
||||||
j = j + 1
|
|
||||||
end
|
|
||||||
tbl[k][i] = tbl[k][i + j]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
tbl[k] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Removes from a keymap.map the bindings that are already registered.
|
|
||||||
---@param map keymap.map
|
|
||||||
local function remove_duplicates(map)
|
|
||||||
for stroke, commands in pairs(map) do
|
|
||||||
if type(commands) == "string" or type(commands) == "function" then
|
|
||||||
commands = { commands }
|
|
||||||
end
|
|
||||||
if keymap.map[stroke] then
|
|
||||||
for _, registered_cmd in ipairs(keymap.map[stroke]) do
|
|
||||||
local j = 0
|
|
||||||
for i=1, #commands do
|
|
||||||
while commands[i + j] == registered_cmd do
|
|
||||||
j = j + 1
|
|
||||||
end
|
|
||||||
commands[i] = commands[i + j]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #commands < 1 then
|
|
||||||
map[stroke] = nil
|
|
||||||
else
|
|
||||||
map[stroke] = commands
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Add bindings by replacing commands that were previously assigned to a shortcut.
|
|
||||||
---@param map keymap.map
|
|
||||||
function keymap.add_direct(map)
|
function keymap.add_direct(map)
|
||||||
for stroke, commands in pairs(map) do
|
for stroke, commands in pairs(map) do
|
||||||
if type(commands) == "string" or type(commands) == "function" then
|
if type(commands) == "string" then
|
||||||
commands = { commands }
|
commands = { commands }
|
||||||
end
|
end
|
||||||
if keymap.map[stroke] then
|
|
||||||
for _, cmd in ipairs(keymap.map[stroke]) do
|
|
||||||
remove_only(keymap.reverse_map, cmd, stroke)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
keymap.map[stroke] = commands
|
keymap.map[stroke] = commands
|
||||||
for _, cmd in ipairs(commands) do
|
for _, cmd in ipairs(commands) do
|
||||||
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
|
keymap.reverse_map[cmd] = stroke
|
||||||
table.insert(keymap.reverse_map[cmd], stroke)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---Adds bindings by appending commands to already registered shortcut or by
|
|
||||||
---replacing currently assigned commands if overwrite is specified.
|
|
||||||
---@param map keymap.map
|
|
||||||
---@param overwrite? boolean
|
|
||||||
function keymap.add(map, overwrite)
|
function keymap.add(map, overwrite)
|
||||||
remove_duplicates(map)
|
|
||||||
for stroke, commands in pairs(map) do
|
for stroke, commands in pairs(map) do
|
||||||
if macos then
|
if macos then
|
||||||
stroke = stroke:gsub("%f[%a]ctrl%f[%A]", "cmd")
|
stroke = stroke:gsub("%f[%a]ctrl%f[%A]", "cmd")
|
||||||
end
|
end
|
||||||
|
if type(commands) == "string" then
|
||||||
|
commands = { commands }
|
||||||
|
end
|
||||||
if overwrite then
|
if overwrite then
|
||||||
if keymap.map[stroke] then
|
|
||||||
for _, cmd in ipairs(keymap.map[stroke]) do
|
|
||||||
remove_only(keymap.reverse_map, cmd, stroke)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
keymap.map[stroke] = commands
|
keymap.map[stroke] = commands
|
||||||
else
|
else
|
||||||
keymap.map[stroke] = keymap.map[stroke] or {}
|
keymap.map[stroke] = keymap.map[stroke] or {}
|
||||||
|
@ -143,42 +55,18 @@ function keymap.add(map, overwrite)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for _, cmd in ipairs(commands) do
|
for _, cmd in ipairs(commands) do
|
||||||
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
|
keymap.reverse_map[cmd] = stroke
|
||||||
table.insert(keymap.reverse_map[cmd], stroke)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---Unregisters the given shortcut and associated command.
|
|
||||||
---@param shortcut string
|
|
||||||
---@param cmd string
|
|
||||||
function keymap.unbind(shortcut, cmd)
|
|
||||||
remove_only(keymap.map, shortcut, cmd)
|
|
||||||
remove_only(keymap.reverse_map, cmd, shortcut)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Returns all the shortcuts associated to a command unpacked for easy assignment.
|
|
||||||
---@param cmd string
|
|
||||||
---@return ...
|
|
||||||
function keymap.get_binding(cmd)
|
function keymap.get_binding(cmd)
|
||||||
return table.unpack(keymap.reverse_map[cmd] or {})
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Returns all the shortcuts associated to a command packed in a table.
|
|
||||||
---@param cmd string
|
|
||||||
---@return table<integer, string> | nil shortcuts
|
|
||||||
function keymap.get_bindings(cmd)
|
|
||||||
return keymap.reverse_map[cmd]
|
return keymap.reverse_map[cmd]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
function keymap.on_key_pressed(k)
|
||||||
-- Events listening
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
function keymap.on_key_pressed(k, ...)
|
|
||||||
local mk = modkey_map[k]
|
local mk = modkey_map[k]
|
||||||
if mk then
|
if mk then
|
||||||
keymap.modkeys[mk] = true
|
keymap.modkeys[mk] = true
|
||||||
|
@ -188,39 +76,18 @@ function keymap.on_key_pressed(k, ...)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local stroke = key_to_stroke(k)
|
local stroke = key_to_stroke(k)
|
||||||
local commands, performed = keymap.map[stroke], false
|
local commands = keymap.map[stroke]
|
||||||
if commands then
|
if commands then
|
||||||
for _, cmd in ipairs(commands) do
|
for _, cmd in ipairs(commands) do
|
||||||
if type(cmd) == "function" then
|
local performed = command.perform(cmd)
|
||||||
local ok, res = core.try(cmd, ...)
|
|
||||||
if ok then
|
|
||||||
performed = not (res == false)
|
|
||||||
else
|
|
||||||
performed = true
|
|
||||||
end
|
|
||||||
else
|
|
||||||
performed = command.perform(cmd, ...)
|
|
||||||
end
|
|
||||||
if performed then break end
|
if performed then break end
|
||||||
end
|
end
|
||||||
return performed
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
function keymap.on_mouse_wheel(delta, ...)
|
|
||||||
return not (keymap.on_key_pressed("wheel" .. (delta > 0 and "up" or "down"), delta, ...)
|
|
||||||
or keymap.on_key_pressed("wheel", delta, ...))
|
|
||||||
end
|
|
||||||
|
|
||||||
function keymap.on_mouse_pressed(button, x, y, clicks)
|
|
||||||
local click_number = (((clicks - 1) % config.max_clicks) + 1)
|
|
||||||
return not (keymap.on_key_pressed(click_number .. button:sub(1,1) .. "click", x, y, clicks) or
|
|
||||||
keymap.on_key_pressed(button:sub(1,1) .. "click", x, y, clicks) or
|
|
||||||
keymap.on_key_pressed(click_number .. "click", x, y, clicks) or
|
|
||||||
keymap.on_key_pressed("click", x, y, clicks))
|
|
||||||
end
|
|
||||||
|
|
||||||
function keymap.on_key_released(k)
|
function keymap.on_key_released(k)
|
||||||
local mk = modkey_map[k]
|
local mk = modkey_map[k]
|
||||||
|
@ -230,9 +97,6 @@ function keymap.on_key_released(k)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- Register default bindings
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
if macos then
|
if macos then
|
||||||
local keymap_macos = require("core.keymap-macos")
|
local keymap_macos = require("core.keymap-macos")
|
||||||
keymap_macos(keymap)
|
keymap_macos(keymap)
|
||||||
|
@ -246,9 +110,7 @@ keymap.add_direct {
|
||||||
["ctrl+n"] = "core:new-doc",
|
["ctrl+n"] = "core:new-doc",
|
||||||
["ctrl+shift+c"] = "core:change-project-folder",
|
["ctrl+shift+c"] = "core:change-project-folder",
|
||||||
["ctrl+shift+o"] = "core:open-project-folder",
|
["ctrl+shift+o"] = "core:open-project-folder",
|
||||||
["ctrl+alt+r"] = "core:restart",
|
|
||||||
["alt+return"] = "core:toggle-fullscreen",
|
["alt+return"] = "core:toggle-fullscreen",
|
||||||
["f11"] = "core:toggle-fullscreen",
|
|
||||||
|
|
||||||
["alt+shift+j"] = "root:split-left",
|
["alt+shift+j"] = "root:split-left",
|
||||||
["alt+shift+l"] = "root:split-right",
|
["alt+shift+l"] = "root:split-right",
|
||||||
|
@ -273,14 +135,11 @@ keymap.add_direct {
|
||||||
["alt+7"] = "root:switch-to-tab-7",
|
["alt+7"] = "root:switch-to-tab-7",
|
||||||
["alt+8"] = "root:switch-to-tab-8",
|
["alt+8"] = "root:switch-to-tab-8",
|
||||||
["alt+9"] = "root:switch-to-tab-9",
|
["alt+9"] = "root:switch-to-tab-9",
|
||||||
["wheel"] = "root:scroll",
|
|
||||||
|
|
||||||
["ctrl+f"] = "find-replace:find",
|
["ctrl+f"] = "find-replace:find",
|
||||||
["ctrl+r"] = "find-replace:replace",
|
["ctrl+r"] = "find-replace:replace",
|
||||||
["f3"] = "find-replace:repeat-find",
|
["f3"] = "find-replace:repeat-find",
|
||||||
["shift+f3"] = "find-replace:previous-find",
|
["shift+f3"] = "find-replace:previous-find",
|
||||||
["ctrl+i"] = "find-replace:toggle-sensitivity",
|
|
||||||
["ctrl+shift+i"] = "find-replace:toggle-regex",
|
|
||||||
["ctrl+g"] = "doc:go-to-line",
|
["ctrl+g"] = "doc:go-to-line",
|
||||||
["ctrl+s"] = "doc:save",
|
["ctrl+s"] = "doc:save",
|
||||||
["ctrl+shift+s"] = "doc:save-as",
|
["ctrl+shift+s"] = "doc:save-as",
|
||||||
|
@ -309,13 +168,9 @@ keymap.add_direct {
|
||||||
["ctrl+shift+return"] = "doc:newline-above",
|
["ctrl+shift+return"] = "doc:newline-above",
|
||||||
["ctrl+j"] = "doc:join-lines",
|
["ctrl+j"] = "doc:join-lines",
|
||||||
["ctrl+a"] = "doc:select-all",
|
["ctrl+a"] = "doc:select-all",
|
||||||
["ctrl+d"] = { "find-replace:select-add-next", "doc:select-word" },
|
["ctrl+d"] = { "find-replace:select-next", "doc:select-word" },
|
||||||
["ctrl+f3"] = "find-replace:select-next",
|
|
||||||
["ctrl+shift+f3"] = "find-replace:select-previous",
|
|
||||||
["ctrl+l"] = "doc:select-lines",
|
["ctrl+l"] = "doc:select-lines",
|
||||||
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
|
||||||
["ctrl+/"] = "doc:toggle-line-comments",
|
["ctrl+/"] = "doc:toggle-line-comments",
|
||||||
["ctrl+shift+/"] = "doc:toggle-block-comments",
|
|
||||||
["ctrl+up"] = "doc:move-lines-up",
|
["ctrl+up"] = "doc:move-lines-up",
|
||||||
["ctrl+down"] = "doc:move-lines-down",
|
["ctrl+down"] = "doc:move-lines-down",
|
||||||
["ctrl+shift+d"] = "doc:duplicate-lines",
|
["ctrl+shift+d"] = "doc:duplicate-lines",
|
||||||
|
@ -329,18 +184,13 @@ keymap.add_direct {
|
||||||
["ctrl+right"] = "doc:move-to-next-word-end",
|
["ctrl+right"] = "doc:move-to-next-word-end",
|
||||||
["ctrl+["] = "doc:move-to-previous-block-start",
|
["ctrl+["] = "doc:move-to-previous-block-start",
|
||||||
["ctrl+]"] = "doc:move-to-next-block-end",
|
["ctrl+]"] = "doc:move-to-next-block-end",
|
||||||
["home"] = "doc:move-to-start-of-indentation",
|
["home"] = "doc:move-to-start-of-line",
|
||||||
["end"] = "doc:move-to-end-of-line",
|
["end"] = "doc:move-to-end-of-line",
|
||||||
["ctrl+home"] = "doc:move-to-start-of-doc",
|
["ctrl+home"] = "doc:move-to-start-of-doc",
|
||||||
["ctrl+end"] = "doc:move-to-end-of-doc",
|
["ctrl+end"] = "doc:move-to-end-of-doc",
|
||||||
["pageup"] = "doc:move-to-previous-page",
|
["pageup"] = "doc:move-to-previous-page",
|
||||||
["pagedown"] = "doc:move-to-next-page",
|
["pagedown"] = "doc:move-to-next-page",
|
||||||
|
|
||||||
["shift+1lclick"] = "doc:select-to-cursor",
|
|
||||||
["ctrl+1lclick"] = "doc:split-cursor",
|
|
||||||
["1lclick"] = "doc:set-cursor",
|
|
||||||
["2lclick"] = "doc:set-cursor-word",
|
|
||||||
["3lclick"] = "doc:set-cursor-line",
|
|
||||||
["shift+left"] = "doc:select-to-previous-char",
|
["shift+left"] = "doc:select-to-previous-char",
|
||||||
["shift+right"] = "doc:select-to-next-char",
|
["shift+right"] = "doc:select-to-next-char",
|
||||||
["shift+up"] = "doc:select-to-previous-line",
|
["shift+up"] = "doc:select-to-previous-line",
|
||||||
|
@ -349,15 +199,12 @@ keymap.add_direct {
|
||||||
["ctrl+shift+right"] = "doc:select-to-next-word-end",
|
["ctrl+shift+right"] = "doc:select-to-next-word-end",
|
||||||
["ctrl+shift+["] = "doc:select-to-previous-block-start",
|
["ctrl+shift+["] = "doc:select-to-previous-block-start",
|
||||||
["ctrl+shift+]"] = "doc:select-to-next-block-end",
|
["ctrl+shift+]"] = "doc:select-to-next-block-end",
|
||||||
["shift+home"] = "doc:select-to-start-of-indentation",
|
["shift+home"] = "doc:select-to-start-of-line",
|
||||||
["shift+end"] = "doc:select-to-end-of-line",
|
["shift+end"] = "doc:select-to-end-of-line",
|
||||||
["ctrl+shift+home"] = "doc:select-to-start-of-doc",
|
["ctrl+shift+home"] = "doc:select-to-start-of-doc",
|
||||||
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
|
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
|
||||||
["shift+pageup"] = "doc:select-to-previous-page",
|
["shift+pageup"] = "doc:select-to-previous-page",
|
||||||
["shift+pagedown"] = "doc:select-to-next-page",
|
["shift+pagedown"] = "doc:select-to-next-page",
|
||||||
["ctrl+shift+up"] = "doc:create-cursor-previous-line",
|
|
||||||
["ctrl+shift+down"] = "doc:create-cursor-next-line"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return keymap
|
return keymap
|
||||||
|
|
||||||
|
|
|
@ -1,52 +1,16 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
|
||||||
local config = require "core.config"
|
|
||||||
local keymap = require "core.keymap"
|
|
||||||
local style = require "core.style"
|
local style = require "core.style"
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
|
|
||||||
|
|
||||||
local function lines(text)
|
|
||||||
if text == "" then return 0 end
|
|
||||||
local l = 1
|
|
||||||
for _ in string.gmatch(text, "\n") do
|
|
||||||
l = l + 1
|
|
||||||
end
|
|
||||||
return l
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local item_height_result = {}
|
|
||||||
|
|
||||||
|
|
||||||
local function get_item_height(item)
|
|
||||||
local h = item_height_result[item]
|
|
||||||
if not h then
|
|
||||||
h = {}
|
|
||||||
local l = 1 + lines(item.text) + lines(item.info or "")
|
|
||||||
h.normal = style.font:get_height() + style.padding.y
|
|
||||||
h.expanded = l * style.font:get_height() + style.padding.y
|
|
||||||
h.current = h.normal
|
|
||||||
h.target = h.current
|
|
||||||
item_height_result[item] = h
|
|
||||||
end
|
|
||||||
return h
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local LogView = View:extend()
|
local LogView = View:extend()
|
||||||
|
|
||||||
LogView.context = "session"
|
|
||||||
|
|
||||||
|
|
||||||
function LogView:new()
|
function LogView:new()
|
||||||
LogView.super.new(self)
|
LogView.super.new(self)
|
||||||
self.last_item = core.log_items[#core.log_items]
|
self.last_item = core.log_items[#core.log_items]
|
||||||
self.expanding = {}
|
|
||||||
self.scrollable = true
|
self.scrollable = true
|
||||||
self.yoffset = 0
|
self.yoffset = 0
|
||||||
|
|
||||||
core.status_view:show_message("i", style.text, "ctrl+click to copy entry")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,73 +19,6 @@ function LogView:get_name()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function is_expanded(item)
|
|
||||||
local item_height = get_item_height(item)
|
|
||||||
return item_height.target == item_height.expanded
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function LogView:expand_item(item)
|
|
||||||
item = get_item_height(item)
|
|
||||||
item.target = item.target == item.expanded and item.normal or item.expanded
|
|
||||||
table.insert(self.expanding, item)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function LogView:each_item()
|
|
||||||
local x, y = self:get_content_offset()
|
|
||||||
y = y + style.padding.y + self.yoffset
|
|
||||||
return coroutine.wrap(function()
|
|
||||||
for i = #core.log_items, 1, -1 do
|
|
||||||
local item = core.log_items[i]
|
|
||||||
local h = get_item_height(item).current
|
|
||||||
coroutine.yield(i, item, x, y, self.size.x, h)
|
|
||||||
y = y + h
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function LogView:get_scrollable_size()
|
|
||||||
local _, y_off = self:get_content_offset()
|
|
||||||
local last_y, last_h = 0, 0
|
|
||||||
for i, item, x, y, w, h in self:each_item() do
|
|
||||||
last_y, last_h = y, h
|
|
||||||
end
|
|
||||||
if not config.scroll_past_end then
|
|
||||||
return last_y + last_h - y_off + style.padding.y
|
|
||||||
end
|
|
||||||
return last_y + self.size.y - y_off
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function LogView:on_mouse_pressed(button, px, py, clicks)
|
|
||||||
if LogView.super.on_mouse_pressed(self, button, px, py, clicks) then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local index, selected
|
|
||||||
for i, item, x, y, w, h in self:each_item() do
|
|
||||||
if px >= x and py >= y and px < x + w and py < y + h then
|
|
||||||
index = i
|
|
||||||
selected = item
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if selected then
|
|
||||||
if keymap.modkeys["ctrl"] then
|
|
||||||
system.set_clipboard(core.get_log(selected))
|
|
||||||
core.status_view:show_message("i", style.text, "copied entry #"..index.." to clipboard.")
|
|
||||||
else
|
|
||||||
self:expand_item(selected)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
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
|
||||||
|
@ -130,15 +27,7 @@ function LogView:update()
|
||||||
self.yoffset = -(style.font:get_height() + style.padding.y)
|
self.yoffset = -(style.font:get_height() + style.padding.y)
|
||||||
end
|
end
|
||||||
|
|
||||||
local expanding = self.expanding[1]
|
self:move_towards("yoffset", 0)
|
||||||
if expanding then
|
|
||||||
self:move_towards(expanding, "current", expanding.target, nil, "logview")
|
|
||||||
if expanding.current == expanding.target then
|
|
||||||
table.remove(self.expanding, 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:move_towards("yoffset", 0, nil, "logview")
|
|
||||||
|
|
||||||
LogView.super.update(self)
|
LogView.super.update(self)
|
||||||
end
|
end
|
||||||
|
@ -146,70 +35,39 @@ end
|
||||||
|
|
||||||
local function draw_text_multiline(font, text, x, y, color)
|
local function draw_text_multiline(font, text, x, y, color)
|
||||||
local th = font:get_height()
|
local th = font:get_height()
|
||||||
local resx = x
|
local resx, resy = x, y
|
||||||
for line in text:gmatch("[^\n]+") do
|
for line in text:gmatch("[^\n]+") do
|
||||||
|
resy = y
|
||||||
resx = renderer.draw_text(style.font, line, x, y, color)
|
resx = renderer.draw_text(style.font, line, x, y, color)
|
||||||
y = y + th
|
y = y + th
|
||||||
end
|
end
|
||||||
return resx, y
|
return resx, resy
|
||||||
end
|
end
|
||||||
|
|
||||||
-- this is just to get a date string that's consistent
|
|
||||||
local datestr = os.date()
|
|
||||||
function LogView:draw()
|
function LogView:draw()
|
||||||
self:draw_background(style.background)
|
self:draw_background(style.background)
|
||||||
|
|
||||||
|
local ox, oy = self:get_content_offset()
|
||||||
local th = style.font:get_height()
|
local th = style.font:get_height()
|
||||||
local lh = th + style.padding.y -- for one line
|
local y = oy + style.padding.y + self.yoffset
|
||||||
local iw = math.max(
|
|
||||||
style.icon_font:get_width(style.log.ERROR.icon),
|
|
||||||
style.icon_font:get_width(style.log.INFO.icon)
|
|
||||||
)
|
|
||||||
|
|
||||||
local tw = style.font:get_width(datestr)
|
for i = #core.log_items, 1, -1 do
|
||||||
for _, item, x, y, w, h in self:each_item() do
|
local x = ox + style.padding.x
|
||||||
if y + h >= self.position.y and y <= self.position.y + self.size.y then
|
local item = core.log_items[i]
|
||||||
core.push_clip_rect(x, y, w, h)
|
local time = os.date(nil, item.time)
|
||||||
x = x + style.padding.x
|
x = renderer.draw_text(style.font, time, x, y, style.dim)
|
||||||
|
x = x + style.padding.x
|
||||||
x = common.draw_text(
|
local subx = x
|
||||||
style.icon_font,
|
x, y = draw_text_multiline(style.font, item.text, x, y, style.text)
|
||||||
style.log[item.level].color,
|
renderer.draw_text(style.font, " at " .. item.at, x, y, style.dim)
|
||||||
style.log[item.level].icon,
|
y = y + th
|
||||||
"center",
|
if item.info then
|
||||||
x, y, iw, lh
|
subx, y = draw_text_multiline(style.font, item.info, subx, y, style.dim)
|
||||||
)
|
y = y + th
|
||||||
x = x + style.padding.x
|
|
||||||
|
|
||||||
-- timestamps are always 15% of the width
|
|
||||||
local time = os.date(nil, item.time)
|
|
||||||
common.draw_text(style.font, style.dim, time, "left", x, y, tw, lh)
|
|
||||||
x = x + tw + style.padding.x
|
|
||||||
|
|
||||||
w = w - (x - self:get_content_offset())
|
|
||||||
|
|
||||||
if is_expanded(item) then
|
|
||||||
y = y + common.round(style.padding.y / 2)
|
|
||||||
_, y = draw_text_multiline(style.font, item.text, x, y, style.text)
|
|
||||||
|
|
||||||
local at = "at " .. common.home_encode(item.at)
|
|
||||||
_, y = common.draw_text(style.font, style.dim, at, "left", x, y, w, lh)
|
|
||||||
|
|
||||||
if item.info then
|
|
||||||
_, y = draw_text_multiline(style.font, item.info, x, y, style.dim)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local line, has_newline = string.match(item.text, "([^\n]+)(\n?)")
|
|
||||||
if has_newline ~= "" then
|
|
||||||
line = line .. " ..."
|
|
||||||
end
|
|
||||||
_, y = common.draw_text(style.font, style.text, line, "left", x, y, w, lh)
|
|
||||||
end
|
|
||||||
|
|
||||||
core.pop_clip_rect()
|
|
||||||
end
|
end
|
||||||
|
y = y + style.padding.y
|
||||||
end
|
end
|
||||||
LogView.super.draw_scrollbar(self)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
local modkeys = {}
|
|
||||||
|
|
||||||
modkeys.map = {
|
|
||||||
["left amiga"] = "cmd",
|
|
||||||
["right amiga"] = "cmd",
|
|
||||||
["control"] = "ctrl",
|
|
||||||
["left shift"] = "shift",
|
|
||||||
["right shift"] = "shift",
|
|
||||||
["left alt"] = "alt",
|
|
||||||
["right alt"] = "altgr",
|
|
||||||
}
|
|
||||||
|
|
||||||
modkeys.keys = { "cmd", "ctrl", "alt", "altgr", "shift" }
|
|
||||||
|
|
||||||
return modkeys
|
|
|
@ -11,19 +11,13 @@ local UNDERLINE_MARGIN = common.round(1 * SCALE)
|
||||||
|
|
||||||
local noop = function() end
|
local noop = function() end
|
||||||
|
|
||||||
---@class core.nagview : core.view
|
|
||||||
---@field super core.view
|
|
||||||
local NagView = View:extend()
|
local NagView = View:extend()
|
||||||
|
|
||||||
function NagView:new()
|
function NagView:new()
|
||||||
NagView.super.new(self)
|
NagView.super.new(self)
|
||||||
self.size.y = 0
|
self.size.y = 0
|
||||||
self.show_height = 0
|
|
||||||
self.force_focus = false
|
self.force_focus = false
|
||||||
self.queue = {}
|
self.queue = {}
|
||||||
self.scrollable = true
|
|
||||||
self.target_height = 0
|
|
||||||
self.on_mouse_pressed_root = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function NagView:get_title()
|
function NagView:get_title()
|
||||||
|
@ -52,20 +46,20 @@ function NagView:get_target_height()
|
||||||
return self.target_height + 2 * style.padding.y
|
return self.target_height + 2 * style.padding.y
|
||||||
end
|
end
|
||||||
|
|
||||||
function NagView:get_scrollable_size()
|
function NagView:update()
|
||||||
local w, h = system.get_window_size()
|
NagView.super.update(self)
|
||||||
if self.visible and self:get_target_height() > h then
|
|
||||||
self.size.y = h
|
if core.active_view == self and self.title then
|
||||||
return self:get_target_height()
|
self:move_towards(self.size, "y", self:get_target_height())
|
||||||
|
self:move_towards(self, "underline_progress", 1)
|
||||||
else
|
else
|
||||||
self.size.y = 0
|
self:move_towards(self.size, "y", 0)
|
||||||
end
|
end
|
||||||
return 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function NagView:dim_window_content()
|
function NagView:draw_overlay()
|
||||||
local ox, oy = self:get_content_offset()
|
local ox, oy = self:get_content_offset()
|
||||||
oy = oy + self.show_height
|
oy = oy + self.size.y
|
||||||
local w, h = core.root_view.size.x, core.root_view.size.y - oy
|
local w, h = core.root_view.size.x, core.root_view.size.y - oy
|
||||||
core.root_view:defer_draw(function()
|
core.root_view:defer_draw(function()
|
||||||
renderer.draw_rect(ox, oy, w, h, style.nagbar_dim)
|
renderer.draw_rect(ox, oy, w, h, style.nagbar_dim)
|
||||||
|
@ -87,7 +81,7 @@ function NagView:each_option()
|
||||||
bh = self:get_buttons_height()
|
bh = self:get_buttons_height()
|
||||||
ox,oy = self:get_content_offset()
|
ox,oy = self:get_content_offset()
|
||||||
ox = ox + self.size.x
|
ox = ox + self.size.x
|
||||||
oy = oy + self.show_height - bh - style.padding.y
|
oy = oy + self.size.y - bh - style.padding.y
|
||||||
|
|
||||||
for i = #self.options, 1, -1 do
|
for i = #self.options, 1, -1 do
|
||||||
opt = self.options[i]
|
opt = self.options[i]
|
||||||
|
@ -100,8 +94,6 @@ function NagView:each_option()
|
||||||
end
|
end
|
||||||
|
|
||||||
function NagView:on_mouse_moved(mx, my, ...)
|
function NagView:on_mouse_moved(mx, my, ...)
|
||||||
if not self.visible then return end
|
|
||||||
core.set_active_view(self)
|
|
||||||
NagView.super.on_mouse_moved(self, mx, my, ...)
|
NagView.super.on_mouse_moved(self, mx, my, ...)
|
||||||
for i, _, x,y,w,h in self:each_option() do
|
for i, _, x,y,w,h in self:each_option() do
|
||||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
if mx >= x and my >= y and mx < x + w and my < y + h then
|
||||||
|
@ -111,55 +103,18 @@ function NagView:on_mouse_moved(mx, my, ...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function register_mouse_pressed(self)
|
|
||||||
if self.on_mouse_pressed_root then return end
|
|
||||||
-- RootView is loaded locally to avoid NagView and RootView being
|
|
||||||
-- mutually recursive
|
|
||||||
local RootView = require "core.rootview"
|
|
||||||
self.on_mouse_pressed_root = RootView.on_mouse_pressed
|
|
||||||
local this = self
|
|
||||||
function RootView:on_mouse_pressed(button, x, y, clicks)
|
|
||||||
if
|
|
||||||
not this:on_mouse_pressed(button, x, y, clicks)
|
|
||||||
then
|
|
||||||
return this.on_mouse_pressed_root(self, button, x, y, clicks)
|
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.new_on_mouse_pressed_root = RootView.on_mouse_pressed
|
|
||||||
end
|
|
||||||
|
|
||||||
local function unregister_mouse_pressed(self)
|
|
||||||
local RootView = require "core.rootview"
|
|
||||||
if
|
|
||||||
self.on_mouse_pressed_root
|
|
||||||
and
|
|
||||||
-- just in case prevent overwriting what something else may
|
|
||||||
-- have overwrote after us, but after testing with various
|
|
||||||
-- plugins this doesn't seems to happen, but just in case
|
|
||||||
self.new_on_mouse_pressed_root == RootView.on_mouse_pressed
|
|
||||||
then
|
|
||||||
RootView.on_mouse_pressed = self.on_mouse_pressed_root
|
|
||||||
self.on_mouse_pressed_root = nil
|
|
||||||
self.new_on_mouse_pressed_root = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:on_mouse_pressed(button, mx, my, clicks)
|
function NagView:on_mouse_pressed(button, mx, my, clicks)
|
||||||
if not self.visible then return false end
|
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
|
||||||
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return true end
|
|
||||||
for i, _, x,y,w,h in self:each_option() do
|
for i, _, x,y,w,h in self:each_option() do
|
||||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
if mx >= x and my >= y and mx < x + w and my < y + h then
|
||||||
self:change_hovered(i)
|
self:change_hovered(i)
|
||||||
command.perform "dialog:select"
|
command.perform "dialog:select"
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function NagView:on_text_input(text)
|
function NagView:on_text_input(text)
|
||||||
if not self.visible then return end
|
|
||||||
if text:lower() == "y" then
|
if text:lower() == "y" then
|
||||||
command.perform "dialog:select-yes"
|
command.perform "dialog:select-yes"
|
||||||
elseif text:lower() == "n" then
|
elseif text:lower() == "n" then
|
||||||
|
@ -167,39 +122,20 @@ function NagView:on_text_input(text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function NagView:update()
|
|
||||||
if not self.visible and self.show_height <= 0 then return end
|
|
||||||
NagView.super.update(self)
|
|
||||||
|
|
||||||
if self.visible and core.active_view == self and self.title then
|
function NagView:draw()
|
||||||
self:move_towards(self, "show_height", self:get_target_height(), nil, "nagbar")
|
if self.size.y <= 0 or not self.title then return end
|
||||||
self:move_towards(self, "underline_progress", 1, nil, "nagbar")
|
|
||||||
else
|
|
||||||
self:move_towards(self, "show_height", 0, nil, "nagbar")
|
|
||||||
if self.show_height <= 0 then
|
|
||||||
self.title = nil
|
|
||||||
self.message = nil
|
|
||||||
self.options = nil
|
|
||||||
self.on_selected = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function draw_nagview_message(self)
|
self:draw_overlay()
|
||||||
self:dim_window_content()
|
self:draw_background(style.nagbar)
|
||||||
|
|
||||||
-- draw message's background
|
|
||||||
local ox, oy = self:get_content_offset()
|
local ox, oy = self:get_content_offset()
|
||||||
renderer.draw_rect(ox, oy, self.size.x, self.show_height, style.nagbar)
|
|
||||||
|
|
||||||
ox = ox + style.padding.x
|
ox = ox + style.padding.x
|
||||||
|
|
||||||
core.push_clip_rect(ox, oy, self.size.x, self.show_height)
|
|
||||||
|
|
||||||
-- if there are other items, show it
|
-- if there are other items, show it
|
||||||
if #self.queue > 0 then
|
if #self.queue > 0 then
|
||||||
local str = string.format("[%d]", #self.queue)
|
local str = string.format("[%d]", #self.queue)
|
||||||
ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.show_height)
|
ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.size.y)
|
||||||
ox = ox + style.padding.x
|
ox = ox + style.padding.x
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -232,17 +168,12 @@ local function draw_nagview_message(self)
|
||||||
|
|
||||||
common.draw_text(opt.font, style.nagbar_text, opt.text, "center", fx,fy,fw,fh)
|
common.draw_text(opt.font, style.nagbar_text, opt.text, "center", fx,fy,fw,fh)
|
||||||
end
|
end
|
||||||
|
|
||||||
self:draw_scrollbar()
|
|
||||||
|
|
||||||
core.pop_clip_rect()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function NagView:draw()
|
local function findindex(tbl, prop)
|
||||||
if (not self.visible and self.show_height <= 0) or not self.title then
|
for i, o in ipairs(tbl) do
|
||||||
return
|
if o[prop] then return i end
|
||||||
end
|
end
|
||||||
core.root_view:defer_draw(draw_nagview_message, self)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function NagView:get_message_height()
|
function NagView:get_message_height()
|
||||||
|
@ -253,31 +184,22 @@ function NagView:get_message_height()
|
||||||
return h
|
return h
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function NagView:next()
|
function NagView:next()
|
||||||
local opts = table.remove(self.queue, 1) or {}
|
local opts = table.remove(self.queue, 1) or {}
|
||||||
if opts.title and opts.message and opts.options then
|
self.title = opts.title
|
||||||
self.visible = true
|
self.message = opts.message and opts.message .. "\n"
|
||||||
self.title = opts.title
|
self.options = opts.options
|
||||||
self.message = opts.message and opts.message .. "\n"
|
self.on_selected = opts.on_selected
|
||||||
self.options = opts.options
|
if self.message and self.options then
|
||||||
self.on_selected = opts.on_selected
|
|
||||||
|
|
||||||
local message_height = self:get_message_height()
|
local message_height = self:get_message_height()
|
||||||
-- self.target_height is the nagview height needed to display the message and
|
-- self.target_height is the nagview height needed to display the message and
|
||||||
-- the buttons, excluding the top and bottom padding space.
|
-- the buttons, excluding the top and bottom padding space.
|
||||||
self.target_height = math.max(message_height, self:get_buttons_height())
|
self.target_height = math.max(message_height, self:get_buttons_height())
|
||||||
self:change_hovered(common.find_index(self.options, "default_yes"))
|
self:change_hovered(findindex(self.options, "default_yes"))
|
||||||
|
|
||||||
self.force_focus = true
|
|
||||||
core.set_active_view(self)
|
|
||||||
-- We add a hook to manage all the mouse_pressed events.
|
|
||||||
register_mouse_pressed(self)
|
|
||||||
else
|
|
||||||
self.force_focus = false
|
|
||||||
core.set_active_view(core.next_active_view or core.last_active_view)
|
|
||||||
self.visible = false
|
|
||||||
unregister_mouse_pressed(self)
|
|
||||||
end
|
end
|
||||||
|
self.force_focus = self.message ~= nil
|
||||||
|
core.set_active_view(self.message ~= nil and self or core.last_active_view)
|
||||||
end
|
end
|
||||||
|
|
||||||
function NagView:show(title, message, options, on_select)
|
function NagView:show(title, message, options, on_select)
|
||||||
|
@ -287,7 +209,39 @@ function NagView:show(title, message, options, on_select)
|
||||||
opts.options = assert(options, "No options")
|
opts.options = assert(options, "No options")
|
||||||
opts.on_selected = on_select or noop
|
opts.on_selected = on_select or noop
|
||||||
table.insert(self.queue, opts)
|
table.insert(self.queue, opts)
|
||||||
self:next()
|
if #self.queue > 0 and not self.title then self:next() end
|
||||||
end
|
end
|
||||||
|
|
||||||
return NagView
|
command.add(NagView, {
|
||||||
|
["dialog:previous-entry"] = function()
|
||||||
|
local v = core.active_view
|
||||||
|
local hover = v.hovered_item or 1
|
||||||
|
v:change_hovered(hover == 1 and #v.options or hover - 1)
|
||||||
|
end,
|
||||||
|
["dialog:next-entry"] = function()
|
||||||
|
local v = core.active_view
|
||||||
|
local hover = v.hovered_item or 1
|
||||||
|
v:change_hovered(hover == #v.options and 1 or hover + 1)
|
||||||
|
end,
|
||||||
|
["dialog:select-yes"] = function()
|
||||||
|
local v = core.active_view
|
||||||
|
if v ~= core.nag_view then return end
|
||||||
|
v:change_hovered(findindex(v.options, "default_yes"))
|
||||||
|
command.perform "dialog:select"
|
||||||
|
end,
|
||||||
|
["dialog:select-no"] = function()
|
||||||
|
local v = core.active_view
|
||||||
|
if v ~= core.nag_view then return end
|
||||||
|
v:change_hovered(findindex(v.options, "default_no"))
|
||||||
|
command.perform "dialog:select"
|
||||||
|
end,
|
||||||
|
["dialog:select"] = function()
|
||||||
|
local v = core.active_view
|
||||||
|
if v.hovered_item then
|
||||||
|
v.on_selected(v.options[v.hovered_item])
|
||||||
|
v:next()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
return NagView
|
|
@ -1,759 +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"
|
|
||||||
local EmptyView = require "core.emptyview"
|
|
||||||
local View = require "core.view"
|
|
||||||
|
|
||||||
---@class core.node : core.object
|
|
||||||
local Node = Object:extend()
|
|
||||||
|
|
||||||
function Node:new(type)
|
|
||||||
self.type = type or "leaf"
|
|
||||||
self.position = { x = 0, y = 0 }
|
|
||||||
self.size = { x = 0, y = 0 }
|
|
||||||
self.views = {}
|
|
||||||
self.divider = 0.5
|
|
||||||
if self.type == "leaf" then
|
|
||||||
self:add_view(EmptyView())
|
|
||||||
end
|
|
||||||
self.hovered = {x = -1, y = -1 }
|
|
||||||
self.hovered_close = 0
|
|
||||||
self.tab_shift = 0
|
|
||||||
self.tab_offset = 1
|
|
||||||
self.tab_width = style.tab_width
|
|
||||||
self.move_towards = View.move_towards
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:propagate(fn, ...)
|
|
||||||
self.a[fn](self.a, ...)
|
|
||||||
self.b[fn](self.b, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:on_mouse_moved(x, y, ...)
|
|
||||||
if self.type == "leaf" then
|
|
||||||
self.hovered.x, self.hovered.y = x, y
|
|
||||||
self.active_view:on_mouse_moved(x, y, ...)
|
|
||||||
else
|
|
||||||
self:propagate("on_mouse_moved", x, y, ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:on_mouse_released(...)
|
|
||||||
if self.type == "leaf" then
|
|
||||||
self.active_view:on_mouse_released(...)
|
|
||||||
else
|
|
||||||
self:propagate("on_mouse_released", ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:on_mouse_left()
|
|
||||||
if self.type == "leaf" then
|
|
||||||
self.active_view:on_mouse_left()
|
|
||||||
else
|
|
||||||
self:propagate("on_mouse_left")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:consume(node)
|
|
||||||
for k, _ in pairs(self) do self[k] = nil end
|
|
||||||
for k, v in pairs(node) do self[k] = v end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local type_map = { up="vsplit", down="vsplit", left="hsplit", right="hsplit" }
|
|
||||||
|
|
||||||
-- The "locked" argument below should be in the form {x = <boolean>, y = <boolean>}
|
|
||||||
-- and it indicates if the node want to have a fixed size along the axis where the
|
|
||||||
-- boolean is true. If not it will be expanded to take all the available space.
|
|
||||||
-- The "resizable" flag indicates if, along the "locked" axis the node can be resized
|
|
||||||
-- by the user. If the node is marked as resizable their view should provide a
|
|
||||||
-- set_target_size method.
|
|
||||||
function Node:split(dir, view, locked, resizable)
|
|
||||||
assert(self.type == "leaf", "Tried to split non-leaf node")
|
|
||||||
local node_type = assert(type_map[dir], "Invalid direction")
|
|
||||||
local last_active = core.active_view
|
|
||||||
local child = Node()
|
|
||||||
child:consume(self)
|
|
||||||
self:consume(Node(node_type))
|
|
||||||
self.a = child
|
|
||||||
self.b = Node()
|
|
||||||
if view then self.b:add_view(view) end
|
|
||||||
if locked then
|
|
||||||
assert(type(locked) == 'table')
|
|
||||||
self.b.locked = locked
|
|
||||||
self.b.resizable = resizable or false
|
|
||||||
core.set_active_view(last_active)
|
|
||||||
end
|
|
||||||
if dir == "up" or dir == "left" then
|
|
||||||
self.a, self.b = self.b, self.a
|
|
||||||
return self.a
|
|
||||||
end
|
|
||||||
return self.b
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:remove_view(root, view)
|
|
||||||
if #self.views > 1 then
|
|
||||||
local idx = self:get_view_idx(view)
|
|
||||||
if idx < self.tab_offset then
|
|
||||||
self.tab_offset = self.tab_offset - 1
|
|
||||||
end
|
|
||||||
table.remove(self.views, idx)
|
|
||||||
if self.active_view == view then
|
|
||||||
self:set_active_view(self.views[idx] or self.views[#self.views])
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local parent = self:get_parent_node(root)
|
|
||||||
local is_a = (parent.a == self)
|
|
||||||
local other = parent[is_a and "b" or "a"]
|
|
||||||
local locked_size_x, locked_size_y = other:get_locked_size()
|
|
||||||
local locked_size
|
|
||||||
if parent.type == "hsplit" then
|
|
||||||
locked_size = locked_size_x
|
|
||||||
else
|
|
||||||
locked_size = locked_size_y
|
|
||||||
end
|
|
||||||
local next_primary
|
|
||||||
if self.is_primary_node then
|
|
||||||
next_primary = core.root_view:select_next_primary_node()
|
|
||||||
end
|
|
||||||
if locked_size or (self.is_primary_node and not next_primary) then
|
|
||||||
self.views = {}
|
|
||||||
self:add_view(EmptyView())
|
|
||||||
else
|
|
||||||
if other == next_primary then
|
|
||||||
next_primary = parent
|
|
||||||
end
|
|
||||||
parent:consume(other)
|
|
||||||
local p = parent
|
|
||||||
while p.type ~= "leaf" do
|
|
||||||
p = p[is_a and "a" or "b"]
|
|
||||||
end
|
|
||||||
p:set_active_view(p.active_view)
|
|
||||||
if self.is_primary_node then
|
|
||||||
next_primary.is_primary_node = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
core.last_active_view = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:close_view(root, view)
|
|
||||||
local do_close = function()
|
|
||||||
self:remove_view(root, view)
|
|
||||||
end
|
|
||||||
view:try_close(do_close)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:close_active_view(root)
|
|
||||||
self:close_view(root, self.active_view)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:add_view(view, idx)
|
|
||||||
assert(self.type == "leaf", "Tried to add view to non-leaf node")
|
|
||||||
assert(not self.locked, "Tried to add view to locked node")
|
|
||||||
if self.views[1] and self.views[1]:is(EmptyView) then
|
|
||||||
table.remove(self.views)
|
|
||||||
end
|
|
||||||
table.insert(self.views, idx or (#self.views + 1), view)
|
|
||||||
self:set_active_view(view)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:set_active_view(view)
|
|
||||||
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
|
|
||||||
local last_active_view = self.active_view
|
|
||||||
self.active_view = view
|
|
||||||
core.set_active_view(view)
|
|
||||||
if last_active_view and last_active_view ~= view then
|
|
||||||
last_active_view:on_mouse_left()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_view_idx(view)
|
|
||||||
for i, v in ipairs(self.views) do
|
|
||||||
if v == view then return i end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_node_for_view(view)
|
|
||||||
for _, v in ipairs(self.views) do
|
|
||||||
if v == view then return self end
|
|
||||||
end
|
|
||||||
if self.type ~= "leaf" then
|
|
||||||
return self.a:get_node_for_view(view) or self.b:get_node_for_view(view)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_parent_node(root)
|
|
||||||
if root.a == self or root.b == self then
|
|
||||||
return root
|
|
||||||
elseif root.type ~= "leaf" then
|
|
||||||
return self:get_parent_node(root.a) or self:get_parent_node(root.b)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_children(t)
|
|
||||||
t = t or {}
|
|
||||||
for _, view in ipairs(self.views) do
|
|
||||||
table.insert(t, view)
|
|
||||||
end
|
|
||||||
if self.a then self.a:get_children(t) end
|
|
||||||
if self.b then self.b:get_children(t) end
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- return the width including the padding space and separately
|
|
||||||
-- the padding space itself
|
|
||||||
local function get_scroll_button_width()
|
|
||||||
local w = style.icon_font:get_width(">")
|
|
||||||
local pad = w
|
|
||||||
return w + 2 * pad, pad
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_divider_overlapping_point(px, py)
|
|
||||||
if self.type ~= "leaf" then
|
|
||||||
local axis = self.type == "hsplit" and "x" or "y"
|
|
||||||
if self.a:is_resizable(axis) and self.b:is_resizable(axis) then
|
|
||||||
local p = 6
|
|
||||||
local x, y, w, h = self:get_divider_rect()
|
|
||||||
x, y = x - p, y - p
|
|
||||||
w, h = w + p * 2, h + p * 2
|
|
||||||
if px > x and py > y and px < x + w and py < y + h then
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return self.a:get_divider_overlapping_point(px, py)
|
|
||||||
or self.b:get_divider_overlapping_point(px, py)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_visible_tabs_number()
|
|
||||||
return math.min(#self.views - self.tab_offset + 1, config.max_tabs)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_tab_overlapping_point(px, py)
|
|
||||||
if not self:should_show_tabs() then return nil end
|
|
||||||
local tabs_number = self:get_visible_tabs_number()
|
|
||||||
local x1, y1, w, h = self:get_tab_rect(self.tab_offset)
|
|
||||||
local x2, y2 = self:get_tab_rect(self.tab_offset + tabs_number)
|
|
||||||
if px >= x1 and py >= y1 and px < x2 and py < y1 + h then
|
|
||||||
return math.floor((px - x1) / w) + self.tab_offset
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:should_show_tabs()
|
|
||||||
if self.locked then return false end
|
|
||||||
local dn = core.root_view.dragged_node
|
|
||||||
if #self.views > 1
|
|
||||||
or (dn and dn.dragging) then -- show tabs while dragging
|
|
||||||
return true
|
|
||||||
elseif config.always_show_tabs then
|
|
||||||
return not self.views[1]:is(EmptyView)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function close_button_location(x, w)
|
|
||||||
local cw = style.icon_font:get_width("C")
|
|
||||||
local pad = style.padding.x / 2
|
|
||||||
return x + w - cw - pad, cw, pad
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_scroll_button_index(px, py)
|
|
||||||
if #self.views == 1 then return end
|
|
||||||
for i = 1, 2 do
|
|
||||||
local x, y, w, h = self:get_scroll_button_rect(i)
|
|
||||||
if px >= x and px < x + w and py >= y and py < y + h then
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:tab_hovered_update(px, py)
|
|
||||||
local tab_index = self:get_tab_overlapping_point(px, py)
|
|
||||||
self.hovered_tab = tab_index
|
|
||||||
self.hovered_close = 0
|
|
||||||
self.hovered_scroll_button = 0
|
|
||||||
if tab_index then
|
|
||||||
local x, y, w, h = self:get_tab_rect(tab_index)
|
|
||||||
local cx, cw = close_button_location(x, w)
|
|
||||||
if px >= cx and px < cx + cw and py >= y and py < y + h and config.tab_close_button then
|
|
||||||
self.hovered_close = tab_index
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.hovered_scroll_button = self:get_scroll_button_index(px, py) or 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_child_overlapping_point(x, y)
|
|
||||||
local child
|
|
||||||
if self.type == "leaf" then
|
|
||||||
return self
|
|
||||||
elseif self.type == "hsplit" then
|
|
||||||
child = (x < self.b.position.x) and self.a or self.b
|
|
||||||
elseif self.type == "vsplit" then
|
|
||||||
child = (y < self.b.position.y) and self.a or self.b
|
|
||||||
end
|
|
||||||
return child:get_child_overlapping_point(x, y)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_scroll_button_rect(index)
|
|
||||||
local w, pad = get_scroll_button_width()
|
|
||||||
local h = style.font:get_height() + style.padding.y * 2
|
|
||||||
local x = self.position.x + (index == 1 and 0 or self.size.x - w)
|
|
||||||
return x, self.position.y, w, h, pad
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_tab_rect(idx)
|
|
||||||
local sbw = get_scroll_button_width()
|
|
||||||
local maxw = self.size.x - 2 * sbw
|
|
||||||
local x0 = self.position.x + sbw
|
|
||||||
local x1 = x0 + common.clamp(self.tab_width * (idx - 1) - self.tab_shift, 0, maxw)
|
|
||||||
local x2 = x0 + common.clamp(self.tab_width * idx - self.tab_shift, 0, maxw)
|
|
||||||
local h = style.font:get_height() + style.padding.y * 2
|
|
||||||
return x1, self.position.y, x2 - x1, h
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_divider_rect()
|
|
||||||
local x, y = self.position.x, self.position.y
|
|
||||||
if self.type == "hsplit" then
|
|
||||||
return x + self.a.size.x, y, style.divider_size, self.size.y
|
|
||||||
elseif self.type == "vsplit" then
|
|
||||||
return x, y + self.a.size.y, self.size.x, style.divider_size
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- Return two values for x and y axis and each of them is either falsy or a number.
|
|
||||||
-- A falsy value indicate no fixed size along the corresponding direction.
|
|
||||||
function Node:get_locked_size()
|
|
||||||
if self.type == "leaf" then
|
|
||||||
if self.locked then
|
|
||||||
local size = self.active_view.size
|
|
||||||
-- The values below should be either a falsy value or a number
|
|
||||||
local sx = (self.locked and self.locked.x) and size.x
|
|
||||||
local sy = (self.locked and self.locked.y) and size.y
|
|
||||||
return sx, sy
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local x1, y1 = self.a:get_locked_size()
|
|
||||||
local x2, y2 = self.b:get_locked_size()
|
|
||||||
-- The values below should be either a falsy value or a number
|
|
||||||
local sx, sy
|
|
||||||
if self.type == 'hsplit' then
|
|
||||||
if x1 and x2 then
|
|
||||||
local dsx = (x1 < 1 or x2 < 1) and 0 or style.divider_size
|
|
||||||
sx = x1 + x2 + dsx
|
|
||||||
end
|
|
||||||
sy = y1 or y2
|
|
||||||
else
|
|
||||||
if y1 and y2 then
|
|
||||||
local dsy = (y1 < 1 or y2 < 1) and 0 or style.divider_size
|
|
||||||
sy = y1 + y2 + dsy
|
|
||||||
end
|
|
||||||
sx = x1 or x2
|
|
||||||
end
|
|
||||||
return sx, sy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node.copy_position_and_size(dst, src)
|
|
||||||
dst.position.x, dst.position.y = src.position.x, src.position.y
|
|
||||||
dst.size.x, dst.size.y = src.size.x, src.size.y
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- calculating the sizes is the same for hsplits and vsplits, except the x/y
|
|
||||||
-- axis are swapped; this function lets us use the same code for both
|
|
||||||
local function calc_split_sizes(self, x, y, x1, x2, y1, y2)
|
|
||||||
local ds = ((x1 and x1 < 1) or (x2 and x2 < 1)) and 0 or style.divider_size
|
|
||||||
local n = x1 and x1 + ds or (x2 and self.size[x] - x2 or math.floor(self.size[x] * self.divider))
|
|
||||||
self.a.position[x] = self.position[x]
|
|
||||||
self.a.position[y] = self.position[y]
|
|
||||||
self.a.size[x] = n - ds
|
|
||||||
self.a.size[y] = self.size[y]
|
|
||||||
self.b.position[x] = self.position[x] + n
|
|
||||||
self.b.position[y] = self.position[y]
|
|
||||||
self.b.size[x] = self.size[x] - n
|
|
||||||
self.b.size[y] = self.size[y]
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:update_layout()
|
|
||||||
if self.type == "leaf" then
|
|
||||||
local av = self.active_view
|
|
||||||
if self:should_show_tabs() then
|
|
||||||
local _, _, _, th = self:get_tab_rect(1)
|
|
||||||
av.position.x, av.position.y = self.position.x, self.position.y + th
|
|
||||||
av.size.x, av.size.y = self.size.x, self.size.y - th
|
|
||||||
else
|
|
||||||
Node.copy_position_and_size(av, self)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local x1, y1 = self.a:get_locked_size()
|
|
||||||
local x2, y2 = self.b:get_locked_size()
|
|
||||||
if self.type == "hsplit" then
|
|
||||||
calc_split_sizes(self, "x", "y", x1, x2)
|
|
||||||
elseif self.type == "vsplit" then
|
|
||||||
calc_split_sizes(self, "y", "x", y1, y2)
|
|
||||||
end
|
|
||||||
self.a:update_layout()
|
|
||||||
self.b:update_layout()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:scroll_tabs_to_visible()
|
|
||||||
local index = self:get_view_idx(self.active_view)
|
|
||||||
if index then
|
|
||||||
local tabs_number = self:get_visible_tabs_number()
|
|
||||||
if self.tab_offset > index then
|
|
||||||
self.tab_offset = index
|
|
||||||
elseif self.tab_offset + tabs_number - 1 < index then
|
|
||||||
self.tab_offset = index - tabs_number + 1
|
|
||||||
elseif tabs_number < config.max_tabs and self.tab_offset > 1 then
|
|
||||||
self.tab_offset = #self.views - config.max_tabs + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:scroll_tabs(dir)
|
|
||||||
local view_index = self:get_view_idx(self.active_view)
|
|
||||||
if dir == 1 then
|
|
||||||
if self.tab_offset > 1 then
|
|
||||||
self.tab_offset = self.tab_offset - 1
|
|
||||||
local last_index = self.tab_offset + self:get_visible_tabs_number() - 1
|
|
||||||
if view_index > last_index then
|
|
||||||
self:set_active_view(self.views[last_index])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif dir == 2 then
|
|
||||||
local tabs_number = self:get_visible_tabs_number()
|
|
||||||
if self.tab_offset + tabs_number - 1 < #self.views then
|
|
||||||
self.tab_offset = self.tab_offset + 1
|
|
||||||
local view_index = self:get_view_idx(self.active_view)
|
|
||||||
if view_index < self.tab_offset then
|
|
||||||
self:set_active_view(self.views[self.tab_offset])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:target_tab_width()
|
|
||||||
local n = self:get_visible_tabs_number()
|
|
||||||
local w = self.size.x - get_scroll_button_width() * 2
|
|
||||||
return common.clamp(style.tab_width, w / config.max_tabs, w / n)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:update()
|
|
||||||
if self.type == "leaf" then
|
|
||||||
self:scroll_tabs_to_visible()
|
|
||||||
for _, view in ipairs(self.views) do
|
|
||||||
view:update()
|
|
||||||
end
|
|
||||||
self:tab_hovered_update(self.hovered.x, self.hovered.y)
|
|
||||||
local tab_width = self:target_tab_width()
|
|
||||||
self:move_towards("tab_shift", tab_width * (self.tab_offset - 1), nil, "tabs")
|
|
||||||
self:move_towards("tab_width", tab_width, nil, "tabs")
|
|
||||||
else
|
|
||||||
self.a:update()
|
|
||||||
self.b:update()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:draw_tab_title(view, font, is_active, is_hovered, x, y, w, h)
|
|
||||||
local text = view and view:get_name() or ""
|
|
||||||
local dots_width = font:get_width("…")
|
|
||||||
local align = "center"
|
|
||||||
if font:get_width(text) > w then
|
|
||||||
align = "left"
|
|
||||||
for i = 1, #text do
|
|
||||||
local reduced_text = text:sub(1, #text - i)
|
|
||||||
if font:get_width(reduced_text) + dots_width <= w then
|
|
||||||
text = reduced_text .. "…"
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local color = style.dim
|
|
||||||
if is_active then color = style.text end
|
|
||||||
if is_hovered then color = style.text end
|
|
||||||
common.draw_text(font, color, text, align, x, y, w, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:draw_tab_borders(view, is_active, is_hovered, x, y, w, h, standalone)
|
|
||||||
-- Tabs deviders
|
|
||||||
local ds = style.divider_size
|
|
||||||
local color = style.dim
|
|
||||||
local padding_y = style.padding.y
|
|
||||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y*2, style.dim)
|
|
||||||
if standalone then
|
|
||||||
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
|
|
||||||
end
|
|
||||||
-- Full border
|
|
||||||
if is_active then
|
|
||||||
color = style.text
|
|
||||||
renderer.draw_rect(x, y, w, h, style.background)
|
|
||||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
|
||||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
|
||||||
end
|
|
||||||
return x + ds, y, w - ds*2, h
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:draw_tab(view, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
|
||||||
x, y, w, h = self:draw_tab_borders(view, is_active, is_hovered, x, y, w, h, standalone)
|
|
||||||
-- Close button
|
|
||||||
local cx, cw, cpad = close_button_location(x, w)
|
|
||||||
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
|
|
||||||
if show_close_button then
|
|
||||||
local close_style = is_close_hovered and style.text or style.dim
|
|
||||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, cw, h)
|
|
||||||
end
|
|
||||||
-- Title
|
|
||||||
x = x + cpad
|
|
||||||
w = cx - x
|
|
||||||
core.push_clip_rect(x, y, w, h)
|
|
||||||
self:draw_tab_title(view, style.font, is_active, is_hovered, x, y, w, h)
|
|
||||||
core.pop_clip_rect()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:draw_tabs()
|
|
||||||
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
|
||||||
local ds = style.divider_size
|
|
||||||
local dots_width = style.font:get_width("…")
|
|
||||||
core.push_clip_rect(x, y, self.size.x, h)
|
|
||||||
renderer.draw_rect(x, y, self.size.x, h, style.background2)
|
|
||||||
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
|
|
||||||
|
|
||||||
if self.tab_offset > 1 then
|
|
||||||
local button_style = self.hovered_scroll_button == 1 and style.text or style.dim
|
|
||||||
common.draw_text(style.icon_font, button_style, "<", nil, x + scroll_padding, y, 0, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
local tabs_number = self:get_visible_tabs_number()
|
|
||||||
if #self.views > self.tab_offset + tabs_number - 1 then
|
|
||||||
local xrb, yrb, wrb = self:get_scroll_button_rect(2)
|
|
||||||
local button_style = self.hovered_scroll_button == 2 and style.text or style.dim
|
|
||||||
common.draw_text(style.icon_font, button_style, ">", nil, xrb + scroll_padding, yrb, 0, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
|
||||||
local view = self.views[i]
|
|
||||||
local x, y, w, h = self:get_tab_rect(i)
|
|
||||||
self:draw_tab(view, view == self.active_view,
|
|
||||||
i == self.hovered_tab, i == self.hovered_close,
|
|
||||||
x, y, w, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
core.pop_clip_rect()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:draw()
|
|
||||||
if self.type == "leaf" then
|
|
||||||
if self:should_show_tabs() then
|
|
||||||
self:draw_tabs()
|
|
||||||
end
|
|
||||||
local pos, size = self.active_view.position, self.active_view.size
|
|
||||||
core.push_clip_rect(pos.x, pos.y, size.x, size.y)
|
|
||||||
self.active_view:draw()
|
|
||||||
core.pop_clip_rect()
|
|
||||||
else
|
|
||||||
local x, y, w, h = self:get_divider_rect()
|
|
||||||
renderer.draw_rect(x, y, w, h, style.divider)
|
|
||||||
self:propagate("draw")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:is_empty()
|
|
||||||
if self.type == "leaf" then
|
|
||||||
return #self.views == 0 or (#self.views == 1 and self.views[1]:is(EmptyView))
|
|
||||||
else
|
|
||||||
return self.a:is_empty() and self.b:is_empty()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:close_all_docviews(keep_active)
|
|
||||||
local node_active_view = self.active_view
|
|
||||||
local lost_active_view = false
|
|
||||||
if self.type == "leaf" then
|
|
||||||
local i = 1
|
|
||||||
while i <= #self.views do
|
|
||||||
local view = self.views[i]
|
|
||||||
if view.context == "session" and (not keep_active or view ~= self.active_view) then
|
|
||||||
table.remove(self.views, i)
|
|
||||||
if view == node_active_view then
|
|
||||||
lost_active_view = true
|
|
||||||
end
|
|
||||||
else
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.tab_offset = 1
|
|
||||||
if #self.views == 0 and self.is_primary_node then
|
|
||||||
-- if we are not the primary view and we had the active view it doesn't
|
|
||||||
-- matter to reattribute the active view because, within the close_all_docviews
|
|
||||||
-- top call, the primary node will take the active view anyway.
|
|
||||||
-- Set the empty view and takes the active view.
|
|
||||||
self:add_view(EmptyView())
|
|
||||||
elseif #self.views > 0 and lost_active_view then
|
|
||||||
-- In practice we never get there but if a view remain we need
|
|
||||||
-- to reset the Node's active view.
|
|
||||||
self:set_active_view(self.views[1])
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.a:close_all_docviews(keep_active)
|
|
||||||
self.b:close_all_docviews(keep_active)
|
|
||||||
if self.a:is_empty() and not self.a.is_primary_node then
|
|
||||||
self:consume(self.b)
|
|
||||||
elseif self.b:is_empty() and not self.b.is_primary_node then
|
|
||||||
self:consume(self.a)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Returns true for nodes that accept either "proportional" resizes (based on the
|
|
||||||
-- node.divider) or "locked" resizable nodes (along the resize axis).
|
|
||||||
function Node:is_resizable(axis)
|
|
||||||
if self.type == 'leaf' then
|
|
||||||
return not self.locked or not self.locked[axis] or self.resizable
|
|
||||||
else
|
|
||||||
local a_resizable = self.a:is_resizable(axis)
|
|
||||||
local b_resizable = self.b:is_resizable(axis)
|
|
||||||
return a_resizable and b_resizable
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- Return true iff it is a locked pane along the rezise axis and is
|
|
||||||
-- declared "resizable".
|
|
||||||
function Node:is_locked_resizable(axis)
|
|
||||||
return self.locked and self.locked[axis] and self.resizable
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:resize(axis, value)
|
|
||||||
-- the application works fine with non-integer values but to have pixel-perfect
|
|
||||||
-- placements of view elements, like the scrollbar, we round the value to be
|
|
||||||
-- an integer.
|
|
||||||
value = math.floor(value)
|
|
||||||
if self.type == 'leaf' then
|
|
||||||
-- If it is not locked we don't accept the
|
|
||||||
-- resize operation here because for proportional panes the resize is
|
|
||||||
-- done using the "divider" value of the parent node.
|
|
||||||
if self:is_locked_resizable(axis) then
|
|
||||||
return self.active_view:set_target_size(axis, value)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if self.type == (axis == "x" and "hsplit" or "vsplit") then
|
|
||||||
-- we are resizing a node that is splitted along the resize axis
|
|
||||||
if self.a:is_locked_resizable(axis) and self.b:is_locked_resizable(axis) then
|
|
||||||
local rem_value = value - self.a.size[axis]
|
|
||||||
if rem_value >= 0 then
|
|
||||||
return self.b.active_view:set_target_size(axis, rem_value)
|
|
||||||
else
|
|
||||||
self.b.active_view:set_target_size(axis, 0)
|
|
||||||
return self.a.active_view:set_target_size(axis, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- we are resizing a node that is splitted along the axis perpendicular
|
|
||||||
-- to the resize axis
|
|
||||||
local a_resizable = self.a:is_resizable(axis)
|
|
||||||
local b_resizable = self.b:is_resizable(axis)
|
|
||||||
if a_resizable and b_resizable then
|
|
||||||
self.a:resize(axis, value)
|
|
||||||
self.b:resize(axis, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_split_type(mouse_x, mouse_y)
|
|
||||||
local x, y = self.position.x, self.position.y
|
|
||||||
local w, h = self.size.x, self.size.y
|
|
||||||
local _, _, _, tab_h = self:get_scroll_button_rect(1)
|
|
||||||
y = y + tab_h
|
|
||||||
h = h - tab_h
|
|
||||||
|
|
||||||
local local_mouse_x = mouse_x - x
|
|
||||||
local local_mouse_y = mouse_y - y
|
|
||||||
|
|
||||||
if local_mouse_y < 0 then
|
|
||||||
return "tab"
|
|
||||||
else
|
|
||||||
local left_pct = local_mouse_x * 100 / w
|
|
||||||
local top_pct = local_mouse_y * 100 / h
|
|
||||||
if left_pct <= 30 then
|
|
||||||
return "left"
|
|
||||||
elseif left_pct >= 70 then
|
|
||||||
return "right"
|
|
||||||
elseif top_pct <= 30 then
|
|
||||||
return "up"
|
|
||||||
elseif top_pct >= 70 then
|
|
||||||
return "down"
|
|
||||||
end
|
|
||||||
return "middle"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Node:get_drag_overlay_tab_position(x, y, dragged_node, dragged_index)
|
|
||||||
local tab_index = self:get_tab_overlapping_point(x, y)
|
|
||||||
if not tab_index then
|
|
||||||
local first_tab_x = self:get_tab_rect(1)
|
|
||||||
if x < first_tab_x then
|
|
||||||
-- mouse before first visible tab
|
|
||||||
tab_index = self.tab_offset or 1
|
|
||||||
else
|
|
||||||
-- mouse after last visible tab
|
|
||||||
tab_index = self:get_visible_tabs_number() + (self.tab_offset - 1 or 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local tab_x, tab_y, tab_w, tab_h = self:get_tab_rect(tab_index)
|
|
||||||
if x > tab_x + tab_w / 2 and tab_index <= #self.views then
|
|
||||||
-- use next tab
|
|
||||||
tab_x = tab_x + tab_w
|
|
||||||
tab_index = tab_index + 1
|
|
||||||
end
|
|
||||||
if self == dragged_node and dragged_index and tab_index > dragged_index then
|
|
||||||
-- the tab we are moving is counted in tab_index
|
|
||||||
tab_index = tab_index - 1
|
|
||||||
tab_x = tab_x - tab_w
|
|
||||||
end
|
|
||||||
return tab_index, tab_x, tab_y, tab_w, tab_h
|
|
||||||
end
|
|
||||||
|
|
||||||
return Node
|
|
|
@ -1,12 +1,11 @@
|
||||||
---@class core.object
|
|
||||||
---@field super core.object
|
|
||||||
local Object = {}
|
local Object = {}
|
||||||
Object.__index = Object
|
Object.__index = Object
|
||||||
|
|
||||||
---Can be overrided by child objects to implement a constructor.
|
|
||||||
function Object:new() end
|
|
||||||
|
|
||||||
---@return core.object
|
function Object:new()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Object:extend()
|
function Object:extend()
|
||||||
local cls = {}
|
local cls = {}
|
||||||
for k, v in pairs(self) do
|
for k, v in pairs(self) do
|
||||||
|
@ -20,17 +19,19 @@ function Object:extend()
|
||||||
return cls
|
return cls
|
||||||
end
|
end
|
||||||
|
|
||||||
---Check if the object is strictly of the given type.
|
|
||||||
---@param T any
|
function Object:implement(...)
|
||||||
---@return boolean
|
for _, cls in pairs({...}) do
|
||||||
function Object:is(T)
|
for k, v in pairs(cls) do
|
||||||
return getmetatable(self) == T
|
if self[k] == nil and type(v) == "function" then
|
||||||
|
self[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Check if the object inherits from the given type.
|
|
||||||
---@param T any
|
function Object:is(T)
|
||||||
---@return boolean
|
|
||||||
function Object:extends(T)
|
|
||||||
local mt = getmetatable(self)
|
local mt = getmetatable(self)
|
||||||
while mt do
|
while mt do
|
||||||
if mt == T then
|
if mt == T then
|
||||||
|
@ -41,14 +42,12 @@ function Object:extends(T)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
---Metamethod to get a string representation of an object.
|
|
||||||
---@return string
|
|
||||||
function Object:__tostring()
|
function Object:__tostring()
|
||||||
return "Object"
|
return "Object"
|
||||||
end
|
end
|
||||||
|
|
||||||
---Methamethod to allow using the object call as a constructor.
|
|
||||||
---@return core.object
|
|
||||||
function Object:__call(...)
|
function Object:__call(...)
|
||||||
local obj = setmetatable({}, self)
|
local obj = setmetatable({}, self)
|
||||||
obj:new(...)
|
obj:new(...)
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
-- So that in addition to regex.gsub(pattern, string), we can also do
|
|
||||||
-- pattern:gsub(string).
|
|
||||||
regex.__index = function(table, key) return regex[key]; end
|
|
||||||
|
|
||||||
regex.match = function(pattern_string, string, offset, options)
|
|
||||||
local pattern = type(pattern_string) == "table" and
|
|
||||||
pattern_string or regex.compile(pattern_string)
|
|
||||||
local res = { regex.cmatch(pattern, string, offset or 1, options or 0) }
|
|
||||||
res[2] = res[2] and res[2] - 1
|
|
||||||
return table.unpack(res)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Will iterate back through any UTF-8 bytes so that we don't replace bits
|
|
||||||
-- mid character.
|
|
||||||
local function previous_character(str, index)
|
|
||||||
local byte
|
|
||||||
repeat
|
|
||||||
index = index - 1
|
|
||||||
byte = string.byte(str, index)
|
|
||||||
until byte < 128 or byte >= 192
|
|
||||||
return index
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Moves to the end of the identified character.
|
|
||||||
local function end_character(str, index)
|
|
||||||
local byte = string.byte(str, index + 1)
|
|
||||||
while byte and byte >= 128 and byte < 192 do
|
|
||||||
index = index + 1
|
|
||||||
byte = string.byte(str, index + 1)
|
|
||||||
end
|
|
||||||
return index
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Build off matching. For now, only support basic replacements, but capture
|
|
||||||
-- groupings should be doable. We can even have custom group replacements and
|
|
||||||
-- transformations and stuff in lua. Currently, this takes group replacements
|
|
||||||
-- as \1 - \9.
|
|
||||||
-- Should work on UTF-8 text.
|
|
||||||
regex.gsub = function(pattern_string, str, replacement)
|
|
||||||
local pattern = type(pattern_string) == "table" and
|
|
||||||
pattern_string or regex.compile(pattern_string)
|
|
||||||
local result, indices = ""
|
|
||||||
local matches, replacements = {}, {}
|
|
||||||
repeat
|
|
||||||
indices = { regex.cmatch(pattern, str) }
|
|
||||||
if #indices > 0 then
|
|
||||||
table.insert(matches, indices)
|
|
||||||
local currentReplacement = replacement
|
|
||||||
if #indices > 2 then
|
|
||||||
for i = 1, (#indices/2 - 1) do
|
|
||||||
currentReplacement = string.gsub(
|
|
||||||
currentReplacement,
|
|
||||||
"\\" .. i,
|
|
||||||
str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
|
|
||||||
table.insert(replacements, { indices[1], #currentReplacement+indices[1] })
|
|
||||||
if indices[1] > 1 then
|
|
||||||
result = result ..
|
|
||||||
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
|
|
||||||
else
|
|
||||||
result = result .. currentReplacement
|
|
||||||
end
|
|
||||||
str = str:sub(indices[2])
|
|
||||||
end
|
|
||||||
until #indices == 0 or indices[1] == indices[2]
|
|
||||||
return result .. str, matches, replacements
|
|
||||||
end
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,8 @@
|
||||||
-- this file is used by lite-xl to setup the Lua environment when starting
|
-- this file is used by lite-xl to setup the Lua environment when starting
|
||||||
VERSION = "2.1.0r1"
|
VERSION = "1.16.12"
|
||||||
MOD_VERSION = "3"
|
MOD_VERSION = "1"
|
||||||
|
|
||||||
SCALE = tonumber(os.getenv("LITE_SCALE") or os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or SCALE
|
SCALE = tonumber(os.getenv("LITE_SCALE")) or SCALE
|
||||||
PATHSEP = package.config:sub(1, 1)
|
PATHSEP = package.config:sub(1, 1)
|
||||||
|
|
||||||
EXEDIR = EXEFILE:match("^(.+)[/\\][^/\\]+$")
|
EXEDIR = EXEFILE:match("^(.+)[/\\][^/\\]+$")
|
||||||
|
@ -12,46 +12,10 @@ else
|
||||||
local prefix = EXEDIR:match("^(.+)[/\\]bin$")
|
local prefix = EXEDIR:match("^(.+)[/\\]bin$")
|
||||||
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
|
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
|
||||||
end
|
end
|
||||||
USERDIR = (system.get_file_info(EXEDIR .. '/user') and (EXEDIR .. '/user'))
|
USERDIR = os.getenv("XDG_CONFIG_HOME") or (HOME and (HOME .. '/.config/lite-xl') or (EXEDIR .. '/user'))
|
||||||
or ((os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl"))
|
|
||||||
or (HOME and (HOME .. '/.config/lite-xl'))
|
|
||||||
|
|
||||||
package.path = DATADIR .. '/?.lua;'
|
package.path = DATADIR .. '/?.lua;' .. package.path
|
||||||
package.path = DATADIR .. '/?/init.lua;' .. package.path
|
package.path = DATADIR .. '/?/init.lua;' .. package.path
|
||||||
package.path = USERDIR .. '/?.lua;' .. package.path
|
package.path = USERDIR .. '/?.lua;' .. package.path
|
||||||
package.path = USERDIR .. '/?/init.lua;' .. package.path
|
package.path = USERDIR .. '/?/init.lua;' .. package.path
|
||||||
|
|
||||||
local suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so')
|
|
||||||
package.cpath =
|
|
||||||
USERDIR .. '/?.' .. ARCH .. "." .. suffix .. ";" ..
|
|
||||||
USERDIR .. '/?/init.' .. ARCH .. "." .. suffix .. ";" ..
|
|
||||||
USERDIR .. '/?.' .. suffix .. ";" ..
|
|
||||||
USERDIR .. '/?/init.' .. suffix .. ";" ..
|
|
||||||
DATADIR .. '/?.' .. ARCH .. "." .. suffix .. ";" ..
|
|
||||||
DATADIR .. '/?/init.' .. ARCH .. "." .. suffix .. ";" ..
|
|
||||||
DATADIR .. '/?.' .. suffix .. ";" ..
|
|
||||||
DATADIR .. '/?/init.' .. suffix .. ";"
|
|
||||||
|
|
||||||
package.native_plugins = {}
|
|
||||||
package.searchers = { package.searchers[1], package.searchers[2], function(modname)
|
|
||||||
local path = package.searchpath(modname, package.cpath)
|
|
||||||
if not path then return nil end
|
|
||||||
return system.load_native_plugin, path
|
|
||||||
end }
|
|
||||||
|
|
||||||
table.pack = table.pack or pack or function(...) return {...} end
|
|
||||||
table.unpack = table.unpack or unpack
|
|
||||||
|
|
||||||
bit32 = bit32 or require "core.bit"
|
|
||||||
|
|
||||||
require "core.utf8string"
|
|
||||||
|
|
||||||
-- Because AppImages change the working directory before running the executable,
|
|
||||||
-- we need to change it back to the original one.
|
|
||||||
-- https://github.com/AppImage/AppImageKit/issues/172
|
|
||||||
-- https://github.com/AppImage/AppImageKit/pull/191
|
|
||||||
local appimage_owd = os.getenv("OWD")
|
|
||||||
if os.getenv("APPIMAGE") and appimage_owd then
|
|
||||||
system.chdir(appimage_owd)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,6 @@ local style = {}
|
||||||
style.padding = { x = common.round(14 * SCALE), y = common.round(7 * SCALE) }
|
style.padding = { x = common.round(14 * SCALE), y = common.round(7 * SCALE) }
|
||||||
style.divider_size = common.round(1 * SCALE)
|
style.divider_size = common.round(1 * SCALE)
|
||||||
style.scrollbar_size = common.round(4 * SCALE)
|
style.scrollbar_size = common.round(4 * SCALE)
|
||||||
style.expanded_scrollbar_size = common.round(12 * SCALE)
|
|
||||||
style.caret_width = common.round(2 * SCALE)
|
style.caret_width = common.round(2 * SCALE)
|
||||||
style.tab_width = common.round(170 * SCALE)
|
style.tab_width = common.round(170 * SCALE)
|
||||||
|
|
||||||
|
@ -22,13 +21,41 @@ style.tab_width = common.round(170 * SCALE)
|
||||||
--
|
--
|
||||||
-- On High DPI monitor or non RGB monitor you may consider using antialiasing grayscale instead.
|
-- On High DPI monitor or non RGB monitor you may consider using antialiasing grayscale instead.
|
||||||
-- The antialiasing grayscale with full hinting is interesting for crisp font rendering.
|
-- The antialiasing grayscale with full hinting is interesting for crisp font rendering.
|
||||||
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 15 * SCALE)
|
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
|
||||||
style.big_font = style.font:copy(46 * SCALE)
|
style.big_font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 40 * SCALE)
|
||||||
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 16 * SCALE, {antialiasing="grayscale", hinting="full"})
|
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 14 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||||
style.icon_big_font = style.icon_font:copy(23 * SCALE)
|
style.icon_big_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 20 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 15 * SCALE)
|
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
|
||||||
|
|
||||||
|
style.background = { common.color "#2e2e32" }
|
||||||
|
style.background2 = { common.color "#252529" }
|
||||||
|
style.background3 = { common.color "#252529" }
|
||||||
|
style.text = { common.color "#97979c" }
|
||||||
|
style.caret = { common.color "#93DDFA" }
|
||||||
|
style.accent = { common.color "#e1e1e6" }
|
||||||
|
style.dim = { common.color "#525257" }
|
||||||
|
style.divider = { common.color "#202024" }
|
||||||
|
style.selection = { common.color "#48484f" }
|
||||||
|
style.line_number = { common.color "#525259" }
|
||||||
|
style.line_number2 = { common.color "#83838f" }
|
||||||
|
style.line_highlight = { common.color "#343438" }
|
||||||
|
style.scrollbar = { common.color "#414146" }
|
||||||
|
style.scrollbar2 = { common.color "#4b4b52" }
|
||||||
|
style.nagbar = { common.color "#FF0000" }
|
||||||
|
style.nagbar_text = { common.color "#FFFFFF" }
|
||||||
|
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
|
||||||
|
|
||||||
style.syntax = {}
|
style.syntax = {}
|
||||||
|
style.syntax["normal"] = { common.color "#e1e1e6" }
|
||||||
|
style.syntax["symbol"] = { common.color "#e1e1e6" }
|
||||||
|
style.syntax["comment"] = { common.color "#676b6f" }
|
||||||
|
style.syntax["keyword"] = { common.color "#E58AC9" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#F77483" }
|
||||||
|
style.syntax["number"] = { common.color "#FFA94D" }
|
||||||
|
style.syntax["literal"] = { common.color "#FFA94D" }
|
||||||
|
style.syntax["string"] = { common.color "#f7c95c" }
|
||||||
|
style.syntax["operator"] = { common.color "#93DDFA" }
|
||||||
|
style.syntax["function"] = { common.color "#93DDFA" }
|
||||||
|
|
||||||
-- This can be used to override fonts per syntax group.
|
-- This can be used to override fonts per syntax group.
|
||||||
-- The syntax highlighter will take existing values from this table and
|
-- The syntax highlighter will take existing values from this table and
|
||||||
|
@ -37,7 +64,4 @@ style.syntax = {}
|
||||||
style.syntax_fonts = {}
|
style.syntax_fonts = {}
|
||||||
-- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options)
|
-- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options)
|
||||||
|
|
||||||
style.log = {}
|
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
|
||||||
|
|
|
@ -3,28 +3,10 @@ local common = require "core.common"
|
||||||
local syntax = {}
|
local syntax = {}
|
||||||
syntax.items = {}
|
syntax.items = {}
|
||||||
|
|
||||||
local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} }
|
local plain_text_syntax = { patterns = {}, symbols = {} }
|
||||||
|
|
||||||
|
|
||||||
function syntax.add(t)
|
function syntax.add(t)
|
||||||
if type(t.space_handling) ~= "boolean" then t.space_handling = true end
|
|
||||||
|
|
||||||
if t.patterns then
|
|
||||||
-- the rule %s+ gives us a performance gain for the tokenizer in lines with
|
|
||||||
-- long amounts of consecutive spaces, can be disabled by plugins where it
|
|
||||||
-- causes conflicts by declaring the table property: space_handling = false
|
|
||||||
if t.space_handling then
|
|
||||||
table.insert(t.patterns, { pattern = "%s+", type = "normal" })
|
|
||||||
end
|
|
||||||
|
|
||||||
-- this rule gives us additional performance gain by matching every word
|
|
||||||
-- that was not matched by the syntax patterns as a single token, preventing
|
|
||||||
-- the tokenizer from iterating over each character individually which is a
|
|
||||||
-- lot slower since iteration occurs in lua instead of C and adding to that
|
|
||||||
-- it will also try to match every pattern to a single char (same as spaces)
|
|
||||||
table.insert(t.patterns, { pattern = "%w+%f[%s]", type = "normal" })
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(syntax.items, t)
|
table.insert(syntax.items, t)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -40,7 +22,7 @@ end
|
||||||
|
|
||||||
function syntax.get(filename, header)
|
function syntax.get(filename, header)
|
||||||
return find(filename, "files")
|
return find(filename, "files")
|
||||||
or (header and find(header, "headers"))
|
or find(header, "headers")
|
||||||
or plain_text_syntax
|
or plain_text_syntax
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,6 @@ local title_commands = {
|
||||||
{symbol = "X", action = function() core.quit() end},
|
{symbol = "X", action = function() core.quit() end},
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class core.titleview : core.view
|
|
||||||
---@field super core.view
|
|
||||||
local TitleView = View:extend()
|
local TitleView = View:extend()
|
||||||
|
|
||||||
local function title_view_height()
|
local function title_view_height()
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
local core = require "core"
|
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
local common = require "core.common"
|
|
||||||
|
|
||||||
local tokenizer = {}
|
local tokenizer = {}
|
||||||
local bad_patterns = {}
|
|
||||||
|
|
||||||
local function push_token(t, type, text)
|
local function push_token(t, type, text)
|
||||||
type = type or "normal"
|
|
||||||
local prev_type = t[#t-1]
|
local prev_type = t[#t-1]
|
||||||
local prev_text = t[#t]
|
local prev_text = t[#t]
|
||||||
if prev_type and (prev_type == type or prev_text:ufind("^%s*$")) then
|
if prev_type and (prev_type == type or prev_text:find("^%s*$")) then
|
||||||
t[#t-1] = type
|
t[#t-1] = type
|
||||||
t[#t] = prev_text .. text
|
t[#t] = prev_text .. text
|
||||||
else
|
else
|
||||||
|
@ -41,26 +37,49 @@ local function push_tokens(t, syn, pattern, full_text, find_results)
|
||||||
local fin = find_results[i + 1] - 1
|
local fin = find_results[i + 1] - 1
|
||||||
local type = pattern.type[i - 2]
|
local type = pattern.type[i - 2]
|
||||||
-- ↑ (i - 2) to convert from [3; n] to [1; n]
|
-- ↑ (i - 2) to convert from [3; n] to [1; n]
|
||||||
local text = full_text:usub(start, fin)
|
local text = full_text:sub(start, fin)
|
||||||
push_token(t, syn.symbols[text] or type, text)
|
push_token(t, syn.symbols[text] or type, text)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local start, fin = find_results[1], find_results[2]
|
local start, fin = find_results[1], find_results[2]
|
||||||
local text = full_text:usub(start, fin)
|
local text = full_text:sub(start, fin)
|
||||||
push_token(t, syn.symbols[text] or pattern.type, text)
|
push_token(t, syn.symbols[text] or pattern.type, text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function is_escaped(text, idx, esc)
|
||||||
|
local byte = esc:byte()
|
||||||
|
local count = 0
|
||||||
|
for i = idx - 1, 1, -1 do
|
||||||
|
if text:byte(i) ~= byte then break end
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
return count % 2 == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function find_non_escaped(text, pattern, offset, esc)
|
||||||
|
while true do
|
||||||
|
local s, e = text:find(pattern, offset)
|
||||||
|
if not s then break end
|
||||||
|
if esc and is_escaped(text, s, esc) then
|
||||||
|
offset = e + 1
|
||||||
|
else
|
||||||
|
return s, e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- State is a 32-bit number that is four separate bytes, illustrating how many
|
-- State is a 32-bit number that is four separate bytes, illustrating how many
|
||||||
-- differnet delimiters we have open, and which subsyntaxes we have active.
|
-- differnet delimiters we have open, and which subsyntaxes we have active.
|
||||||
-- At most, there are 3 subsyntaxes active at the same time. Beyond that,
|
-- At most, there are 3 subsyntaxes active at the same time. Beyond that,
|
||||||
-- does not support further highlighting.
|
-- does not support further highlighting.
|
||||||
|
|
||||||
-- You can think of it as a maximum 4 integer (0-255) stack. It always has
|
-- You can think of it as a maximum 4 integer (0-255) stack. It always has
|
||||||
-- 1 integer in it. Calling `push_subsyntax` increases the stack depth. Calling
|
-- 1 integer in it. Calling `push_subsyntax` increases the stack depth. Calling
|
||||||
-- `pop_subsyntax` decreases it. The integers represent the index of a pattern
|
-- `pop_subsyntax` decreases it. The integers represent the index of a pattern
|
||||||
-- that we're following in the syntax. The top of the stack can be any valid
|
-- that we're following in the syntax. The top of the stack can be any valid
|
||||||
-- pattern index, any integer lower in the stack must represent a pattern that
|
-- pattern index, any integer lower in the stack must represent a pattern that
|
||||||
-- specifies a subsyntax.
|
-- specifies a subsyntax.
|
||||||
|
|
||||||
|
@ -95,19 +114,6 @@ local function retrieve_syntax_state(incoming_syntax, state)
|
||||||
return current_syntax, subsyntax_info, current_pattern_idx, current_level
|
return current_syntax, subsyntax_info, current_pattern_idx, current_level
|
||||||
end
|
end
|
||||||
|
|
||||||
local function report_bad_pattern(log_fn, syntax, pattern_idx, msg, ...)
|
|
||||||
if not bad_patterns[syntax] then
|
|
||||||
bad_patterns[syntax] = { }
|
|
||||||
end
|
|
||||||
if bad_patterns[syntax][pattern_idx] then return end
|
|
||||||
bad_patterns[syntax][pattern_idx] = true
|
|
||||||
log_fn("Malformed pattern #%d in %s language plugin. " .. msg,
|
|
||||||
pattern_idx, syntax.name or "unnamed", ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param incoming_syntax table
|
|
||||||
---@param text string
|
|
||||||
---@param state integer
|
|
||||||
function tokenizer.tokenize(incoming_syntax, text, state)
|
function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
local res = {}
|
local res = {}
|
||||||
local i = 1
|
local i = 1
|
||||||
|
@ -118,22 +124,22 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
|
|
||||||
state = state or 0
|
state = state or 0
|
||||||
-- incoming_syntax : the parent syntax of the file.
|
-- incoming_syntax : the parent syntax of the file.
|
||||||
-- state : a 32-bit number representing syntax state (see above)
|
-- state : a 32-bit number representing syntax state (see above)
|
||||||
|
|
||||||
-- current_syntax : the syntax we're currently in.
|
-- current_syntax : the syntax we're currently in.
|
||||||
-- subsyntax_info : info about the delimiters of this subsyntax.
|
-- subsyntax_info : info about the delimiters of this subsyntax.
|
||||||
-- current_pattern_idx: the index of the pattern we're on for this syntax.
|
-- current_pattern_idx: the index of the pattern we're on for this syntax.
|
||||||
-- current_level : how many subsyntaxes deep we are.
|
-- current_level : how many subsyntaxes deep we are.
|
||||||
local current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
local current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||||
retrieve_syntax_state(incoming_syntax, state)
|
retrieve_syntax_state(incoming_syntax, state)
|
||||||
|
|
||||||
-- Should be used to set the state variable. Don't modify it directly.
|
-- Should be used to set the state variable. Don't modify it directly.
|
||||||
local function set_subsyntax_pattern_idx(pattern_idx)
|
local function set_subsyntax_pattern_idx(pattern_idx)
|
||||||
current_pattern_idx = pattern_idx
|
current_pattern_idx = pattern_idx
|
||||||
state = bit32.replace(state, pattern_idx, current_level*8, 8)
|
state = bit32.replace(state, pattern_idx, current_level*8, 8)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function push_subsyntax(entering_syntax, pattern_idx)
|
local function push_subsyntax(entering_syntax, pattern_idx)
|
||||||
set_subsyntax_pattern_idx(pattern_idx)
|
set_subsyntax_pattern_idx(pattern_idx)
|
||||||
current_level = current_level + 1
|
current_level = current_level + 1
|
||||||
|
@ -142,182 +148,109 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
entering_syntax.syntax or syntax.get(entering_syntax.syntax)
|
entering_syntax.syntax or syntax.get(entering_syntax.syntax)
|
||||||
current_pattern_idx = 0
|
current_pattern_idx = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
local function pop_subsyntax()
|
local function pop_subsyntax()
|
||||||
set_subsyntax_pattern_idx(0)
|
set_subsyntax_pattern_idx(0)
|
||||||
current_level = current_level - 1
|
current_level = current_level - 1
|
||||||
set_subsyntax_pattern_idx(0)
|
set_subsyntax_pattern_idx(0)
|
||||||
current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
||||||
retrieve_syntax_state(incoming_syntax, state)
|
retrieve_syntax_state(incoming_syntax, state)
|
||||||
end
|
|
||||||
|
|
||||||
local function find_text(text, p, offset, at_start, close)
|
|
||||||
local target, res = p.pattern or p.regex, { 1, offset - 1 }
|
|
||||||
local p_idx = close and 2 or 1
|
|
||||||
local code = type(target) == "table" and target[p_idx] or target
|
|
||||||
|
|
||||||
if p.whole_line == nil then p.whole_line = { } end
|
|
||||||
if p.whole_line[p_idx] == nil then
|
|
||||||
-- Match patterns that start with '^'
|
|
||||||
p.whole_line[p_idx] = code:umatch("^%^") and true or false
|
|
||||||
if p.whole_line[p_idx] then
|
|
||||||
-- Remove '^' from the beginning of the pattern
|
|
||||||
if type(target) == "table" then
|
|
||||||
target[p_idx] = code:usub(2)
|
|
||||||
else
|
|
||||||
p.pattern = p.pattern and code:usub(2)
|
|
||||||
p.regex = p.regex and code:usub(2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if p.regex and type(p.regex) ~= "table" then
|
|
||||||
p._regex = p._regex or regex.compile(p.regex)
|
|
||||||
code = p._regex
|
|
||||||
end
|
|
||||||
|
|
||||||
repeat
|
|
||||||
local next = res[2] + 1
|
|
||||||
-- If the pattern contained '^', allow matching only the whole line
|
|
||||||
if p.whole_line[p_idx] and next > 1 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
res = p.pattern and { text:ufind((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) }
|
|
||||||
or { regex.match(code, text, text:ucharpos(next), (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) }
|
|
||||||
if p.regex and #res > 0 then -- set correct utf8 len for regex result
|
|
||||||
local char_pos_1 = string.ulen(text:sub(1, res[1]))
|
|
||||||
local char_pos_2 = char_pos_1 + string.ulen(text:sub(res[1], res[2])) - 1
|
|
||||||
-- `regex.match` returns group results as a series of `begin, end`
|
|
||||||
-- we only want `begin`s
|
|
||||||
if #res >= 3 then
|
|
||||||
res[3] = char_pos_1 + string.ulen(text:sub(res[1], res[3])) - 1
|
|
||||||
end
|
|
||||||
for i=1,(#res-3) do
|
|
||||||
local curr = i + 3
|
|
||||||
local from = i * 2 + 3
|
|
||||||
if from < #res then
|
|
||||||
res[curr] = char_pos_1 + string.ulen(text:sub(res[1], res[from])) - 1
|
|
||||||
else
|
|
||||||
res[curr] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
res[1] = char_pos_1
|
|
||||||
res[2] = char_pos_2
|
|
||||||
end
|
|
||||||
if res[1] and target[3] then
|
|
||||||
-- Check to see if the escaped character is there,
|
|
||||||
-- and if it is not itself escaped.
|
|
||||||
local count = 0
|
|
||||||
for i = res[1] - 1, 1, -1 do
|
|
||||||
if text:ubyte(i) ~= target[3]:ubyte() then break end
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
if count % 2 == 0 then
|
|
||||||
-- The match is not escaped, so confirm it
|
|
||||||
break
|
|
||||||
elseif not close then
|
|
||||||
-- The *open* match is escaped, so avoid it
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
until not res[1] or not close or not target[3]
|
|
||||||
return table.unpack(res)
|
|
||||||
end
|
|
||||||
|
|
||||||
local text_len = text:ulen()
|
|
||||||
if text_len ~= nil then
|
|
||||||
while i <= text_len do
|
|
||||||
-- continue trying to match the end pattern of a pair if we have a state set
|
|
||||||
if current_pattern_idx > 0 then
|
|
||||||
local p = current_syntax.patterns[current_pattern_idx]
|
|
||||||
local s, e = find_text(text, p, i, false, true)
|
|
||||||
|
|
||||||
local cont = true
|
|
||||||
-- If we're in subsyntax mode, always check to see if we end our syntax
|
|
||||||
-- first, before the found delimeter, as ending the subsyntax takes
|
|
||||||
-- precedence over ending the delimiter in the subsyntax.
|
|
||||||
if subsyntax_info then
|
|
||||||
local ss, se = find_text(text, subsyntax_info, i, false, true)
|
|
||||||
-- If we find that we end the subsyntax before the
|
|
||||||
-- delimiter, push the token, and signal we shouldn't
|
|
||||||
-- treat the bit after as a token to be normally parsed
|
|
||||||
-- (as it's the syntax delimiter).
|
|
||||||
if ss and (s == nil or ss < s) then
|
|
||||||
push_token(res, p.type, text:usub(i, ss - 1))
|
|
||||||
i = ss
|
|
||||||
cont = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- If we don't have any concerns about syntax delimiters,
|
|
||||||
-- continue on as normal.
|
|
||||||
if cont then
|
|
||||||
if s then
|
|
||||||
push_token(res, p.type, text:usub(i, e))
|
|
||||||
set_subsyntax_pattern_idx(0)
|
|
||||||
i = e + 1
|
|
||||||
else
|
|
||||||
push_token(res, p.type, text:usub(i))
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- General end of syntax check. Applies in the case where
|
|
||||||
-- we're ending early in the middle of a delimiter, or
|
|
||||||
-- just normally, upon finding a token.
|
|
||||||
if subsyntax_info then
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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
|
end
|
||||||
|
|
||||||
|
while i <= #text do
|
||||||
|
-- continue trying to match the end pattern of a pair if we have a state set
|
||||||
|
if current_pattern_idx > 0 then
|
||||||
|
local p = current_syntax.patterns[current_pattern_idx]
|
||||||
|
local s, e = find_non_escaped(text, p.pattern[2], i, p.pattern[3])
|
||||||
|
|
||||||
|
local cont = true
|
||||||
|
-- If we're in subsyntax mode, always check to see if we end our syntax
|
||||||
|
-- first, before the found delimeter, as ending the subsyntax takes
|
||||||
|
-- precedence over ending the delimiter in the subsyntax.
|
||||||
|
if subsyntax_info then
|
||||||
|
local ss, se = find_non_escaped(
|
||||||
|
text,
|
||||||
|
subsyntax_info.pattern[2],
|
||||||
|
i,
|
||||||
|
subsyntax_info.pattern[3]
|
||||||
|
)
|
||||||
|
-- If we find that we end the subsyntax before the
|
||||||
|
-- delimiter, push the token, and signal we shouldn't
|
||||||
|
-- treat the bit after as a token to be normally parsed
|
||||||
|
-- (as it's the syntax delimiter).
|
||||||
|
if ss and (s == nil or ss < s) then
|
||||||
|
push_token(res, p.type, text:sub(i, ss - 1))
|
||||||
|
i = ss
|
||||||
|
cont = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- If we don't have any concerns about syntax delimiters,
|
||||||
|
-- continue on as normal.
|
||||||
|
if cont then
|
||||||
|
if s then
|
||||||
|
push_token(res, p.type, text:sub(i, e))
|
||||||
|
set_subsyntax_pattern_idx(0)
|
||||||
|
i = e + 1
|
||||||
|
else
|
||||||
|
push_token(res, p.type, text:sub(i))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- General end of syntax check. Applies in the case where
|
||||||
|
-- we're ending early in the middle of a delimiter, or
|
||||||
|
-- just normally, upon finding a token.
|
||||||
|
if subsyntax_info then
|
||||||
|
local s, e = find_non_escaped(
|
||||||
|
text,
|
||||||
|
"^" .. subsyntax_info.pattern[2],
|
||||||
|
i,
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
if s then
|
||||||
|
push_token(res, subsyntax_info.type, text:sub(i, e))
|
||||||
|
-- On finding unescaped delimiter, pop it.
|
||||||
|
pop_subsyntax()
|
||||||
|
i = e + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- find matching pattern
|
||||||
|
local matched = false
|
||||||
|
for n, p in ipairs(current_syntax.patterns) do
|
||||||
|
local pattern = (type(p.pattern) == "table") and p.pattern[1] or p.pattern
|
||||||
|
local find_results = { text:find("^" .. pattern, i) }
|
||||||
|
local start, fin = find_results[1], find_results[2]
|
||||||
|
|
||||||
|
if start then
|
||||||
|
-- matched pattern; make and add tokens
|
||||||
|
push_tokens(res, current_syntax, p, text, find_results)
|
||||||
|
|
||||||
|
-- update state if this was a start|end pattern pair
|
||||||
|
if type(p.pattern) == "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 = fin + 1
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- consume character if we didn't match
|
||||||
|
if not matched then
|
||||||
|
push_token(res, "normal", text:sub(i, i))
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return res, state
|
return res, state
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
-- inject utf8 functions to strings
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
local utf8 = require "utf8extra"
|
|
||||||
|
|
||||||
string.ubyte = utf8.byte
|
|
||||||
string.uchar = utf8.char
|
|
||||||
string.ufind = utf8.find
|
|
||||||
string.ugmatch = utf8.gmatch
|
|
||||||
string.ugsub = utf8.gsub
|
|
||||||
string.ulen = utf8.len
|
|
||||||
string.ulower = utf8.lower
|
|
||||||
string.umatch = utf8.match
|
|
||||||
string.ureverse = utf8.reverse
|
|
||||||
string.usub = utf8.sub
|
|
||||||
string.uupper = utf8.upper
|
|
||||||
|
|
||||||
string.uescape = utf8.escape
|
|
||||||
string.ucharpos = utf8.charpos
|
|
||||||
string.unext = utf8.next
|
|
||||||
string.uinsert = utf8.insert
|
|
||||||
string.uremove = utf8.remove
|
|
||||||
string.uwidth = utf8.width
|
|
||||||
string.uwidthindex = utf8.widthindex
|
|
||||||
string.utitle = utf8.title
|
|
||||||
string.ufold = utf8.fold
|
|
||||||
string.uncasecmp = utf8.ncasecmp
|
|
||||||
|
|
||||||
string.uoffset = utf8.offset
|
|
||||||
string.ucodepoint = utf8.codepoint
|
|
||||||
string.ucodes = utf8.codes
|
|
|
@ -4,57 +4,9 @@ local style = require "core.style"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local Object = require "core.object"
|
local Object = require "core.object"
|
||||||
|
|
||||||
---@class core.view.position
|
|
||||||
---@field x number
|
|
||||||
---@field y number
|
|
||||||
|
|
||||||
---@class core.view.scroll
|
|
||||||
---@field x number
|
|
||||||
---@field y number
|
|
||||||
---@field to core.view.position
|
|
||||||
|
|
||||||
---@class core.view.thumbtrack
|
|
||||||
---@field thumb number
|
|
||||||
---@field track number
|
|
||||||
|
|
||||||
---@class core.view.thumbtrackwidth
|
|
||||||
---@field thumb number
|
|
||||||
---@field track number
|
|
||||||
---@field to core.view.thumbtrack
|
|
||||||
|
|
||||||
---@class core.view.scrollbar
|
|
||||||
---@field x core.view.thumbtrack
|
|
||||||
---@field y core.view.thumbtrack
|
|
||||||
---@field w core.view.thumbtrackwidth
|
|
||||||
---@field h core.view.thumbtrack
|
|
||||||
|
|
||||||
---@class core.view.increment
|
|
||||||
---@field value number
|
|
||||||
---@field to number
|
|
||||||
|
|
||||||
---@alias core.view.cursor "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'"
|
|
||||||
|
|
||||||
---@alias core.view.mousebutton "'left'" | "'right'"
|
|
||||||
|
|
||||||
---@alias core.view.context "'application'" | "'session'"
|
|
||||||
|
|
||||||
---Base view.
|
|
||||||
---@class core.view : core.object
|
|
||||||
---@field context core.view.context
|
|
||||||
---@field super core.object
|
|
||||||
---@field position core.view.position
|
|
||||||
---@field size core.view.position
|
|
||||||
---@field scroll core.view.scroll
|
|
||||||
---@field cursor core.view.cursor
|
|
||||||
---@field scrollable boolean
|
|
||||||
---@field scrollbar core.view.scrollbar
|
|
||||||
---@field scrollbar_alpha core.view.increment
|
|
||||||
local View = Object:extend()
|
local View = Object:extend()
|
||||||
|
|
||||||
-- context can be "application" or "session". The instance of objects
|
|
||||||
-- with context "session" will be closed when a project session is
|
|
||||||
-- terminated. The context "application" is for functional UI elements.
|
|
||||||
View.context = "application"
|
|
||||||
|
|
||||||
function View:new()
|
function View:new()
|
||||||
self.position = { x = 0, y = 0 }
|
self.position = { x = 0, y = 0 }
|
||||||
|
@ -62,22 +14,14 @@ function View:new()
|
||||||
self.scroll = { x = 0, y = 0, to = { x = 0, y = 0 } }
|
self.scroll = { x = 0, y = 0, to = { x = 0, y = 0 } }
|
||||||
self.cursor = "arrow"
|
self.cursor = "arrow"
|
||||||
self.scrollable = false
|
self.scrollable = false
|
||||||
self.scrollbar = {
|
|
||||||
x = { thumb = 0, track = 0 },
|
|
||||||
y = { thumb = 0, track = 0 },
|
|
||||||
w = { thumb = 0, track = 0, to = { thumb = 0, track = 0 } },
|
|
||||||
h = { thumb = 0, track = 0 },
|
|
||||||
}
|
|
||||||
self.scrollbar_alpha = { value = 0, to = 0 }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function View:move_towards(t, k, dest, rate, name)
|
function View:move_towards(t, k, dest, rate)
|
||||||
if type(t) ~= "table" then
|
if type(t) ~= "table" then
|
||||||
return self:move_towards(self, t, k, dest, rate, name)
|
return self:move_towards(self, t, k, dest, rate)
|
||||||
end
|
end
|
||||||
local val = t[k]
|
local val = t[k]
|
||||||
local diff = math.abs(val - dest)
|
if not config.transitions or math.abs(val - dest) < 0.5 then
|
||||||
if not config.transitions or diff < 0.5 or config.disabled_transitions[name] then
|
|
||||||
t[k] = dest
|
t[k] = dest
|
||||||
else
|
else
|
||||||
rate = rate or 0.5
|
rate = rate or 0.5
|
||||||
|
@ -87,7 +31,7 @@ function View:move_towards(t, k, dest, rate, name)
|
||||||
end
|
end
|
||||||
t[k] = common.lerp(val, dest, rate)
|
t[k] = common.lerp(val, dest, rate)
|
||||||
end
|
end
|
||||||
if diff > 1e-8 then
|
if val ~= dest then
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -98,150 +42,70 @@ function View:try_close(do_close)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@return string
|
|
||||||
function View:get_name()
|
function View:get_name()
|
||||||
return "---"
|
return "---"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@return number
|
|
||||||
function View:get_scrollable_size()
|
function View:get_scrollable_size()
|
||||||
return math.huge
|
return math.huge
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@return number x
|
|
||||||
---@return number y
|
|
||||||
---@return number width
|
|
||||||
---@return number height
|
|
||||||
function View:get_scrollbar_track_rect()
|
|
||||||
local sz = self:get_scrollable_size()
|
|
||||||
if sz <= self.size.y or sz == math.huge then
|
|
||||||
return 0, 0, 0, 0
|
|
||||||
end
|
|
||||||
local width = style.scrollbar_size
|
|
||||||
if self.hovered_scrollbar_track or self.dragging_scrollbar then
|
|
||||||
width = style.expanded_scrollbar_size
|
|
||||||
end
|
|
||||||
return
|
|
||||||
self.position.x + self.size.x - width,
|
|
||||||
self.position.y,
|
|
||||||
width,
|
|
||||||
self.size.y
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---@return number x
|
|
||||||
---@return number y
|
|
||||||
---@return number width
|
|
||||||
---@return number height
|
|
||||||
function View:get_scrollbar_rect()
|
function View:get_scrollbar_rect()
|
||||||
local sz = self:get_scrollable_size()
|
local sz = self:get_scrollable_size()
|
||||||
if sz <= self.size.y or sz == math.huge then
|
if sz <= self.size.y or sz == math.huge then
|
||||||
return 0, 0, 0, 0
|
return 0, 0, 0, 0
|
||||||
end
|
end
|
||||||
local h = math.max(20, self.size.y * self.size.y / sz)
|
local h = math.max(20, self.size.y * self.size.y / sz)
|
||||||
local width = style.scrollbar_size
|
|
||||||
if self.hovered_scrollbar_track or self.dragging_scrollbar then
|
|
||||||
width = style.expanded_scrollbar_size
|
|
||||||
end
|
|
||||||
return
|
return
|
||||||
self.position.x + self.size.x - width,
|
self.position.x + self.size.x - style.scrollbar_size,
|
||||||
self.position.y + self.scroll.y * (self.size.y - h) / (sz - self.size.y),
|
self.position.y + self.scroll.y * (self.size.y - h) / (sz - self.size.y),
|
||||||
width,
|
style.scrollbar_size,
|
||||||
h
|
h
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@return boolean
|
|
||||||
function View:scrollbar_overlaps_point(x, y)
|
function View:scrollbar_overlaps_point(x, y)
|
||||||
local sx, sy, sw, sh = self:get_scrollbar_rect()
|
local sx, sy, sw, sh = self:get_scrollbar_rect()
|
||||||
return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh
|
return x >= sx - sw * 3 and x < sx + sw and y >= sy and y < sy + sh
|
||||||
end
|
|
||||||
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@return boolean
|
|
||||||
function View:scrollbar_track_overlaps_point(x, y)
|
|
||||||
local sx, sy, sw, sh = self:get_scrollbar_track_rect()
|
|
||||||
return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@param button core.view.mousebutton
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param clicks integer
|
|
||||||
---return boolean
|
|
||||||
function View:on_mouse_pressed(button, x, y, clicks)
|
function View:on_mouse_pressed(button, x, y, clicks)
|
||||||
if self:scrollbar_track_overlaps_point(x, y) then
|
if self:scrollbar_overlaps_point(x, y) then
|
||||||
if self:scrollbar_overlaps_point(x, y) then
|
self.dragging_scrollbar = true
|
||||||
self.dragging_scrollbar = true
|
|
||||||
else
|
|
||||||
local _, _, _, sh = self:get_scrollbar_rect()
|
|
||||||
local ly = (y - self.position.y) - sh / 2
|
|
||||||
local pct = common.clamp(ly / self.size.y, 0, 100)
|
|
||||||
self.scroll.to.y = self:get_scrollable_size() * pct
|
|
||||||
end
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@param button core.view.mousebutton
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
function View:on_mouse_released(button, x, y)
|
function View:on_mouse_released(button, x, y)
|
||||||
self.dragging_scrollbar = false
|
self.dragging_scrollbar = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param dx number
|
|
||||||
---@param dy number
|
|
||||||
function View:on_mouse_moved(x, y, dx, dy)
|
function View:on_mouse_moved(x, y, dx, dy)
|
||||||
if self.dragging_scrollbar then
|
if self.dragging_scrollbar then
|
||||||
local delta = self:get_scrollable_size() / self.size.y * dy
|
local delta = self:get_scrollable_size() / self.size.y * dy
|
||||||
self.scroll.to.y = self.scroll.to.y + delta
|
self.scroll.to.y = self.scroll.to.y + delta
|
||||||
if not config.animate_drag_scroll then
|
|
||||||
self:clamp_scroll_position()
|
|
||||||
self.scroll.y = self.scroll.to.y
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
self.hovered_scrollbar = self:scrollbar_overlaps_point(x, y)
|
self.hovered_scrollbar = self:scrollbar_overlaps_point(x, y)
|
||||||
self.hovered_scrollbar_track = self.hovered_scrollbar or self:scrollbar_track_overlaps_point(x, y)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function View:on_mouse_left()
|
|
||||||
self.hovered_scrollbar = false
|
|
||||||
self.hovered_scrollbar_track = false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---@param filename string
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@return boolean
|
|
||||||
function View:on_file_dropped(filename, x, y)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---@param text string
|
|
||||||
function View:on_text_input(text)
|
function View:on_text_input(text)
|
||||||
-- no-op
|
-- no-op
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param y number
|
|
||||||
---@return boolean
|
|
||||||
function View:on_mouse_wheel(y)
|
|
||||||
|
|
||||||
|
function View:on_mouse_wheel(y)
|
||||||
|
if self.scrollable then
|
||||||
|
self.scroll.to.y = self.scroll.to.y + y * -config.mouse_wheel_scroll
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function View:get_content_bounds()
|
function View:get_content_bounds()
|
||||||
local x = self.scroll.x
|
local x = self.scroll.x
|
||||||
local y = self.scroll.y
|
local y = self.scroll.y
|
||||||
|
@ -249,8 +113,6 @@ function View:get_content_bounds()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@return number x
|
|
||||||
---@return number y
|
|
||||||
function View:get_content_offset()
|
function View:get_content_offset()
|
||||||
local x = common.round(self.position.x - self.scroll.x)
|
local x = common.round(self.position.x - self.scroll.x)
|
||||||
local y = common.round(self.position.y - self.scroll.y)
|
local y = common.round(self.position.y - self.scroll.y)
|
||||||
|
@ -264,67 +126,25 @@ function View:clamp_scroll_position()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function View:update_scrollbar()
|
|
||||||
local x, y, w, h = self:get_scrollbar_rect()
|
|
||||||
self.scrollbar.w.to.thumb = w
|
|
||||||
self:move_towards(self.scrollbar.w, "thumb", self.scrollbar.w.to.thumb, 0.3, "scroll")
|
|
||||||
self.scrollbar.x.thumb = x + w - self.scrollbar.w.thumb
|
|
||||||
self.scrollbar.y.thumb = y
|
|
||||||
self.scrollbar.h.thumb = h
|
|
||||||
|
|
||||||
local x, y, w, h = self:get_scrollbar_track_rect()
|
|
||||||
self.scrollbar.w.to.track = w
|
|
||||||
self:move_towards(self.scrollbar.w, "track", self.scrollbar.w.to.track, 0.3, "scroll")
|
|
||||||
self.scrollbar.x.track = x + w - self.scrollbar.w.track
|
|
||||||
self.scrollbar.y.track = y
|
|
||||||
self.scrollbar.h.track = h
|
|
||||||
|
|
||||||
-- we use 100 for a smoother transition
|
|
||||||
self.scrollbar_alpha.to = (self.hovered_scrollbar_track or self.dragging_scrollbar) and 100 or 0
|
|
||||||
self:move_towards(self.scrollbar_alpha, "value", self.scrollbar_alpha.to, 0.3, "scroll")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function View:update()
|
function View:update()
|
||||||
self:clamp_scroll_position()
|
self:clamp_scroll_position()
|
||||||
self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3, "scroll")
|
self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3)
|
||||||
self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3, "scroll")
|
self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3)
|
||||||
|
|
||||||
self:update_scrollbar()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@param color renderer.color
|
|
||||||
function View:draw_background(color)
|
function View:draw_background(color)
|
||||||
local x, y = self.position.x, self.position.y
|
local x, y = self.position.x, self.position.y
|
||||||
local w, h = self.size.x, self.size.y
|
local w, h = self.size.x, self.size.y
|
||||||
renderer.draw_rect(x, y, w, h, color)
|
renderer.draw_rect(x, y, w + x % 1, h + y % 1, color)
|
||||||
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
|
end
|
||||||
|
|
||||||
|
|
||||||
function View:draw_scrollbar()
|
function View:draw_scrollbar()
|
||||||
self:draw_scrollbar_track()
|
local x, y, w, h = self:get_scrollbar_rect()
|
||||||
self:draw_scrollbar_thumb()
|
local highlight = self.hovered_scrollbar or self.dragging_scrollbar
|
||||||
|
local color = highlight and style.scrollbar2 or style.scrollbar
|
||||||
|
renderer.draw_rect(x, y, w, h, color)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
|
@ -8,154 +8,44 @@ local keymap = require "core.keymap"
|
||||||
local translate = require "core.doc.translate"
|
local translate = require "core.doc.translate"
|
||||||
local RootView = require "core.rootview"
|
local RootView = require "core.rootview"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
local Doc = require "core.doc"
|
|
||||||
|
|
||||||
config.plugins.autocomplete = common.merge({
|
config.autocomplete_max_suggestions = 6
|
||||||
-- Amount of characters that need to be written for autocomplete
|
|
||||||
min_len = 3,
|
|
||||||
-- The max amount of visible items
|
|
||||||
max_height = 6,
|
|
||||||
-- The max amount of scrollable items
|
|
||||||
max_suggestions = 100,
|
|
||||||
-- Maximum amount of symbols to cache per document
|
|
||||||
max_symbols = 4000,
|
|
||||||
-- Font size of the description box
|
|
||||||
desc_font_size = 12,
|
|
||||||
-- The config specification used by gui generators
|
|
||||||
config_spec = {
|
|
||||||
name = "Autocomplete",
|
|
||||||
{
|
|
||||||
label = "Minimum Length",
|
|
||||||
description = "Amount of characters that need to be written for autocomplete to popup.",
|
|
||||||
path = "min_len",
|
|
||||||
type = "number",
|
|
||||||
default = 3,
|
|
||||||
min = 1,
|
|
||||||
max = 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Maximum Height",
|
|
||||||
description = "The maximum amount of visible items.",
|
|
||||||
path = "max_height",
|
|
||||||
type = "number",
|
|
||||||
default = 6,
|
|
||||||
min = 1,
|
|
||||||
max = 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Maximum Suggestions",
|
|
||||||
description = "The maximum amount of scrollable items.",
|
|
||||||
path = "max_suggestions",
|
|
||||||
type = "number",
|
|
||||||
default = 100,
|
|
||||||
min = 10,
|
|
||||||
max = 10000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Maximum Symbols",
|
|
||||||
description = "Maximum amount of symbols to cache per document.",
|
|
||||||
path = "max_symbols",
|
|
||||||
type = "number",
|
|
||||||
default = 4000,
|
|
||||||
min = 1000,
|
|
||||||
max = 10000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Description Font Size",
|
|
||||||
description = "Font size of the description box.",
|
|
||||||
path = "desc_font_size",
|
|
||||||
type = "number",
|
|
||||||
default = 12,
|
|
||||||
min = 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, config.plugins.autocomplete)
|
|
||||||
|
|
||||||
local autocomplete = {}
|
local autocomplete = {}
|
||||||
|
|
||||||
autocomplete.map = {}
|
autocomplete.map = {}
|
||||||
autocomplete.map_manually = {}
|
|
||||||
autocomplete.on_close = nil
|
|
||||||
|
|
||||||
-- Flag that indicates if the autocomplete box was manually triggered
|
|
||||||
-- with the autocomplete.complete() function to prevent the suggestions
|
|
||||||
-- from getting cluttered with arbitrary document symbols by using the
|
|
||||||
-- autocomplete.map_manually table.
|
|
||||||
local triggered_manually = false
|
|
||||||
|
|
||||||
local mt = { __tostring = function(t) return t.text end }
|
local mt = { __tostring = function(t) return t.text end }
|
||||||
|
|
||||||
function autocomplete.add(t, manually_triggered)
|
function autocomplete.add(t)
|
||||||
local items = {}
|
local items = {}
|
||||||
for text, info in pairs(t.items) do
|
for text, info in pairs(t.items) do
|
||||||
if type(info) == "table" then
|
info = (type(info) == "string") and info
|
||||||
table.insert(
|
table.insert(items, setmetatable({ text = text, info = info }, mt))
|
||||||
items,
|
|
||||||
setmetatable(
|
|
||||||
{
|
|
||||||
text = text,
|
|
||||||
info = info.info,
|
|
||||||
desc = info.desc, -- Description shown on item selected
|
|
||||||
onhover = info.onhover, -- A callback called once when item is hovered
|
|
||||||
onselect = info.onselect, -- A callback called when item is selected
|
|
||||||
data = info.data -- Optional data that can be used on cb
|
|
||||||
},
|
|
||||||
mt
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else
|
|
||||||
info = (type(info) == "string") and info
|
|
||||||
table.insert(items, setmetatable({ text = text, info = info }, mt))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not manually_triggered then
|
|
||||||
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
|
||||||
else
|
|
||||||
autocomplete.map_manually[t.name] = { files = t.files or ".*", items = items }
|
|
||||||
end
|
end
|
||||||
|
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
local max_symbols = config.max_symbols or 2000
|
||||||
-- Thread that scans open document symbols and cache them
|
|
||||||
--
|
|
||||||
local max_symbols = config.plugins.autocomplete.max_symbols
|
|
||||||
|
|
||||||
core.add_thread(function()
|
core.add_thread(function()
|
||||||
local cache = setmetatable({}, { __mode = "k" })
|
local cache = setmetatable({}, { __mode = "k" })
|
||||||
|
|
||||||
local function get_syntax_symbols(symbols, doc)
|
|
||||||
if doc.syntax then
|
|
||||||
for sym in pairs(doc.syntax.symbols) do
|
|
||||||
symbols[sym] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_symbols(doc)
|
local function get_symbols(doc)
|
||||||
local s = {}
|
if doc.disable_symbols then return {} end
|
||||||
get_syntax_symbols(s, doc)
|
|
||||||
if doc.disable_symbols then return s end
|
|
||||||
local i = 1
|
local i = 1
|
||||||
|
local s = {}
|
||||||
local symbols_count = 0
|
local symbols_count = 0
|
||||||
while i <= #doc.lines do
|
while i < #doc.lines do
|
||||||
for sym in doc.lines[i]:gmatch(config.symbol_pattern) do
|
for sym in doc.lines[i]:gmatch(config.symbol_pattern) do
|
||||||
if not s[sym] then
|
if not s[sym] then
|
||||||
symbols_count = symbols_count + 1
|
symbols_count = symbols_count + 1
|
||||||
if symbols_count > max_symbols then
|
if symbols_count > max_symbols then
|
||||||
s = nil
|
s = nil
|
||||||
doc.disable_symbols = true
|
doc.disable_symbols = true
|
||||||
local filename_message
|
|
||||||
if doc.filename then
|
|
||||||
filename_message = "document " .. doc.filename
|
|
||||||
else
|
|
||||||
filename_message = "unnamed document"
|
|
||||||
end
|
|
||||||
core.status_view:show_message("!", style.accent,
|
core.status_view:show_message("!", style.accent,
|
||||||
"Too many symbols in "..filename_message..
|
"Too many symbols in document "..doc.filename..
|
||||||
": stopping auto-complete for this document according to "..
|
": stopping auto-complete for this document according to config.max_symbols.")
|
||||||
"config.plugins.autocomplete.max_symbols."
|
|
||||||
)
|
|
||||||
collectgarbage('collect')
|
collectgarbage('collect')
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
@ -202,7 +92,6 @@ core.add_thread(function()
|
||||||
for _, doc in ipairs(core.docs) do
|
for _, doc in ipairs(core.docs) do
|
||||||
if not cache_is_valid(doc) then
|
if not cache_is_valid(doc) then
|
||||||
valid = false
|
valid = false
|
||||||
break
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -220,29 +109,16 @@ local last_line, last_col
|
||||||
local function reset_suggestions()
|
local function reset_suggestions()
|
||||||
suggestions_idx = 1
|
suggestions_idx = 1
|
||||||
suggestions = {}
|
suggestions = {}
|
||||||
|
|
||||||
triggered_manually = false
|
|
||||||
|
|
||||||
local doc = core.active_view.doc
|
|
||||||
if autocomplete.on_close then
|
|
||||||
autocomplete.on_close(doc, suggestions[suggestions_idx])
|
|
||||||
autocomplete.on_close = nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function update_suggestions()
|
local function update_suggestions()
|
||||||
local doc = core.active_view.doc
|
local doc = core.active_view.doc
|
||||||
local filename = doc and doc.filename or ""
|
local filename = doc and doc.filename or ""
|
||||||
|
|
||||||
local map = autocomplete.map
|
|
||||||
|
|
||||||
if triggered_manually then
|
|
||||||
map = autocomplete.map_manually
|
|
||||||
end
|
|
||||||
|
|
||||||
-- get all relevant suggestions for given filename
|
-- get all relevant suggestions for given filename
|
||||||
local items = {}
|
local items = {}
|
||||||
for _, v in pairs(map) do
|
for _, v in pairs(autocomplete.map) do
|
||||||
if common.match_pattern(filename, v.files) then
|
if common.match_pattern(filename, v.files) then
|
||||||
for _, item in pairs(v.items) do
|
for _, item in pairs(v.items) do
|
||||||
table.insert(items, item)
|
table.insert(items, item)
|
||||||
|
@ -253,16 +129,16 @@ local function update_suggestions()
|
||||||
-- fuzzy match, remove duplicates and store
|
-- fuzzy match, remove duplicates and store
|
||||||
items = common.fuzzy_match(items, partial)
|
items = common.fuzzy_match(items, partial)
|
||||||
local j = 1
|
local j = 1
|
||||||
for i = 1, config.plugins.autocomplete.max_suggestions do
|
for i = 1, config.autocomplete_max_suggestions do
|
||||||
suggestions[i] = items[j]
|
suggestions[i] = items[j]
|
||||||
while items[j] and items[i].text == items[j].text do
|
while items[j] and items[i].text == items[j].text do
|
||||||
items[i].info = items[i].info or items[j].info
|
items[i].info = items[i].info or items[j].info
|
||||||
j = j + 1
|
j = j + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
suggestions_idx = 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_partial_symbol()
|
local function get_partial_symbol()
|
||||||
local doc = core.active_view.doc
|
local doc = core.active_view.doc
|
||||||
local line2, col2 = doc:get_selection()
|
local line2, col2 = doc:get_selection()
|
||||||
|
@ -270,19 +146,22 @@ local function get_partial_symbol()
|
||||||
return doc:get_text(line1, col1, line2, col2)
|
return doc:get_text(line1, col1, line2, col2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_active_view()
|
local function get_active_view()
|
||||||
if core.active_view:is(DocView) then
|
if getmetatable(core.active_view) == DocView then
|
||||||
return core.active_view
|
return core.active_view
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_suggestions_rect(av)
|
local function get_suggestions_rect(av)
|
||||||
if #suggestions == 0 then
|
if #suggestions == 0 then
|
||||||
return 0, 0, 0, 0
|
return 0, 0, 0, 0
|
||||||
end
|
end
|
||||||
|
|
||||||
local line, col = av.doc:get_selection()
|
local line, col = av.doc:get_selection()
|
||||||
local x, y = av:get_line_screen_position(line, col - #partial)
|
local x, y = av:get_line_screen_position(line)
|
||||||
|
x = x + av:get_col_x_offset(line, col - #partial)
|
||||||
y = y + av:get_line_height() + style.padding.y
|
y = y + av:get_line_height() + style.padding.y
|
||||||
local font = av:get_font()
|
local font = av:get_font()
|
||||||
local th = font:get_height()
|
local th = font:get_height()
|
||||||
|
@ -296,148 +175,15 @@ local function get_suggestions_rect(av)
|
||||||
max_width = math.max(max_width, w)
|
max_width = math.max(max_width, w)
|
||||||
end
|
end
|
||||||
|
|
||||||
local ah = config.plugins.autocomplete.max_height
|
|
||||||
|
|
||||||
local max_items = #suggestions
|
|
||||||
if max_items > ah then
|
|
||||||
max_items = ah
|
|
||||||
end
|
|
||||||
|
|
||||||
-- additional line to display total items
|
|
||||||
max_items = max_items + 1
|
|
||||||
|
|
||||||
if max_width < 150 then
|
|
||||||
max_width = 150
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if portion not visiable to right, reposition to DocView right margin
|
|
||||||
if (x - av.position.x) + max_width > av.size.x then
|
|
||||||
x = (av.size.x + av.position.x) - max_width - (style.padding.x * 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
return
|
return
|
||||||
x - style.padding.x,
|
x - style.padding.x,
|
||||||
y - style.padding.y,
|
y - style.padding.y,
|
||||||
max_width + style.padding.x * 2,
|
max_width + style.padding.x * 2,
|
||||||
max_items * (th + style.padding.y) + style.padding.y
|
#suggestions * (th + style.padding.y) + style.padding.y
|
||||||
end
|
end
|
||||||
|
|
||||||
local function wrap_line(line, max_chars)
|
|
||||||
if #line > max_chars then
|
|
||||||
local lines = {}
|
|
||||||
local line_len = #line
|
|
||||||
local new_line = ""
|
|
||||||
local prev_char = ""
|
|
||||||
local position = 0
|
|
||||||
local indent = line:match("^%s+")
|
|
||||||
for char in line:gmatch(".") do
|
|
||||||
position = position + 1
|
|
||||||
if #new_line < max_chars then
|
|
||||||
new_line = new_line .. char
|
|
||||||
prev_char = char
|
|
||||||
if position >= line_len then
|
|
||||||
table.insert(lines, new_line)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if
|
|
||||||
not prev_char:match("%s")
|
|
||||||
and
|
|
||||||
not string.sub(line, position+1, 1):match("%s")
|
|
||||||
and
|
|
||||||
position < line_len
|
|
||||||
then
|
|
||||||
new_line = new_line .. "-"
|
|
||||||
end
|
|
||||||
table.insert(lines, new_line)
|
|
||||||
if indent then
|
|
||||||
new_line = indent .. char
|
|
||||||
else
|
|
||||||
new_line = char
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return lines
|
|
||||||
end
|
|
||||||
return line
|
|
||||||
end
|
|
||||||
|
|
||||||
local previous_scale = SCALE
|
|
||||||
local desc_font = style.code_font:copy(
|
|
||||||
config.plugins.autocomplete.desc_font_size * SCALE
|
|
||||||
)
|
|
||||||
local function draw_description_box(text, av, sx, sy, sw, sh)
|
|
||||||
if previous_scale ~= SCALE then
|
|
||||||
desc_font = style.code_font:copy(
|
|
||||||
config.plugins.autocomplete.desc_font_size * SCALE
|
|
||||||
)
|
|
||||||
previous_scale = SCALE
|
|
||||||
end
|
|
||||||
|
|
||||||
local font = desc_font
|
|
||||||
local lh = font:get_height()
|
|
||||||
local y = sy + style.padding.y
|
|
||||||
local x = sx + sw + style.padding.x / 4
|
|
||||||
local width = 0
|
|
||||||
local char_width = font:get_width(" ")
|
|
||||||
local draw_left = false;
|
|
||||||
|
|
||||||
local max_chars = 0
|
|
||||||
if sx - av.position.x < av.size.x - (sx - av.position.x) - sw then
|
|
||||||
max_chars = (((av.size.x+av.position.x) - x) / char_width) - 5
|
|
||||||
else
|
|
||||||
draw_left = true;
|
|
||||||
max_chars = (
|
|
||||||
(sx - av.position.x - (style.padding.x / 4) - style.scrollbar_size)
|
|
||||||
/ char_width
|
|
||||||
) - 5
|
|
||||||
end
|
|
||||||
|
|
||||||
local lines = {}
|
|
||||||
for line in string.gmatch(text.."\n", "(.-)\n") do
|
|
||||||
local wrapper_lines = wrap_line(line, max_chars)
|
|
||||||
if type(wrapper_lines) == "table" then
|
|
||||||
for _, wrapped_line in pairs(wrapper_lines) do
|
|
||||||
width = math.max(width, font:get_width(wrapped_line))
|
|
||||||
table.insert(lines, wrapped_line)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
width = math.max(width, font:get_width(line))
|
|
||||||
table.insert(lines, line)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if draw_left then
|
|
||||||
x = sx - (style.padding.x / 4) - width - (style.padding.x * 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
local height = #lines * font:get_height()
|
|
||||||
|
|
||||||
-- draw background rect
|
|
||||||
renderer.draw_rect(
|
|
||||||
x,
|
|
||||||
sy,
|
|
||||||
width + style.padding.x * 2,
|
|
||||||
height + style.padding.y * 2,
|
|
||||||
style.background3
|
|
||||||
)
|
|
||||||
|
|
||||||
-- draw text
|
|
||||||
for _, line in pairs(lines) do
|
|
||||||
common.draw_text(
|
|
||||||
font, style.text, line, "left",
|
|
||||||
x + style.padding.x, y, width, lh
|
|
||||||
)
|
|
||||||
y = y + lh
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function draw_suggestions_box(av)
|
local function draw_suggestions_box(av)
|
||||||
if #suggestions <= 0 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local ah = config.plugins.autocomplete.max_height
|
|
||||||
|
|
||||||
-- draw background rect
|
-- draw background rect
|
||||||
local rx, ry, rw, rh = get_suggestions_rect(av)
|
local rx, ry, rw, rh = get_suggestions_rect(av)
|
||||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||||
|
@ -446,14 +192,7 @@ 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 = #suggestions <= ah and #suggestions or ah
|
for i, s in ipairs(suggestions) do
|
||||||
local start_index = suggestions_idx > ah and (suggestions_idx-(ah-1)) or 1
|
|
||||||
|
|
||||||
for i=start_index, start_index+show_count-1, 1 do
|
|
||||||
if not suggestions[i] then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
local s = suggestions[i]
|
|
||||||
local color = (i == suggestions_idx) and style.accent or style.text
|
local color = (i == suggestions_idx) and style.accent or style.text
|
||||||
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
||||||
if s.info then
|
if s.info then
|
||||||
|
@ -461,54 +200,26 @@ local function draw_suggestions_box(av)
|
||||||
common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh)
|
common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh)
|
||||||
end
|
end
|
||||||
y = y + lh
|
y = y + lh
|
||||||
if suggestions_idx == i then
|
|
||||||
if s.onhover then
|
|
||||||
s.onhover(suggestions_idx, s)
|
|
||||||
s.onhover = nil
|
|
||||||
end
|
|
||||||
if s.desc and #s.desc > 0 then
|
|
||||||
draw_description_box(s.desc, av, rx, ry, rw, rh)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
renderer.draw_rect(rx, y, rw, 2, style.caret)
|
|
||||||
renderer.draw_rect(rx, y+2, rw, lh, style.background)
|
|
||||||
common.draw_text(
|
|
||||||
style.font,
|
|
||||||
style.accent,
|
|
||||||
"Items",
|
|
||||||
"left",
|
|
||||||
rx + style.padding.x, y, rw, lh
|
|
||||||
)
|
|
||||||
common.draw_text(
|
|
||||||
style.font,
|
|
||||||
style.accent,
|
|
||||||
tostring(suggestions_idx) .. "/" .. tostring(#suggestions),
|
|
||||||
"right",
|
|
||||||
rx, y, rw - style.padding.x, lh
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function show_autocomplete()
|
|
||||||
|
-- patch event logic into RootView
|
||||||
|
local on_text_input = RootView.on_text_input
|
||||||
|
local update = RootView.update
|
||||||
|
local draw = RootView.draw
|
||||||
|
|
||||||
|
|
||||||
|
RootView.on_text_input = function(...)
|
||||||
|
on_text_input(...)
|
||||||
|
|
||||||
local av = get_active_view()
|
local av = get_active_view()
|
||||||
if av then
|
if av then
|
||||||
-- update partial symbol and suggestions
|
-- update partial symbol and suggestions
|
||||||
partial = get_partial_symbol()
|
partial = get_partial_symbol()
|
||||||
|
if #partial >= 3 then
|
||||||
if #partial >= config.plugins.autocomplete.min_len or triggered_manually then
|
|
||||||
update_suggestions()
|
update_suggestions()
|
||||||
|
last_line, last_col = av.doc:get_selection()
|
||||||
if not triggered_manually then
|
|
||||||
last_line, last_col = av.doc:get_selection()
|
|
||||||
else
|
|
||||||
local line, col = av.doc:get_selection()
|
|
||||||
local char = av.doc:get_char(line, col-1, line, col-1)
|
|
||||||
|
|
||||||
if char:match("%s") or (char:match("%p") and col ~= last_col) then
|
|
||||||
reset_suggestions()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
reset_suggestions()
|
reset_suggestions()
|
||||||
end
|
end
|
||||||
|
@ -522,30 +233,6 @@ local function show_autocomplete()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
|
||||||
-- Patch event logic into RootView and Doc
|
|
||||||
--
|
|
||||||
local on_text_input = RootView.on_text_input
|
|
||||||
local on_text_remove = Doc.remove
|
|
||||||
local update = RootView.update
|
|
||||||
local draw = RootView.draw
|
|
||||||
|
|
||||||
RootView.on_text_input = function(...)
|
|
||||||
on_text_input(...)
|
|
||||||
show_autocomplete()
|
|
||||||
end
|
|
||||||
|
|
||||||
Doc.remove = function(self, line1, col1, line2, col2)
|
|
||||||
on_text_remove(self, line1, col1, line2, col2)
|
|
||||||
|
|
||||||
if triggered_manually and line1 == line2 then
|
|
||||||
if last_col >= col1 then
|
|
||||||
reset_suggestions()
|
|
||||||
else
|
|
||||||
show_autocomplete()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
RootView.update = function(...)
|
RootView.update = function(...)
|
||||||
update(...)
|
update(...)
|
||||||
|
@ -554,19 +241,13 @@ RootView.update = function(...)
|
||||||
if av then
|
if av then
|
||||||
-- reset suggestions if caret was moved
|
-- reset suggestions if caret was moved
|
||||||
local line, col = av.doc:get_selection()
|
local line, col = av.doc:get_selection()
|
||||||
|
if line ~= last_line or col ~= last_col then
|
||||||
if not triggered_manually then
|
reset_suggestions()
|
||||||
if line ~= last_line or col ~= last_col then
|
|
||||||
reset_suggestions()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if line ~= last_line or col < last_col then
|
|
||||||
reset_suggestions()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
RootView.draw = function(...)
|
RootView.draw = function(...)
|
||||||
draw(...)
|
draw(...)
|
||||||
|
|
||||||
|
@ -577,70 +258,20 @@ RootView.draw = function(...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
|
||||||
-- Public functions
|
|
||||||
--
|
|
||||||
function autocomplete.open(on_close)
|
|
||||||
triggered_manually = true
|
|
||||||
|
|
||||||
if on_close then
|
|
||||||
autocomplete.on_close = on_close
|
|
||||||
end
|
|
||||||
|
|
||||||
local av = get_active_view()
|
|
||||||
last_line, last_col = av.doc:get_selection()
|
|
||||||
update_suggestions()
|
|
||||||
end
|
|
||||||
|
|
||||||
function autocomplete.close()
|
|
||||||
reset_suggestions()
|
|
||||||
end
|
|
||||||
|
|
||||||
function autocomplete.is_open()
|
|
||||||
return #suggestions > 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function autocomplete.complete(completions, on_close)
|
|
||||||
reset_suggestions()
|
|
||||||
|
|
||||||
autocomplete.map_manually = {}
|
|
||||||
autocomplete.add(completions, true)
|
|
||||||
|
|
||||||
autocomplete.open(on_close)
|
|
||||||
end
|
|
||||||
|
|
||||||
function autocomplete.can_complete()
|
|
||||||
if #partial >= config.plugins.autocomplete.min_len then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Commands
|
|
||||||
--
|
|
||||||
local function predicate()
|
local function predicate()
|
||||||
local active_docview = get_active_view()
|
return get_active_view() and #suggestions > 0
|
||||||
return active_docview and #suggestions > 0, active_docview
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
command.add(predicate, {
|
command.add(predicate, {
|
||||||
["autocomplete:complete"] = function(dv)
|
["autocomplete:complete"] = function()
|
||||||
local doc = dv.doc
|
local doc = core.active_view.doc
|
||||||
local line, col = doc:get_selection()
|
local line, col = doc:get_selection()
|
||||||
local item = suggestions[suggestions_idx]
|
local text = suggestions[suggestions_idx].text
|
||||||
local text = item.text
|
doc:insert(line, col, text)
|
||||||
local inserted = false
|
doc:remove(line, col, line, col - #partial)
|
||||||
if item.onselect then
|
doc:set_selection(line, col + #text - #partial)
|
||||||
inserted = item.onselect(suggestions_idx, item)
|
|
||||||
end
|
|
||||||
if not inserted then
|
|
||||||
local current_partial = get_partial_symbol()
|
|
||||||
doc:insert(line, col, text)
|
|
||||||
doc:remove(line, col, line, col - #current_partial)
|
|
||||||
doc:set_selection(line, col + #text - #current_partial)
|
|
||||||
end
|
|
||||||
reset_suggestions()
|
reset_suggestions()
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -652,19 +283,12 @@ command.add(predicate, {
|
||||||
suggestions_idx = math.min(suggestions_idx + 1, #suggestions)
|
suggestions_idx = math.min(suggestions_idx + 1, #suggestions)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["autocomplete:cycle"] = function()
|
|
||||||
local newidx = suggestions_idx + 1
|
|
||||||
suggestions_idx = newidx > #suggestions and 1 or newidx
|
|
||||||
end,
|
|
||||||
|
|
||||||
["autocomplete:cancel"] = function()
|
["autocomplete:cancel"] = function()
|
||||||
reset_suggestions()
|
reset_suggestions()
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
--
|
|
||||||
-- Keymaps
|
|
||||||
--
|
|
||||||
keymap.add {
|
keymap.add {
|
||||||
["tab"] = "autocomplete:complete",
|
["tab"] = "autocomplete:complete",
|
||||||
["up"] = "autocomplete:previous",
|
["up"] = "autocomplete:previous",
|
||||||
|
|
|
@ -1,110 +1,50 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local style = require "core.style"
|
|
||||||
local Doc = require "core.doc"
|
local Doc = require "core.doc"
|
||||||
local Node = require "core.node"
|
|
||||||
local common = require "core.common"
|
|
||||||
local dirwatch = require "core.dirwatch"
|
|
||||||
|
|
||||||
config.plugins.autoreload = common.merge({
|
|
||||||
always_show_nagview = false,
|
|
||||||
config_spec = {
|
|
||||||
name = "Autoreload",
|
|
||||||
{
|
|
||||||
label = "Always Show Nagview",
|
|
||||||
description = "Alerts you if an opened file changes externally even if you haven't modified it.",
|
|
||||||
path = "always_show_nagview",
|
|
||||||
type = "toggle",
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, config.plugins.autoreload)
|
|
||||||
|
|
||||||
local watch = dirwatch.new()
|
|
||||||
local times = setmetatable({}, { __mode = "k" })
|
local times = setmetatable({}, { __mode = "k" })
|
||||||
local visible = setmetatable({}, { __mode = "k" })
|
|
||||||
|
|
||||||
local function get_project_doc_watch(doc)
|
|
||||||
for i, v in ipairs(core.project_directories) do
|
|
||||||
if doc.abs_filename:find(v.name, 1, true) == 1 then return v.watch end
|
|
||||||
end
|
|
||||||
return watch
|
|
||||||
end
|
|
||||||
|
|
||||||
local function update_time(doc)
|
local function update_time(doc)
|
||||||
times[doc] = system.get_file_info(doc.filename).modified
|
local info = system.get_file_info(doc.filename)
|
||||||
|
times[doc] = info.modified
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function reload_doc(doc)
|
local function reload_doc(doc)
|
||||||
doc:reload()
|
local fp = io.open(doc.filename, "r")
|
||||||
|
local text = fp:read("*a")
|
||||||
|
fp:close()
|
||||||
|
|
||||||
|
local sel = { doc:get_selection() }
|
||||||
|
doc:remove(1, 1, math.huge, math.huge)
|
||||||
|
doc:insert(1, 1, text:gsub("\r", ""):gsub("\n$", ""))
|
||||||
|
doc:set_selection(table.unpack(sel))
|
||||||
|
|
||||||
update_time(doc)
|
update_time(doc)
|
||||||
core.redraw = true
|
doc:clean()
|
||||||
core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename)
|
core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function check_prompt_reload(doc)
|
|
||||||
if doc and doc.deferred_reload then
|
|
||||||
core.nag_view:show("File Changed", doc.filename .. " has changed. Reload this file?", {
|
|
||||||
{ font = style.font, text = "Yes", default_yes = true },
|
|
||||||
{ font = style.font, text = "No" , default_no = true }
|
|
||||||
}, function(item)
|
|
||||||
if item.text == "Yes" then reload_doc(doc) end
|
|
||||||
doc.deferred_reload = false
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function doc_changes_visiblity(doc, visibility)
|
|
||||||
if doc and visible[doc] ~= visibility and doc.abs_filename then
|
|
||||||
visible[doc] = visibility
|
|
||||||
if visibility then check_prompt_reload(doc) end
|
|
||||||
get_project_doc_watch(doc):watch(doc.abs_filename, visibility)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local on_check = dirwatch.check
|
|
||||||
function dirwatch:check(change_callback, ...)
|
|
||||||
on_check(self, function(dir)
|
|
||||||
for _, doc in ipairs(core.docs) do
|
|
||||||
if doc.abs_filename and (dir == common.dirname(doc.abs_filename) or dir == doc.abs_filename) then
|
|
||||||
local info = system.get_file_info(doc.filename or "")
|
|
||||||
if info and times[doc] ~= info.modified then
|
|
||||||
if not doc:is_dirty() and not config.plugins.autoreload.always_show_nagview then
|
|
||||||
reload_doc(doc)
|
|
||||||
else
|
|
||||||
doc.deferred_reload = true
|
|
||||||
if doc == core.active_view.doc then check_prompt_reload(doc) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
change_callback(dir)
|
|
||||||
end, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
local core_set_active_view = core.set_active_view
|
|
||||||
function core.set_active_view(view)
|
|
||||||
core_set_active_view(view)
|
|
||||||
doc_changes_visiblity(view.doc, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
local node_set_active_view = Node.set_active_view
|
|
||||||
function Node:set_active_view(view)
|
|
||||||
if self.active_view then doc_changes_visiblity(self.active_view.doc, false) end
|
|
||||||
node_set_active_view(self, view)
|
|
||||||
doc_changes_visiblity(self.active_view.doc, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
core.add_thread(function()
|
core.add_thread(function()
|
||||||
while true do
|
while true do
|
||||||
-- because we already hook this function above; we only
|
-- check all doc modified times
|
||||||
-- need to check the file.
|
for _, doc in ipairs(core.docs) do
|
||||||
watch:check(function() end)
|
local info = system.get_file_info(doc.filename or "")
|
||||||
coroutine.yield(0.05)
|
if info and times[doc] ~= info.modified then
|
||||||
|
reload_doc(doc)
|
||||||
|
end
|
||||||
|
coroutine.yield()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- wait for next scan
|
||||||
|
coroutine.yield(config.project_scan_rate)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
||||||
-- patch `Doc.save|load` to store modified time
|
-- patch `Doc.save|load` to store modified time
|
||||||
local load = Doc.load
|
local load = Doc.load
|
||||||
local save = Doc.save
|
local save = Doc.save
|
||||||
|
@ -117,8 +57,6 @@ end
|
||||||
|
|
||||||
Doc.save = function(self, ...)
|
Doc.save = function(self, ...)
|
||||||
local res = save(self, ...)
|
local res = save(self, ...)
|
||||||
-- if starting with an unsaved document with a filename.
|
|
||||||
if not times[self] then get_project_doc_watch(self):watch(self.abs_filename, true) end
|
|
||||||
update_time(self)
|
update_time(self)
|
||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
-- mod-version:3
|
|
||||||
local core = require "core"
|
|
||||||
local command = require "core.command"
|
|
||||||
local keymap = require "core.keymap"
|
|
||||||
local ContextMenu = require "core.contextmenu"
|
|
||||||
local RootView = require "core.rootview"
|
|
||||||
|
|
||||||
local menu = ContextMenu()
|
|
||||||
local on_view_mouse_pressed = RootView.on_view_mouse_pressed
|
|
||||||
local on_mouse_moved = RootView.on_mouse_moved
|
|
||||||
local root_view_update = RootView.update
|
|
||||||
local root_view_draw = RootView.draw
|
|
||||||
|
|
||||||
function RootView:on_mouse_moved(...)
|
|
||||||
if menu:on_mouse_moved(...) then return end
|
|
||||||
on_mouse_moved(self, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
function RootView.on_view_mouse_pressed(button, x, y, clicks)
|
|
||||||
-- We give the priority to the menu to process mouse pressed events.
|
|
||||||
local handled = menu:on_mouse_pressed(button, x, y, clicks)
|
|
||||||
return handled or on_view_mouse_pressed(button, x, y, clicks)
|
|
||||||
end
|
|
||||||
|
|
||||||
function RootView:update(...)
|
|
||||||
root_view_update(self, ...)
|
|
||||||
menu:update()
|
|
||||||
end
|
|
||||||
|
|
||||||
function RootView:draw(...)
|
|
||||||
root_view_draw(self, ...)
|
|
||||||
menu:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
command.add("core.docview!", {
|
|
||||||
["context:show"] = function(dv)
|
|
||||||
menu:show(dv.position.x, dv.position.y)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
keymap.add {
|
|
||||||
["menu"] = "context:show"
|
|
||||||
}
|
|
||||||
|
|
||||||
command.add(function() return menu.show_context_menu == true end, {
|
|
||||||
["context:focus-previous"] = function()
|
|
||||||
menu:focus_previous()
|
|
||||||
end,
|
|
||||||
["context:focus-next"] = function()
|
|
||||||
menu:focus_next()
|
|
||||||
end,
|
|
||||||
["context:hide"] = function()
|
|
||||||
menu:hide()
|
|
||||||
end,
|
|
||||||
["context:on-selected"] = function()
|
|
||||||
menu:call_selected_item()
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
keymap.add { ["return"] = "context:on-selected" }
|
|
||||||
keymap.add { ["up"] = "context:focus-previous" }
|
|
||||||
keymap.add { ["down"] = "context:focus-next" }
|
|
||||||
keymap.add { ["escape"] = "context:hide" }
|
|
||||||
|
|
||||||
if require("plugins.scale") then
|
|
||||||
menu:register("core.docview", {
|
|
||||||
{ text = "Cut", command = "doc:cut" },
|
|
||||||
{ text = "Copy", command = "doc:copy" },
|
|
||||||
{ text = "Paste", command = "doc:paste" },
|
|
||||||
{ text = "Font +", command = "scale:increase" },
|
|
||||||
{ text = "Font -", command = "scale:decrease" },
|
|
||||||
{ text = "Font Reset", command = "scale:reset" },
|
|
||||||
ContextMenu.DIVIDER,
|
|
||||||
{ text = "Find", command = "find-replace:find" },
|
|
||||||
{ text = "Replace", command = "find-replace:replace" }
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
return menu
|
|
|
@ -1,256 +1,95 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local core_syntax = require "core.syntax"
|
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
local Doc = require "core.doc"
|
local Doc = require "core.doc"
|
||||||
|
local tokenizer = require "core.tokenizer"
|
||||||
|
|
||||||
local cache = setmetatable({}, { __mode = "k" })
|
local cache = setmetatable({}, { __mode = "k" })
|
||||||
local comments_cache = {}
|
|
||||||
local auto_detect_max_lines = 150
|
|
||||||
|
|
||||||
|
|
||||||
local function indent_occurrences_more_than_once(stat, idx)
|
local function add_to_stat(stat, val)
|
||||||
if stat[idx-1] and stat[idx-1] == stat[idx] then
|
for i = 1, #stat do
|
||||||
return true
|
if val == stat[i][1] then
|
||||||
elseif stat[idx+1] and stat[idx+1] == stat[idx] then
|
stat[i][2] = stat[i][2] + 1
|
||||||
return true
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return false
|
stat[#stat + 1] = {val, 1}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function optimal_indent_from_stat(stat)
|
local function optimal_indent_from_stat(stat)
|
||||||
if #stat == 0 then return nil, 0 end
|
if #stat == 0 then return nil, 0 end
|
||||||
table.sort(stat, function(a, b) return a > b end)
|
local bins = {}
|
||||||
local best_indent = 0
|
for k = 1, #stat do
|
||||||
local best_score = 0
|
local indent = stat[k][1]
|
||||||
local count = #stat
|
|
||||||
for x=1, count do
|
|
||||||
local indent = stat[x]
|
|
||||||
local score = 0
|
local score = 0
|
||||||
for y=1, count do
|
local mult_prev, lines_prev
|
||||||
if y ~= x and stat[y] % indent == 0 then
|
for i = k, #stat do
|
||||||
score = score + 1
|
if stat[i][1] % indent == 0 then
|
||||||
elseif
|
local mult = stat[i][1] / indent
|
||||||
indent > stat[y]
|
if not mult_prev or (mult_prev + 1 == mult and lines_prev / stat[i][2] > 0.1) then
|
||||||
and
|
-- we add the number of lines to the score only if the previous
|
||||||
indent_occurrences_more_than_once(stat, y)
|
-- multiple of "indent" was populated with enough lines.
|
||||||
then
|
score = score + stat[i][2]
|
||||||
score = 0
|
end
|
||||||
break
|
mult_prev, lines_prev = mult, stat[i][2]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if score > best_score then
|
bins[#bins + 1] = {indent, score}
|
||||||
best_indent = indent
|
|
||||||
best_score = score
|
|
||||||
end
|
|
||||||
if score > 0 then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return best_score > 0 and best_indent or nil, best_score
|
table.sort(bins, function(a, b) return a[2] > b[2] end)
|
||||||
|
return bins[1][1], bins[1][2]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function escape_comment_tokens(token)
|
-- return nil if it is a comment or blank line or the initial part of the
|
||||||
local special_chars = "*-%[].()+?^$"
|
-- line otherwise.
|
||||||
local escaped = ""
|
-- we don't need to have the whole line to detect indentation.
|
||||||
for x=1, token:len() do
|
local function get_first_line_part(tokens)
|
||||||
local found = false
|
local i, n = 1, #tokens
|
||||||
for y=1, special_chars:len() do
|
while i + 1 <= n do
|
||||||
if token:sub(x, x) == special_chars:sub(y, y) then
|
local ttype, ttext = tokens[i], tokens[i + 1]
|
||||||
escaped = escaped .. "%" .. token:sub(x, x)
|
if ttype ~= "comment" and ttext:gsub("%s+", "") ~= "" then
|
||||||
found = true
|
return ttext
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not found then
|
|
||||||
escaped = escaped .. token:sub(x, x)
|
|
||||||
end
|
end
|
||||||
|
i = i + 2
|
||||||
end
|
end
|
||||||
return escaped
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_comment_patterns(syntax)
|
|
||||||
if comments_cache[syntax] then
|
|
||||||
if #comments_cache[syntax] > 0 then
|
|
||||||
return comments_cache[syntax]
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local comments = {}
|
|
||||||
for idx=1, #syntax.patterns do
|
|
||||||
local pattern = syntax.patterns[idx]
|
|
||||||
local startp = ""
|
|
||||||
if
|
|
||||||
type(pattern.type) == "string"
|
|
||||||
and
|
|
||||||
(pattern.type == "comment" or pattern.type == "string")
|
|
||||||
then
|
|
||||||
local not_is_string = pattern.type ~= "string"
|
|
||||||
if pattern.pattern then
|
|
||||||
startp = type(pattern.pattern) == "table"
|
|
||||||
and pattern.pattern[1] or pattern.pattern
|
|
||||||
if not_is_string and startp:sub(1, 1) ~= "^" then
|
|
||||||
startp = "^%s*" .. startp
|
|
||||||
elseif not_is_string then
|
|
||||||
startp = "^%s*" .. startp:sub(2, startp:len())
|
|
||||||
end
|
|
||||||
if type(pattern.pattern) == "table" then
|
|
||||||
table.insert(comments, {"p", startp, pattern.pattern[2]})
|
|
||||||
elseif not_is_string then
|
|
||||||
table.insert(comments, {"p", startp})
|
|
||||||
end
|
|
||||||
elseif pattern.regex then
|
|
||||||
startp = type(pattern.regex) == "table"
|
|
||||||
and pattern.regex[1] or pattern.regex
|
|
||||||
if not_is_string and startp:sub(1, 1) ~= "^" then
|
|
||||||
startp = "^\\s*" .. startp
|
|
||||||
elseif not_is_string then
|
|
||||||
startp = "^\\s*" .. startp:sub(2, startp:len())
|
|
||||||
end
|
|
||||||
if type(pattern.regex) == "table" then
|
|
||||||
table.insert(comments, {
|
|
||||||
"r", regex.compile(startp), regex.compile(pattern.regex[2])
|
|
||||||
})
|
|
||||||
elseif not_is_string then
|
|
||||||
table.insert(comments, {"r", regex.compile(startp)})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif pattern.syntax then
|
|
||||||
local subsyntax = type(pattern.syntax) == 'table' and pattern.syntax
|
|
||||||
or core_syntax.get("file"..pattern.syntax, "")
|
|
||||||
local sub_comments = get_comment_patterns(subsyntax)
|
|
||||||
if sub_comments then
|
|
||||||
for s=1, #sub_comments do
|
|
||||||
table.insert(comments, sub_comments[s])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #comments == 0 then
|
|
||||||
local single_line_comment = syntax.comment
|
|
||||||
and escape_comment_tokens(syntax.comment) or nil
|
|
||||||
local block_comment = nil
|
|
||||||
if syntax.block_comment then
|
|
||||||
block_comment = {
|
|
||||||
escape_comment_tokens(syntax.block_comment[1]),
|
|
||||||
escape_comment_tokens(syntax.block_comment[2])
|
|
||||||
}
|
|
||||||
end
|
|
||||||
if single_line_comment then
|
|
||||||
table.insert(comments, {"p", "^%s*" .. single_line_comment})
|
|
||||||
end
|
|
||||||
if block_comment then
|
|
||||||
table.insert(comments, {"p", "^%s*" .. block_comment[1], block_comment[2]})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
comments_cache[syntax] = comments
|
|
||||||
if #comments > 0 then
|
|
||||||
return comments
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function get_non_empty_lines(syntax, lines)
|
local function get_non_empty_lines(syntax, lines)
|
||||||
return coroutine.wrap(function()
|
return coroutine.wrap(function()
|
||||||
local comments = get_comment_patterns(syntax)
|
local tokens, state
|
||||||
|
|
||||||
local i = 0
|
local i = 0
|
||||||
local end_regex = nil
|
|
||||||
local end_pattern = nil
|
|
||||||
local inside_comment = false
|
|
||||||
for _, line in ipairs(lines) do
|
for _, line in ipairs(lines) do
|
||||||
if line:gsub("^%s+", "") ~= "" then
|
tokens, state = tokenizer.tokenize(syntax, line, state)
|
||||||
local is_comment = false
|
local line_start = get_first_line_part(tokens)
|
||||||
if comments then
|
if line_start then
|
||||||
if not inside_comment then
|
i = i + 1
|
||||||
for c=1, #comments do
|
coroutine.yield(i, line_start)
|
||||||
local comment = comments[c]
|
|
||||||
if comment[1] == "p" then
|
|
||||||
if comment[3] then
|
|
||||||
local start, ending = line:find(comment[2])
|
|
||||||
if start then
|
|
||||||
if not line:find(comment[3], ending+1) then
|
|
||||||
is_comment = true
|
|
||||||
inside_comment = true
|
|
||||||
end_pattern = comment[3]
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
elseif line:find(comment[2]) then
|
|
||||||
is_comment = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if comment[3] then
|
|
||||||
local start, ending = regex.match(
|
|
||||||
comment[2], line, 1, regex.ANCHORED
|
|
||||||
)
|
|
||||||
if start then
|
|
||||||
if not regex.match(
|
|
||||||
comment[3], line, ending+1, regex.ANCHORED
|
|
||||||
)
|
|
||||||
then
|
|
||||||
is_comment = true
|
|
||||||
inside_comment = true
|
|
||||||
end_regex = comment[3]
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
elseif regex.match(comment[2], line, 1, regex.ANCHORED) then
|
|
||||||
is_comment = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif end_pattern and line:find(end_pattern) then
|
|
||||||
is_comment = true
|
|
||||||
inside_comment = false
|
|
||||||
end_pattern = nil
|
|
||||||
elseif end_regex and regex.match(end_regex, line) then
|
|
||||||
is_comment = true
|
|
||||||
inside_comment = false
|
|
||||||
end_regex = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if
|
|
||||||
not is_comment
|
|
||||||
and
|
|
||||||
not inside_comment
|
|
||||||
then
|
|
||||||
i = i + 1
|
|
||||||
coroutine.yield(i, line)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local auto_detect_max_lines = 100
|
||||||
|
|
||||||
local function detect_indent_stat(doc)
|
local function detect_indent_stat(doc)
|
||||||
local stat = {}
|
local stat = {}
|
||||||
local tab_count = 0
|
local tab_count = 0
|
||||||
local runs = 1
|
|
||||||
local max_lines = auto_detect_max_lines
|
|
||||||
for i, text in get_non_empty_lines(doc.syntax, doc.lines) do
|
for i, text in get_non_empty_lines(doc.syntax, doc.lines) do
|
||||||
local spaces = text:match("^ +")
|
local str = text:match("^ %s+%S")
|
||||||
if spaces then table.insert(stat, spaces:len()) end
|
if str then add_to_stat(stat, #str - 1) end
|
||||||
local tabs = text:match("^\t+")
|
local str = text:match("^\t+")
|
||||||
if tabs then tab_count = tab_count + 1 end
|
if str then tab_count = tab_count + 1 end
|
||||||
-- if nothing found for first lines try at least 4 more times
|
|
||||||
if i == max_lines and runs < 5 and #stat == 0 and tab_count == 0 then
|
|
||||||
max_lines = max_lines + auto_detect_max_lines
|
|
||||||
runs = runs + 1
|
|
||||||
-- Stop parsing when files is very long. Not needed for euristic determination.
|
-- Stop parsing when files is very long. Not needed for euristic determination.
|
||||||
elseif i > max_lines then break end
|
if i > auto_detect_max_lines then break end
|
||||||
end
|
end
|
||||||
|
table.sort(stat, function(a, b) return a[1] < b[1] end)
|
||||||
local indent, score = optimal_indent_from_stat(stat)
|
local indent, score = optimal_indent_from_stat(stat)
|
||||||
if tab_count > score then
|
if tab_count > score then
|
||||||
return "hard", config.indent_size, tab_count
|
return "hard", config.indent_size, tab_count
|
||||||
|
@ -262,12 +101,7 @@ end
|
||||||
|
|
||||||
local function update_cache(doc)
|
local function update_cache(doc)
|
||||||
local type, size, score = detect_indent_stat(doc)
|
local type, size, score = detect_indent_stat(doc)
|
||||||
local score_threshold = 2
|
local score_threshold = 4
|
||||||
if score < score_threshold then
|
|
||||||
-- use default values
|
|
||||||
type = config.tab_type
|
|
||||||
size = config.indent_size
|
|
||||||
end
|
|
||||||
cache[doc] = { type = type, size = size, confirmed = (score >= score_threshold) }
|
cache[doc] = { type = type, size = size, confirmed = (score >= score_threshold) }
|
||||||
doc.indent_info = cache[doc]
|
doc.indent_info = cache[doc]
|
||||||
end
|
end
|
||||||
|
@ -277,94 +111,44 @@ local new = Doc.new
|
||||||
function Doc:new(...)
|
function Doc:new(...)
|
||||||
new(self, ...)
|
new(self, ...)
|
||||||
update_cache(self)
|
update_cache(self)
|
||||||
|
if not cache[self].confirmed then
|
||||||
|
core.add_thread(function ()
|
||||||
|
while not cache[self].confirmed do
|
||||||
|
update_cache(self)
|
||||||
|
coroutine.yield(1)
|
||||||
|
end
|
||||||
|
end, self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local clean = Doc.clean
|
local clean = Doc.clean
|
||||||
function Doc:clean(...)
|
function Doc:clean(...)
|
||||||
clean(self, ...)
|
clean(self, ...)
|
||||||
local _, _, confirmed = self:get_indent_info()
|
update_cache(self)
|
||||||
if not confirmed then
|
end
|
||||||
update_cache(self)
|
|
||||||
|
|
||||||
|
local function with_indent_override(doc, fn, ...)
|
||||||
|
local c = cache[doc]
|
||||||
|
if not c then
|
||||||
|
return fn(...)
|
||||||
end
|
end
|
||||||
|
local type, size = config.tab_type, config.indent_size
|
||||||
|
config.tab_type, config.indent_size = c.type, c.size or config.indent_size
|
||||||
|
local r1, r2, r3 = fn(...)
|
||||||
|
config.tab_type, config.indent_size = type, size
|
||||||
|
return r1, r2, r3
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function set_indent_type(doc, type)
|
local perform = command.perform
|
||||||
local _, indent_size = doc:get_indent_info()
|
function command.perform(...)
|
||||||
cache[doc] = {
|
return with_indent_override(core.active_view.doc, perform, ...)
|
||||||
type = type,
|
|
||||||
size = indent_size,
|
|
||||||
confirmed = true
|
|
||||||
}
|
|
||||||
doc.indent_info = cache[doc]
|
|
||||||
end
|
|
||||||
|
|
||||||
local function set_indent_type_command(dv)
|
|
||||||
core.command_view:enter("Specify indent style for this file", {
|
|
||||||
submit = function(value)
|
|
||||||
local doc = dv.doc
|
|
||||||
value = value:lower()
|
|
||||||
set_indent_type(doc, value == "tabs" and "hard" or "soft")
|
|
||||||
end,
|
|
||||||
suggest = function(text)
|
|
||||||
return common.fuzzy_match({"tabs", "spaces"}, text)
|
|
||||||
end,
|
|
||||||
validate = function(text)
|
|
||||||
local t = text:lower()
|
|
||||||
return t == "tabs" or t == "spaces"
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function set_indent_size(doc, size)
|
local draw = DocView.draw
|
||||||
local indent_type = doc:get_indent_info()
|
function DocView:draw(...)
|
||||||
cache[doc] = {
|
return with_indent_override(self.doc, draw, self, ...)
|
||||||
type = indent_type,
|
|
||||||
size = size,
|
|
||||||
confirmed = true
|
|
||||||
}
|
|
||||||
doc.indent_info = cache[doc]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function set_indent_size_command(dv)
|
|
||||||
core.command_view:enter("Specify indent size for current file", {
|
|
||||||
submit = function(value)
|
|
||||||
value = math.floor(tonumber(value))
|
|
||||||
local doc = dv.doc
|
|
||||||
set_indent_size(doc, value)
|
|
||||||
end,
|
|
||||||
validate = function(value)
|
|
||||||
value = tonumber(value)
|
|
||||||
return value ~= nil and value >= 1
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
command.add("core.docview", {
|
|
||||||
["indent:set-file-indent-type"] = set_indent_type_command,
|
|
||||||
["indent:set-file-indent-size"] = set_indent_size_command
|
|
||||||
})
|
|
||||||
|
|
||||||
command.add(
|
|
||||||
function()
|
|
||||||
return core.active_view:is(DocView)
|
|
||||||
and cache[core.active_view.doc]
|
|
||||||
and cache[core.active_view.doc].type == "soft"
|
|
||||||
end, {
|
|
||||||
["indent:switch-file-to-tabs-indentation"] = function()
|
|
||||||
set_indent_type(core.active_view.doc, "hard")
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
command.add(
|
|
||||||
function()
|
|
||||||
return core.active_view:is(DocView)
|
|
||||||
and cache[core.active_view.doc]
|
|
||||||
and cache[core.active_view.doc].type == "hard"
|
|
||||||
end, {
|
|
||||||
["indent:switch-file-to-spaces-indentation"] = function()
|
|
||||||
set_indent_type(core.active_view.doc, "soft")
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,304 +0,0 @@
|
||||||
-- mod-version:3
|
|
||||||
|
|
||||||
local style = require "core.style"
|
|
||||||
local DocView = require "core.docview"
|
|
||||||
local common = require "core.common"
|
|
||||||
local config = require "core.config"
|
|
||||||
local Highlighter = require "core.doc.highlighter"
|
|
||||||
|
|
||||||
config.plugins.drawwhitespace = common.merge({
|
|
||||||
enabled = true,
|
|
||||||
show_leading = true,
|
|
||||||
show_trailing = true,
|
|
||||||
show_middle = true,
|
|
||||||
|
|
||||||
show_middle_min = 1,
|
|
||||||
|
|
||||||
color = style.syntax.whitespace or style.syntax.comment,
|
|
||||||
leading_color = nil,
|
|
||||||
middle_color = nil,
|
|
||||||
trailing_color = nil,
|
|
||||||
|
|
||||||
substitutions = {
|
|
||||||
{
|
|
||||||
char = " ",
|
|
||||||
sub = "·",
|
|
||||||
-- You can put any of the previous options here too.
|
|
||||||
-- For example:
|
|
||||||
-- show_middle_min = 2,
|
|
||||||
-- show_leading = false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
char = "\t",
|
|
||||||
sub = "»",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
config_spec = {
|
|
||||||
name = "Draw Whitespace",
|
|
||||||
{
|
|
||||||
label = "Enabled",
|
|
||||||
description = "Disable or enable the drawing of white spaces.",
|
|
||||||
path = "enabled",
|
|
||||||
type = "toggle",
|
|
||||||
default = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Show Leading",
|
|
||||||
description = "Draw whitespaces starting at the beginning of a line.",
|
|
||||||
path = "show_leading",
|
|
||||||
type = "toggle",
|
|
||||||
default = true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Show Middle",
|
|
||||||
description = "Draw whitespaces on the middle of a line.",
|
|
||||||
path = "show_middle",
|
|
||||||
type = "toggle",
|
|
||||||
default = true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Show Trailing",
|
|
||||||
description = "Draw whitespaces on the end of a line.",
|
|
||||||
path = "show_trailing",
|
|
||||||
type = "toggle",
|
|
||||||
default = true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Show Trailing as Error",
|
|
||||||
description = "Uses an error square to spot them easily, requires 'Show Trailing' enabled.",
|
|
||||||
path = "show_trailing_error",
|
|
||||||
type = "toggle",
|
|
||||||
default = false,
|
|
||||||
on_apply = function(enabled)
|
|
||||||
local found = nil
|
|
||||||
local substitutions = config.plugins.drawwhitespace.substitutions
|
|
||||||
for i, sub in ipairs(substitutions) do
|
|
||||||
if sub.trailing_error then
|
|
||||||
found = i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if found == nil and enabled then
|
|
||||||
table.insert(substitutions, {
|
|
||||||
char = " ",
|
|
||||||
sub = "█",
|
|
||||||
show_leading = false,
|
|
||||||
show_middle = false,
|
|
||||||
show_trailing = true,
|
|
||||||
trailing_color = style.error,
|
|
||||||
trailing_error = true
|
|
||||||
})
|
|
||||||
elseif found ~= nil and not enabled then
|
|
||||||
table.remove(substitutions, found)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, config.plugins.drawwhitespace)
|
|
||||||
|
|
||||||
|
|
||||||
local ws_cache
|
|
||||||
local cached_settings
|
|
||||||
local function reset_cache()
|
|
||||||
ws_cache = setmetatable({}, { __mode = "k" })
|
|
||||||
local settings = config.plugins.drawwhitespace
|
|
||||||
cached_settings = {
|
|
||||||
show_leading = settings.show_leading,
|
|
||||||
show_trailing = settings.show_trailing,
|
|
||||||
show_middle = settings.show_middle,
|
|
||||||
show_middle_min = settings.show_middle_min,
|
|
||||||
color = settings.color,
|
|
||||||
leading_color = settings.leading_color,
|
|
||||||
middle_color = settings.middle_color,
|
|
||||||
trailing_color = settings.trailing_color,
|
|
||||||
substitutions = settings.substitutions,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
reset_cache()
|
|
||||||
|
|
||||||
local function reset_cache_if_needed()
|
|
||||||
local settings = config.plugins.drawwhitespace
|
|
||||||
if
|
|
||||||
not ws_cache or
|
|
||||||
cached_settings.show_leading ~= settings.show_leading
|
|
||||||
or cached_settings.show_trailing ~= settings.show_trailing
|
|
||||||
or cached_settings.show_middle ~= settings.show_middle
|
|
||||||
or cached_settings.show_middle_min ~= settings.show_middle_min
|
|
||||||
or cached_settings.color ~= settings.color
|
|
||||||
or cached_settings.leading_color ~= settings.leading_color
|
|
||||||
or cached_settings.middle_color ~= settings.middle_color
|
|
||||||
or cached_settings.trailing_color ~= settings.trailing_color
|
|
||||||
-- we assume that the entire table changes
|
|
||||||
or cached_settings.substitutions ~= settings.substitutions
|
|
||||||
then
|
|
||||||
reset_cache()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Move cache to make space for new lines
|
|
||||||
local prev_insert_notify = Highlighter.insert_notify
|
|
||||||
function Highlighter:insert_notify(line, n, ...)
|
|
||||||
prev_insert_notify(self, line, n, ...)
|
|
||||||
if not ws_cache[self] then
|
|
||||||
ws_cache[self] = {}
|
|
||||||
end
|
|
||||||
local to = math.min(line + n, #self.doc.lines)
|
|
||||||
for i=#self.doc.lines+n,to,-1 do
|
|
||||||
ws_cache[self][i] = ws_cache[self][i - n]
|
|
||||||
end
|
|
||||||
for i=line,to do
|
|
||||||
ws_cache[self][i] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Close the cache gap created by removed lines
|
|
||||||
local prev_remove_notify = Highlighter.remove_notify
|
|
||||||
function Highlighter:remove_notify(line, n, ...)
|
|
||||||
prev_remove_notify(self, line, n, ...)
|
|
||||||
if not ws_cache[self] then
|
|
||||||
ws_cache[self] = {}
|
|
||||||
end
|
|
||||||
local to = math.max(line + n, #self.doc.lines)
|
|
||||||
for i=line,to do
|
|
||||||
ws_cache[self][i] = ws_cache[self][i + n]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Remove changed lines from the cache
|
|
||||||
local prev_update_notify = Highlighter.update_notify
|
|
||||||
function Highlighter:update_notify(line, n, ...)
|
|
||||||
prev_update_notify(self, line, n, ...)
|
|
||||||
if not ws_cache[self] then
|
|
||||||
ws_cache[self] = {}
|
|
||||||
end
|
|
||||||
for i=line,line+n do
|
|
||||||
ws_cache[self][i] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function get_option(substitution, option)
|
|
||||||
if substitution[option] == nil then
|
|
||||||
return config.plugins.drawwhitespace[option]
|
|
||||||
end
|
|
||||||
return substitution[option]
|
|
||||||
end
|
|
||||||
|
|
||||||
local draw_line_text = DocView.draw_line_text
|
|
||||||
function DocView:draw_line_text(idx, x, y)
|
|
||||||
if
|
|
||||||
not config.plugins.drawwhitespace.enabled
|
|
||||||
or
|
|
||||||
getmetatable(self) ~= DocView
|
|
||||||
then
|
|
||||||
return draw_line_text(self, idx, x, y)
|
|
||||||
end
|
|
||||||
|
|
||||||
local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"])
|
|
||||||
local font_size = font:get_size()
|
|
||||||
local _, indent_size = self.doc:get_indent_info()
|
|
||||||
|
|
||||||
reset_cache_if_needed()
|
|
||||||
if
|
|
||||||
not ws_cache[self.doc.highlighter]
|
|
||||||
or ws_cache[self.doc.highlighter].font ~= font
|
|
||||||
or ws_cache[self.doc.highlighter].font_size ~= font_size
|
|
||||||
or ws_cache[self.doc.highlighter].indent_size ~= indent_size
|
|
||||||
then
|
|
||||||
ws_cache[self.doc.highlighter] =
|
|
||||||
setmetatable(
|
|
||||||
{ font = font, font_size = font_size, indent_size = indent_size },
|
|
||||||
{ __mode = "k" }
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not ws_cache[self.doc.highlighter][idx] then -- need to cache line
|
|
||||||
local cache = {}
|
|
||||||
|
|
||||||
local tx
|
|
||||||
local text = self.doc.lines[idx]
|
|
||||||
|
|
||||||
for _, substitution in pairs(config.plugins.drawwhitespace.substitutions) do
|
|
||||||
local char = substitution.char
|
|
||||||
local sub = substitution.sub
|
|
||||||
local offset = 1
|
|
||||||
|
|
||||||
local show_leading = get_option(substitution, "show_leading")
|
|
||||||
local show_middle = get_option(substitution, "show_middle")
|
|
||||||
local show_trailing = get_option(substitution, "show_trailing")
|
|
||||||
|
|
||||||
local show_middle_min = get_option(substitution, "show_middle_min")
|
|
||||||
|
|
||||||
local base_color = get_option(substitution, "color")
|
|
||||||
local leading_color = get_option(substitution, "leading_color") or base_color
|
|
||||||
local middle_color = get_option(substitution, "middle_color") or base_color
|
|
||||||
local trailing_color = get_option(substitution, "trailing_color") or base_color
|
|
||||||
|
|
||||||
local pattern = char.."+"
|
|
||||||
while true do
|
|
||||||
local s, e = text:find(pattern, offset)
|
|
||||||
if not s then break end
|
|
||||||
|
|
||||||
tx = self:get_col_x_offset(idx, s)
|
|
||||||
|
|
||||||
local color = base_color
|
|
||||||
local draw = false
|
|
||||||
|
|
||||||
if e == #text - 1 then
|
|
||||||
draw = show_trailing
|
|
||||||
color = trailing_color
|
|
||||||
elseif s == 1 then
|
|
||||||
draw = show_leading
|
|
||||||
color = leading_color
|
|
||||||
else
|
|
||||||
draw = show_middle and (e - s + 1 >= show_middle_min)
|
|
||||||
color = middle_color
|
|
||||||
end
|
|
||||||
|
|
||||||
if draw then
|
|
||||||
local last_cache_idx = #cache
|
|
||||||
-- We need to draw tabs one at a time because they might have a
|
|
||||||
-- different size than the substituting character.
|
|
||||||
-- This also applies to any other char if we use non-monospace fonts
|
|
||||||
-- but we ignore this case for now.
|
|
||||||
if char == "\t" then
|
|
||||||
for i = s,e do
|
|
||||||
tx = self:get_col_x_offset(idx, i)
|
|
||||||
cache[last_cache_idx + 1] = sub
|
|
||||||
cache[last_cache_idx + 2] = tx
|
|
||||||
cache[last_cache_idx + 3] = font:get_width(sub)
|
|
||||||
cache[last_cache_idx + 4] = color
|
|
||||||
last_cache_idx = last_cache_idx + 4
|
|
||||||
end
|
|
||||||
else
|
|
||||||
cache[last_cache_idx + 1] = string.rep(sub, e - s + 1)
|
|
||||||
cache[last_cache_idx + 2] = tx
|
|
||||||
cache[last_cache_idx + 3] = font:get_width(cache[last_cache_idx + 1])
|
|
||||||
cache[last_cache_idx + 4] = color
|
|
||||||
end
|
|
||||||
end
|
|
||||||
offset = e + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ws_cache[self.doc.highlighter][idx] = cache
|
|
||||||
end
|
|
||||||
|
|
||||||
-- draw from cache
|
|
||||||
local x1, _, x2, _ = self:get_content_bounds()
|
|
||||||
x1 = x1 + x
|
|
||||||
x2 = x2 + x
|
|
||||||
local ty = y + self:get_line_text_y_offset()
|
|
||||||
local cache = ws_cache[self.doc.highlighter][idx]
|
|
||||||
for i=1,#cache,4 do
|
|
||||||
local sub = cache[i]
|
|
||||||
local tx = cache[i + 1] + x
|
|
||||||
local tw = cache[i + 2]
|
|
||||||
local color = cache[i + 3]
|
|
||||||
if tx + tw >= x1 then
|
|
||||||
tx = renderer.draw_text(font, sub, tx, ty, color)
|
|
||||||
end
|
|
||||||
if tx > x2 then break end
|
|
||||||
end
|
|
||||||
|
|
||||||
return draw_line_text(self, idx, x, y)
|
|
||||||
end
|
|
|
@ -1,13 +1,11 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "C",
|
files = { "%.c$", "%.h$", "%.inl$" },
|
||||||
files = { "%.c$" },
|
|
||||||
comment = "//",
|
comment = "//",
|
||||||
block_comment = { "/*", "*/" },
|
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = "//.*", type = "comment" },
|
{ pattern = "//.-\n", type = "comment" },
|
||||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||||
|
@ -15,64 +13,12 @@ syntax.add {
|
||||||
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
|
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
|
||||||
{ pattern = "%.?%d+f?", type = "number" },
|
{ pattern = "%.?%d+f?", type = "number" },
|
||||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||||
{ pattern = "##", type = "operator" },
|
|
||||||
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||||
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||||
-- static declarations
|
|
||||||
{ pattern = "static()%s+()inline",
|
|
||||||
type = { "keyword", "normal", "keyword" }
|
|
||||||
},
|
|
||||||
{ pattern = "static()%s+()const",
|
|
||||||
type = { "keyword", "normal", "keyword" }
|
|
||||||
},
|
|
||||||
{ pattern = "static()%s+()[%a_][%w_]*",
|
|
||||||
type = { "keyword", "normal", "literal" }
|
|
||||||
},
|
|
||||||
-- match function type declarations
|
|
||||||
{ pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*%f[%(]",
|
|
||||||
type = { "literal", "operator", "normal", "function" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*%f[%(]",
|
|
||||||
type = { "literal", "normal", "operator", "function" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*%f[%(]",
|
|
||||||
type = { "literal", "normal", "function" }
|
|
||||||
},
|
|
||||||
-- match variable type declarations
|
|
||||||
{ pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*",
|
|
||||||
type = { "literal", "operator", "normal", "normal" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*",
|
|
||||||
type = { "literal", "normal", "operator", "normal" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()[;,%[%)]",
|
|
||||||
type = { "literal", "normal", "normal", "normal", "normal" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()=",
|
|
||||||
type = { "literal", "normal", "normal", "normal", "operator" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()&()%s+()[%a_][%w_]*",
|
|
||||||
type = { "literal", "operator", "normal", "normal" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()&()[%a_][%w_]*",
|
|
||||||
type = { "literal", "normal", "operator", "normal" }
|
|
||||||
},
|
|
||||||
-- Uppercase constants of at least 2 chars in len
|
|
||||||
{ pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%)%]}%?%^%%=/<>~|&;:,!]",
|
|
||||||
type = "number"
|
|
||||||
},
|
|
||||||
-- Magic constants
|
|
||||||
{ pattern = "__[%u%l]+__", type = "number" },
|
|
||||||
-- all other functions
|
|
||||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||||
-- Macros
|
|
||||||
{ pattern = "^%s*#%s*define%s+()[%a_][%a%d_]*",
|
|
||||||
type = { "keyword", "symbol" }
|
|
||||||
},
|
|
||||||
{ pattern = "#%s*include%s()<.->", type = {"keyword", "string"} },
|
|
||||||
{ pattern = "%f[#]#%s*[%a_][%w_]*", type = "keyword" },
|
|
||||||
-- Everything else to make the tokenizer work properly
|
|
||||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||||
|
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
|
||||||
|
{ pattern = "#[%a_][%w_]*", type = "keyword" },
|
||||||
},
|
},
|
||||||
symbols = {
|
symbols = {
|
||||||
["if"] = "keyword",
|
["if"] = "keyword",
|
||||||
|
@ -97,9 +43,7 @@ syntax.add {
|
||||||
["case"] = "keyword",
|
["case"] = "keyword",
|
||||||
["default"] = "keyword",
|
["default"] = "keyword",
|
||||||
["auto"] = "keyword",
|
["auto"] = "keyword",
|
||||||
["struct"] = "keyword",
|
["void"] = "keyword",
|
||||||
["union"] = "keyword",
|
|
||||||
["void"] = "keyword2",
|
|
||||||
["int"] = "keyword2",
|
["int"] = "keyword2",
|
||||||
["short"] = "keyword2",
|
["short"] = "keyword2",
|
||||||
["long"] = "keyword2",
|
["long"] = "keyword2",
|
||||||
|
@ -111,18 +55,6 @@ syntax.add {
|
||||||
["true"] = "literal",
|
["true"] = "literal",
|
||||||
["false"] = "literal",
|
["false"] = "literal",
|
||||||
["NULL"] = "literal",
|
["NULL"] = "literal",
|
||||||
["#include"] = "keyword",
|
|
||||||
["#if"] = "keyword",
|
|
||||||
["#ifdef"] = "keyword",
|
|
||||||
["#ifndef"] = "keyword",
|
|
||||||
["#elif"] = "keyword",
|
|
||||||
["#else"] = "keyword",
|
|
||||||
["#elseif"] = "keyword",
|
|
||||||
["#endif"] = "keyword",
|
|
||||||
["#define"] = "keyword",
|
|
||||||
["#warning"] = "keyword",
|
|
||||||
["#error"] = "keyword",
|
|
||||||
["#pragma"] = "keyword",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,108 +1,36 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
|
pcall(require, "plugins.language_c")
|
||||||
|
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
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++$"
|
||||||
},
|
},
|
||||||
comment = "//",
|
comment = "//",
|
||||||
block_comment = { "/*", "*/" },
|
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = "//.*", type = "comment" },
|
{ pattern = "//.-\n", type = "comment" },
|
||||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||||
{ pattern = "0x%x+", type = "number" },
|
{ pattern = "0x%x+", type = "number" },
|
||||||
{ pattern = "%d+[%d%.'eE]*f?", type = "number" },
|
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
|
||||||
{ pattern = "%.?%d+f?", type = "number" },
|
{ pattern = "%.?%d+f?", type = "number" },
|
||||||
{ pattern = "[%+%-=/%*%^%%<>!~|:&]", type = "operator" },
|
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||||
{ pattern = "##", type = "operator" },
|
|
||||||
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||||
{ pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
{ pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||||
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||||
{ pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
{ pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
||||||
-- static declarations
|
{ pattern = "[%a_][%w_]*::", type = "symbol" },
|
||||||
{ pattern = "static()%s+()inline",
|
{ pattern = "::", type = "symbol" },
|
||||||
type = { "keyword", "normal", "keyword" }
|
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||||
},
|
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
|
||||||
{ pattern = "static()%s+()const",
|
{ pattern = "#[%a_][%w_]*", type = "keyword" },
|
||||||
type = { "keyword", "normal", "keyword" }
|
|
||||||
},
|
|
||||||
{ pattern = "static()%s+()[%a_][%w_]*",
|
|
||||||
type = { "keyword", "normal", "literal" }
|
|
||||||
},
|
|
||||||
-- match method type declarations
|
|
||||||
{ pattern = "[%a_][%w_]*()%s*()%**()%s*()[%a_][%w_]*()%s*()::",
|
|
||||||
type = {
|
|
||||||
"literal", "normal", "operator", "normal",
|
|
||||||
"literal", "normal", "operator"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
-- match function type declarations
|
|
||||||
{ pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*%f[%(]",
|
|
||||||
type = { "literal", "operator", "normal", "function" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*%f[%(]",
|
|
||||||
type = { "literal", "normal", "operator", "function" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*%f[%(]",
|
|
||||||
type = { "literal", "normal", "function" }
|
|
||||||
},
|
|
||||||
-- match variable type declarations
|
|
||||||
{ pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*",
|
|
||||||
type = { "literal", "operator", "normal", "normal" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*",
|
|
||||||
type = { "literal", "normal", "operator", "normal" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()[;,%[%)]",
|
|
||||||
type = { "literal", "normal", "normal", "normal", "normal" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()=",
|
|
||||||
type = { "literal", "normal", "normal", "normal", "operator" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()&()%s+()[%a_][%w_]*",
|
|
||||||
type = { "literal", "operator", "normal", "normal" }
|
|
||||||
},
|
|
||||||
{ pattern = "[%a_][%w_]*()%s+()&()[%a_][%w_]*",
|
|
||||||
type = { "literal", "normal", "operator", "normal" }
|
|
||||||
},
|
|
||||||
-- Match scope operator element access
|
|
||||||
{ pattern = "[%a_][%w_]*()%s*()::",
|
|
||||||
type = { "literal", "normal", "operator" }
|
|
||||||
},
|
|
||||||
-- Uppercase constants of at least 2 chars in len
|
|
||||||
{ pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%)%]}%?%^%%=/<>~|&;:,!]",
|
|
||||||
type = "number"
|
|
||||||
},
|
|
||||||
-- Magic constants
|
|
||||||
{ pattern = "__[%u%l]+__", type = "number" },
|
|
||||||
-- all other functions
|
|
||||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
|
||||||
-- Macros
|
|
||||||
{ pattern = "^%s*#%s*define%s+()[%a_][%a%d_]*",
|
|
||||||
type = { "keyword", "symbol" }
|
|
||||||
},
|
|
||||||
{ pattern = "#%s*include%s+()<.->",
|
|
||||||
type = { "keyword", "string" }
|
|
||||||
},
|
|
||||||
{ pattern = "%f[#]#%s*[%a_][%w_]*", type = "keyword" },
|
|
||||||
-- Everything else to make the tokenizer work properly
|
|
||||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
|
||||||
},
|
},
|
||||||
symbols = {
|
symbols = {
|
||||||
["alignof"] = "keyword",
|
["alignof"] = "keyword",
|
||||||
["alignas"] = "keyword",
|
["alignas"] = "keyword",
|
||||||
["and"] = "keyword",
|
|
||||||
["and_eq"] = "keyword",
|
|
||||||
["not"] = "keyword",
|
|
||||||
["not_eq"] = "keyword",
|
|
||||||
["or"] = "keyword",
|
|
||||||
["or_eq"] = "keyword",
|
|
||||||
["xor"] = "keyword",
|
|
||||||
["xor_eq"] = "keyword",
|
|
||||||
["private"] = "keyword",
|
["private"] = "keyword",
|
||||||
["protected"] = "keyword",
|
["protected"] = "keyword",
|
||||||
["public"] = "keyword",
|
["public"] = "keyword",
|
||||||
|
@ -110,12 +38,9 @@ syntax.add {
|
||||||
["nullptr"] = "keyword",
|
["nullptr"] = "keyword",
|
||||||
["operator"] = "keyword",
|
["operator"] = "keyword",
|
||||||
["asm"] = "keyword",
|
["asm"] = "keyword",
|
||||||
["bitand"] = "keyword",
|
|
||||||
["bitor"] = "keyword",
|
|
||||||
["catch"] = "keyword",
|
["catch"] = "keyword",
|
||||||
["throw"] = "keyword",
|
["throw"] = "keyword",
|
||||||
["try"] = "keyword",
|
["try"] = "keyword",
|
||||||
["class"] = "keyword",
|
|
||||||
["compl"] = "keyword",
|
["compl"] = "keyword",
|
||||||
["explicit"] = "keyword",
|
["explicit"] = "keyword",
|
||||||
["export"] = "keyword",
|
["export"] = "keyword",
|
||||||
|
@ -125,8 +50,8 @@ syntax.add {
|
||||||
["constinit"] = "keyword",
|
["constinit"] = "keyword",
|
||||||
["const_cast"] = "keyword",
|
["const_cast"] = "keyword",
|
||||||
["dynamic_cast"] = "keyword",
|
["dynamic_cast"] = "keyword",
|
||||||
["reinterpret_cast"] = "keyword",
|
["reinterpret_cast"] = "keyword",
|
||||||
["static_cast"] = "keyword",
|
["static_cast"] = "keyword",
|
||||||
["static_assert"] = "keyword",
|
["static_assert"] = "keyword",
|
||||||
["template"] = "keyword",
|
["template"] = "keyword",
|
||||||
["this"] = "keyword",
|
["this"] = "keyword",
|
||||||
|
@ -137,6 +62,7 @@ syntax.add {
|
||||||
["co_yield"] = "keyword",
|
["co_yield"] = "keyword",
|
||||||
["decltype"] = "keyword",
|
["decltype"] = "keyword",
|
||||||
["delete"] = "keyword",
|
["delete"] = "keyword",
|
||||||
|
["export"] = "keyword",
|
||||||
["friend"] = "keyword",
|
["friend"] = "keyword",
|
||||||
["typeid"] = "keyword",
|
["typeid"] = "keyword",
|
||||||
["typename"] = "keyword",
|
["typename"] = "keyword",
|
||||||
|
@ -144,7 +70,6 @@ syntax.add {
|
||||||
["override"] = "keyword",
|
["override"] = "keyword",
|
||||||
["virtual"] = "keyword",
|
["virtual"] = "keyword",
|
||||||
["using"] = "keyword",
|
["using"] = "keyword",
|
||||||
["namespace"] = "keyword",
|
|
||||||
["new"] = "keyword",
|
["new"] = "keyword",
|
||||||
["noexcept"] = "keyword",
|
["noexcept"] = "keyword",
|
||||||
["if"] = "keyword",
|
["if"] = "keyword",
|
||||||
|
@ -158,8 +83,6 @@ syntax.add {
|
||||||
["continue"] = "keyword",
|
["continue"] = "keyword",
|
||||||
["return"] = "keyword",
|
["return"] = "keyword",
|
||||||
["goto"] = "keyword",
|
["goto"] = "keyword",
|
||||||
["struct"] = "keyword",
|
|
||||||
["union"] = "keyword",
|
|
||||||
["typedef"] = "keyword",
|
["typedef"] = "keyword",
|
||||||
["enum"] = "keyword",
|
["enum"] = "keyword",
|
||||||
["extern"] = "keyword",
|
["extern"] = "keyword",
|
||||||
|
@ -171,7 +94,8 @@ syntax.add {
|
||||||
["case"] = "keyword",
|
["case"] = "keyword",
|
||||||
["default"] = "keyword",
|
["default"] = "keyword",
|
||||||
["auto"] = "keyword",
|
["auto"] = "keyword",
|
||||||
["void"] = "keyword2",
|
["const"] = "keyword",
|
||||||
|
["void"] = "keyword",
|
||||||
["int"] = "keyword2",
|
["int"] = "keyword2",
|
||||||
["short"] = "keyword2",
|
["short"] = "keyword2",
|
||||||
["long"] = "keyword2",
|
["long"] = "keyword2",
|
||||||
|
@ -180,18 +104,12 @@ syntax.add {
|
||||||
["char"] = "keyword2",
|
["char"] = "keyword2",
|
||||||
["unsigned"] = "keyword2",
|
["unsigned"] = "keyword2",
|
||||||
["bool"] = "keyword2",
|
["bool"] = "keyword2",
|
||||||
["true"] = "literal",
|
["true"] = "keyword2",
|
||||||
["false"] = "literal",
|
["false"] = "keyword2",
|
||||||
["NULL"] = "literal",
|
|
||||||
["wchar_t"] = "keyword2",
|
|
||||||
["char8_t"] = "keyword2",
|
|
||||||
["char16_t"] = "keyword2",
|
|
||||||
["char32_t"] = "keyword2",
|
|
||||||
["#include"] = "keyword",
|
["#include"] = "keyword",
|
||||||
["#if"] = "keyword",
|
["#if"] = "keyword",
|
||||||
["#ifdef"] = "keyword",
|
["#ifdef"] = "keyword",
|
||||||
["#ifndef"] = "keyword",
|
["#ifndef"] = "keyword",
|
||||||
["#elif"] = "keyword",
|
|
||||||
["#else"] = "keyword",
|
["#else"] = "keyword",
|
||||||
["#elseif"] = "keyword",
|
["#elseif"] = "keyword",
|
||||||
["#endif"] = "keyword",
|
["#endif"] = "keyword",
|
||||||
|
@ -199,5 +117,6 @@ syntax.add {
|
||||||
["#warning"] = "keyword",
|
["#warning"] = "keyword",
|
||||||
["#error"] = "keyword",
|
["#error"] = "keyword",
|
||||||
["#pragma"] = "keyword",
|
["#pragma"] = "keyword",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "CSS",
|
|
||||||
files = { "%.css$" },
|
files = { "%.css$" },
|
||||||
block_comment = { "/*", "*/" },
|
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = "\\.", type = "normal" },
|
{ pattern = "\\.", type = "normal" },
|
||||||
{ pattern = "//.*", type = "comment" },
|
{ pattern = "//.-\n", type = "comment" },
|
||||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||||
|
|
|
@ -1,23 +1,30 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "HTML",
|
|
||||||
files = { "%.html?$" },
|
files = { "%.html?$" },
|
||||||
block_comment = { "<!--", "-->" },
|
|
||||||
patterns = {
|
patterns = {
|
||||||
{
|
{
|
||||||
pattern = {
|
pattern = {
|
||||||
"<%s*[sS][cC][rR][iI][pP][tT]%f[%s>].->",
|
"<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*" ..
|
||||||
"<%s*/%s*[sS][cC][rR][iI][pP][tT]%s*>"
|
"['\"]%a+/[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]['\"]%s*>",
|
||||||
|
"<%s*/[sS][cC][rR][iI][pP][tT]>"
|
||||||
|
},
|
||||||
|
syntax = ".js",
|
||||||
|
type = "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern = {
|
||||||
|
"<%s*[sS][cC][rR][iI][pP][tT]%s*>",
|
||||||
|
"<%s*/%s*[sS][cC][rR][iI][pP][tT]>"
|
||||||
},
|
},
|
||||||
syntax = ".js",
|
syntax = ".js",
|
||||||
type = "function"
|
type = "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern = {
|
pattern = {
|
||||||
"<%s*[sS][tT][yY][lL][eE]%f[%s>].->",
|
"<%s*[sS][tT][yY][lL][eE][^>]*>",
|
||||||
"<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
|
"<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
|
||||||
},
|
},
|
||||||
syntax = ".css",
|
syntax = ".css",
|
||||||
type = "function"
|
type = "function"
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "JavaScript",
|
files = { "%.js$", "%.json$", "%.cson$" },
|
||||||
files = { "%.js$", "%.json$", "%.cson$", "%.mjs$", "%.cjs$" },
|
|
||||||
comment = "//",
|
comment = "//",
|
||||||
block_comment = { "/*", "*/" },
|
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = "//.*", type = "comment" },
|
{ pattern = "//.-\n", type = "comment" },
|
||||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||||
{ pattern = { '/[^= ]', '/', '\\' },type = "string" },
|
{ pattern = { '/%g', '/', '\\' }, type = "string" },
|
||||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||||
{ pattern = { "`", "`", '\\' }, type = "string" },
|
{ pattern = { "`", "`", '\\' }, type = "string" },
|
||||||
{ pattern = "0x[%da-fA-F_]+n?", type = "number" },
|
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
||||||
{ pattern = "-?%d+[%d%.eE_n]*", 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" },
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "Lua",
|
|
||||||
files = "%.lua$",
|
files = "%.lua$",
|
||||||
headers = "^#!.*[ /]lua",
|
headers = "^#!.*[ /]lua",
|
||||||
comment = "--",
|
comment = "--",
|
||||||
block_comment = { "--[[", "]]" },
|
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||||
{ pattern = { "%[%[", "%]%]" }, type = "string" },
|
{ pattern = { "%[%[", "%]%]" }, type = "string" },
|
||||||
{ pattern = { "%-%-%[%[", "%]%]"}, type = "comment" },
|
{ pattern = { "%-%-%[%[", "%]%]"}, type = "comment" },
|
||||||
{ pattern = "%-%-.*", type = "comment" },
|
{ pattern = "%-%-.-\n", type = "comment" },
|
||||||
{ pattern = "0x%x+%.%x*[pP][-+]?%d+", type = "number" },
|
{ pattern = "0x%x+%.%x*[pP][-+]?%d+", type = "number" },
|
||||||
{ pattern = "0x%x+%.%x*", type = "number" },
|
{ pattern = "0x%x+%.%x*", type = "number" },
|
||||||
{ pattern = "0x%.%x+[pP][-+]?%d+", type = "number" },
|
{ pattern = "0x%.%x+[pP][-+]?%d+", type = "number" },
|
||||||
|
|
|
@ -1,235 +1,22 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
local style = require "core.style"
|
|
||||||
local core = require "core"
|
|
||||||
|
|
||||||
local initial_color = style.syntax["keyword2"]
|
|
||||||
|
|
||||||
-- Add 3 type of font styles for use on markdown files
|
|
||||||
for _, attr in pairs({"bold", "italic", "bold_italic"}) do
|
|
||||||
local attributes = {}
|
|
||||||
if attr ~= "bold_italic" then
|
|
||||||
attributes[attr] = true
|
|
||||||
else
|
|
||||||
attributes["bold"] = true
|
|
||||||
attributes["italic"] = true
|
|
||||||
end
|
|
||||||
style.syntax_fonts["markdown_"..attr] = style.code_font:copy(
|
|
||||||
style.code_font:get_size(),
|
|
||||||
attributes
|
|
||||||
)
|
|
||||||
-- also add a color for it
|
|
||||||
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
|
|
||||||
end
|
|
||||||
|
|
||||||
local in_squares_match = "^%[%]"
|
|
||||||
local in_parenthesis_match = "^%(%)"
|
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "Markdown",
|
|
||||||
files = { "%.md$", "%.markdown$" },
|
files = { "%.md$", "%.markdown$" },
|
||||||
block_comment = { "<!--", "-->" },
|
|
||||||
space_handling = false, -- turn off this feature to handle it our selfs
|
|
||||||
patterns = {
|
patterns = {
|
||||||
---- Place patterns that require spaces at start to optimize matching speed
|
{ pattern = "\\.", type = "normal" },
|
||||||
---- and apply the %s+ optimization immediately afterwards
|
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||||
-- bullets
|
{ pattern = { "```", "```" }, type = "string" },
|
||||||
{ pattern = "^%s*%*%s", type = "number" },
|
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||||
{ pattern = "^%s*%-%s", type = "number" },
|
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||||
{ pattern = "^%s*%+%s", type = "number" },
|
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||||
-- numbered bullet
|
{ pattern = "%-%-%-+", type = "comment" },
|
||||||
{ pattern = "^%s*[0-9]+[%.%)]%s", type = "number" },
|
{ pattern = "%*%s+", type = "operator" },
|
||||||
-- blockquote
|
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||||
{ pattern = "^%s*>+%s", type = "string" },
|
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||||
-- alternative bold italic formats
|
{ pattern = "#.-\n", type = "keyword" },
|
||||||
{ pattern = { "%s___", "___%f[%s]" }, type = "markdown_bold_italic" },
|
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||||
{ pattern = { "%s__", "__%f[%s]" }, type = "markdown_bold" },
|
{ pattern = "https?://%S+", type = "function" },
|
||||||
{ pattern = { "%s_[%S]", "_%f[%s]" }, type = "markdown_italic" },
|
|
||||||
-- reference links
|
|
||||||
{
|
|
||||||
pattern = "^%s*%[%^()["..in_squares_match.."]+()%]: ",
|
|
||||||
type = { "function", "number", "function" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern = "^%s*%[%^?()["..in_squares_match.."]+()%]:%s+.*",
|
|
||||||
type = { "function", "number", "function" }
|
|
||||||
},
|
|
||||||
-- optimization
|
|
||||||
{ pattern = "%s+", type = "normal" },
|
|
||||||
|
|
||||||
---- HTML rules imported and adapted from language_html
|
|
||||||
---- to not conflict with markdown rules
|
|
||||||
-- Inline JS and CSS
|
|
||||||
{
|
|
||||||
pattern = {
|
|
||||||
"<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*" ..
|
|
||||||
"['\"]%a+/[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]['\"]%s*>",
|
|
||||||
"<%s*/[sS][cC][rR][iI][pP][tT]>"
|
|
||||||
},
|
|
||||||
syntax = ".js",
|
|
||||||
type = "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern = {
|
|
||||||
"<%s*[sS][cC][rR][iI][pP][tT]%s*>",
|
|
||||||
"<%s*/%s*[sS][cC][rR][iI][pP][tT]>"
|
|
||||||
},
|
|
||||||
syntax = ".js",
|
|
||||||
type = "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern = {
|
|
||||||
"<%s*[sS][tT][yY][lL][eE][^>]*>",
|
|
||||||
"<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
|
|
||||||
},
|
|
||||||
syntax = ".css",
|
|
||||||
type = "function"
|
|
||||||
},
|
|
||||||
-- Comments
|
|
||||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
|
||||||
-- Tags
|
|
||||||
{ pattern = "%f[^<]![%a_][%w_]*", type = "keyword2" },
|
|
||||||
{ pattern = "%f[^<][%a_][%w_]*", type = "function" },
|
|
||||||
{ pattern = "%f[^<]/[%a_][%w_]*", type = "function" },
|
|
||||||
-- Attributes
|
|
||||||
{
|
|
||||||
pattern = "[a-z%-]+%s*()=%s*()\".-\"",
|
|
||||||
type = { "keyword", "operator", "string" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern = "[a-z%-]+%s*()=%s*()'.-'",
|
|
||||||
type = { "keyword", "operator", "string" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern = "[a-z%-]+%s*()=%s*()%-?%d[%d%.]*",
|
|
||||||
type = { "keyword", "operator", "number" }
|
|
||||||
},
|
|
||||||
-- Entities
|
|
||||||
{ pattern = "&#?[a-zA-Z0-9]+;", type = "keyword2" },
|
|
||||||
|
|
||||||
---- Markdown rules
|
|
||||||
-- math
|
|
||||||
{ pattern = { "%$%$", "%$%$", "\\" }, type = "string", syntax = ".tex"},
|
|
||||||
{ regex = { "\\$", [[\$|(?=\\*\n)]], "\\" }, type = "string", syntax = ".tex"},
|
|
||||||
-- code blocks
|
|
||||||
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
|
|
||||||
{ pattern = { "```cpp", "```" }, type = "string", syntax = ".cpp" },
|
|
||||||
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" },
|
|
||||||
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
|
|
||||||
{ pattern = { "```perl", "```" }, type = "string", syntax = ".pl" },
|
|
||||||
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
|
|
||||||
{ pattern = { "```javascript", "```" }, type = "string", syntax = ".js" },
|
|
||||||
{ pattern = { "```json", "```" }, type = "string", syntax = ".js" },
|
|
||||||
{ pattern = { "```html", "```" }, type = "string", syntax = ".html" },
|
|
||||||
{ pattern = { "```ini", "```" }, type = "string", syntax = ".ini" },
|
|
||||||
{ pattern = { "```xml", "```" }, type = "string", syntax = ".xml" },
|
|
||||||
{ pattern = { "```css", "```" }, type = "string", syntax = ".css" },
|
|
||||||
{ pattern = { "```lua", "```" }, type = "string", syntax = ".lua" },
|
|
||||||
{ pattern = { "```bash", "```" }, type = "string", syntax = ".sh" },
|
|
||||||
{ pattern = { "```sh", "```" }, type = "string", syntax = ".sh" },
|
|
||||||
{ pattern = { "```java", "```" }, type = "string", syntax = ".java" },
|
|
||||||
{ pattern = { "```c#", "```" }, type = "string", syntax = ".cs" },
|
|
||||||
{ pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" },
|
|
||||||
{ pattern = { "```d", "```" }, type = "string", syntax = ".d" },
|
|
||||||
{ pattern = { "```glsl", "```" }, type = "string", syntax = ".glsl" },
|
|
||||||
{ pattern = { "```c", "```" }, type = "string", syntax = ".c" },
|
|
||||||
{ pattern = { "```julia", "```" }, type = "string", syntax = ".jl" },
|
|
||||||
{ pattern = { "```rust", "```" }, type = "string", syntax = ".rs" },
|
|
||||||
{ pattern = { "```dart", "```" }, type = "string", syntax = ".dart" },
|
|
||||||
{ pattern = { "```v", "```" }, type = "string", syntax = ".v" },
|
|
||||||
{ pattern = { "```toml", "```" }, type = "string", syntax = ".toml" },
|
|
||||||
{ pattern = { "```yaml", "```" }, type = "string", syntax = ".yaml" },
|
|
||||||
{ pattern = { "```nim", "```" }, type = "string", syntax = ".nim" },
|
|
||||||
{ pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" },
|
|
||||||
{ pattern = { "```rescript", "```" }, type = "string", syntax = ".res" },
|
|
||||||
{ pattern = { "```moon", "```" }, type = "string", syntax = ".moon" },
|
|
||||||
{ pattern = { "```go", "```" }, type = "string", syntax = ".go" },
|
|
||||||
{ pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" },
|
|
||||||
{ pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" },
|
|
||||||
{ pattern = { "```", "```" }, type = "string" },
|
|
||||||
{ pattern = { "``", "``" }, type = "string" },
|
|
||||||
{ pattern = { "%f[\\`]%`[%S]", "`" }, type = "string" },
|
|
||||||
-- strike
|
|
||||||
{ pattern = { "~~", "~~" }, type = "keyword2" },
|
|
||||||
-- highlight
|
|
||||||
{ pattern = { "==", "==" }, type = "literal" },
|
|
||||||
-- lines
|
|
||||||
{ pattern = "^%-%-%-+$" , type = "comment" },
|
|
||||||
{ pattern = "^%*%*%*+$", type = "comment" },
|
|
||||||
{ pattern = "^___+$", type = "comment" },
|
|
||||||
-- bold and italic
|
|
||||||
{ pattern = { "%*%*%*%S", "%*%*%*" }, type = "markdown_bold_italic" },
|
|
||||||
{ pattern = { "%*%*%S", "%*%*" }, type = "markdown_bold" },
|
|
||||||
-- handle edge case where asterisk can be at end of line and not close
|
|
||||||
{
|
|
||||||
pattern = { "%f[\\%*]%*[%S]", "%*%f[^%*]" },
|
|
||||||
type = "markdown_italic"
|
|
||||||
},
|
|
||||||
-- alternative bold italic formats
|
|
||||||
{ pattern = "^___[%s%p%w]+___%s" , type = "markdown_bold_italic" },
|
|
||||||
{ pattern = "^__[%s%p%w]+__%s" , type = "markdown_bold" },
|
|
||||||
{ pattern = "^_[%s%p%w]+_%s" , type = "markdown_italic" },
|
|
||||||
-- heading with custom id
|
|
||||||
{
|
|
||||||
pattern = "^#+%s[%w%s%p]+(){()#[%w%-]+()}",
|
|
||||||
type = { "keyword", "function", "string", "function" }
|
|
||||||
},
|
|
||||||
-- headings
|
|
||||||
{ pattern = "^#+%s.+$", type = "keyword" },
|
|
||||||
-- superscript and subscript
|
|
||||||
{
|
|
||||||
pattern = "%^()%d+()%^",
|
|
||||||
type = { "function", "number", "function" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern = "%~()%d+()%~",
|
|
||||||
type = { "function", "number", "function" }
|
|
||||||
},
|
|
||||||
-- definitions
|
|
||||||
{ pattern = "^:%s.+", type = "function" },
|
|
||||||
-- emoji
|
|
||||||
{ pattern = ":[a-zA-Z0-9_%-]+:", type = "literal" },
|
|
||||||
-- images and link
|
|
||||||
{
|
|
||||||
pattern = "!?%[!?%[()["..in_squares_match.."]+()%]%(()["..in_parenthesis_match.."]+()%)%]%(()["..in_parenthesis_match.."]+()%)",
|
|
||||||
type = { "function", "string", "function", "number", "function", "number", "function" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern = "!?%[!?%[?()["..in_squares_match.."]+()%]?%]%(()["..in_parenthesis_match.."]+()%)",
|
|
||||||
type = { "function", "string", "function", "number", "function" }
|
|
||||||
},
|
|
||||||
-- reference links
|
|
||||||
{
|
|
||||||
pattern = "%[()["..in_squares_match.."]+()%] *()%[()["..in_squares_match.."]+()%]",
|
|
||||||
type = { "function", "string", "function", "function", "number", "function" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern = "!?%[%^?()["..in_squares_match.."]+()%]",
|
|
||||||
type = { "function", "number", "function" }
|
|
||||||
},
|
|
||||||
-- url's and email
|
|
||||||
{
|
|
||||||
pattern = "<[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+%.[a-zA-Z0-9-.]+>",
|
|
||||||
type = "function"
|
|
||||||
},
|
|
||||||
{ pattern = "<https?://%S+>", type = "function" },
|
|
||||||
{ pattern = "https?://%S+", type = "function" },
|
|
||||||
-- optimize consecutive dashes used in tables
|
|
||||||
{ pattern = "%-+", type = "normal" },
|
|
||||||
},
|
},
|
||||||
symbols = { },
|
symbols = { },
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Adjust the color on theme changes
|
|
||||||
core.add_thread(function()
|
|
||||||
while true do
|
|
||||||
if initial_color ~= style.syntax["keyword2"] then
|
|
||||||
for _, attr in pairs({"bold", "italic", "bold_italic"}) do
|
|
||||||
style.syntax["markdown_"..attr] = style.syntax["keyword2"]
|
|
||||||
end
|
|
||||||
initial_color = style.syntax["keyword2"]
|
|
||||||
end
|
|
||||||
coroutine.yield(1)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,21 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "Python",
|
files = { "%.py$", "%.pyw$" },
|
||||||
files = { "%.py$", "%.pyw$", "%.rpy$" },
|
|
||||||
headers = "^#!.*[ /]python",
|
headers = "^#!.*[ /]python",
|
||||||
comment = "#",
|
comment = "#",
|
||||||
block_comment = { '"""', '"""' },
|
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = "#.*", type = "comment" },
|
{ pattern = { "#", "\n" }, type = "comment" },
|
||||||
{ pattern = { '^%s*"""', '"""' }, type = "comment" },
|
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
|
||||||
{ pattern = '[uUrR]%f["]', type = "keyword" },
|
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
|
||||||
{ pattern = "class%s+()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
{ pattern = { '"""', '"""' }, type = "string" },
|
||||||
{ pattern = { '[ruU]?"""', '"""'; '\\' }, type = "string" },
|
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
||||||
{ pattern = { "[ruU]?'''", "'''", '\\' }, type = "string" },
|
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
||||||
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
|
{ pattern = "-?%.?%d+", type = "number" },
|
||||||
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
|
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||||
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||||
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||||
{ pattern = "-?%.?%d+", type = "number" },
|
|
||||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
|
||||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
|
||||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
|
||||||
},
|
},
|
||||||
symbols = {
|
symbols = {
|
||||||
["class"] = "keyword",
|
["class"] = "keyword",
|
||||||
|
@ -33,8 +27,6 @@ syntax.add {
|
||||||
["lambda"] = "keyword",
|
["lambda"] = "keyword",
|
||||||
["try"] = "keyword",
|
["try"] = "keyword",
|
||||||
["def"] = "keyword",
|
["def"] = "keyword",
|
||||||
["async"] = "keyword",
|
|
||||||
["await"] = "keyword",
|
|
||||||
["from"] = "keyword",
|
["from"] = "keyword",
|
||||||
["nonlocal"] = "keyword",
|
["nonlocal"] = "keyword",
|
||||||
["while"] = "keyword",
|
["while"] = "keyword",
|
||||||
|
@ -47,8 +39,6 @@ syntax.add {
|
||||||
["if"] = "keyword",
|
["if"] = "keyword",
|
||||||
["or"] = "keyword",
|
["or"] = "keyword",
|
||||||
["else"] = "keyword",
|
["else"] = "keyword",
|
||||||
["match"] = "keyword",
|
|
||||||
["case"] = "keyword",
|
|
||||||
["import"] = "keyword",
|
["import"] = "keyword",
|
||||||
["pass"] = "keyword",
|
["pass"] = "keyword",
|
||||||
["break"] = "keyword",
|
["break"] = "keyword",
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "XML",
|
|
||||||
files = { "%.xml$" },
|
files = { "%.xml$" },
|
||||||
headers = "<%?xml",
|
headers = "<%?xml",
|
||||||
block_comment = { "<!--", "-->" },
|
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||||
{ pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },
|
{ pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },
|
||||||
|
|
|
@ -1,115 +0,0 @@
|
||||||
-- mod-version:3
|
|
||||||
local common = require "core.common"
|
|
||||||
local command = require "core.command"
|
|
||||||
local config = require "core.config"
|
|
||||||
local style = require "core.style"
|
|
||||||
local DocView = require "core.docview"
|
|
||||||
local CommandView = require "core.commandview"
|
|
||||||
|
|
||||||
config.plugins.lineguide = common.merge({
|
|
||||||
enabled = false,
|
|
||||||
width = 2,
|
|
||||||
rulers = {
|
|
||||||
-- 80,
|
|
||||||
-- 100,
|
|
||||||
-- 120,
|
|
||||||
config.line_limit
|
|
||||||
},
|
|
||||||
-- The config specification used by gui generators
|
|
||||||
config_spec = {
|
|
||||||
name = "Line Guide",
|
|
||||||
{
|
|
||||||
label = "Enabled",
|
|
||||||
description = "Disable or enable drawing of the line guide.",
|
|
||||||
path = "enabled",
|
|
||||||
type = "toggle",
|
|
||||||
default = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Width",
|
|
||||||
description = "Width in pixels of the line guide.",
|
|
||||||
path = "width",
|
|
||||||
type = "number",
|
|
||||||
default = 2,
|
|
||||||
min = 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Ruler Positions",
|
|
||||||
description = "The different column numbers for the line guides to draw.",
|
|
||||||
path = "rulers",
|
|
||||||
type = "list_strings",
|
|
||||||
default = { tostring(config.line_limit) or "80" },
|
|
||||||
get_value = function(rulers)
|
|
||||||
if type(rulers) == "table" then
|
|
||||||
local new_rulers = {}
|
|
||||||
for _, ruler in ipairs(rulers) do
|
|
||||||
table.insert(new_rulers, tostring(ruler))
|
|
||||||
end
|
|
||||||
return new_rulers
|
|
||||||
else
|
|
||||||
return { tostring(config.line_limit) }
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
set_value = function(rulers)
|
|
||||||
local new_rulers = {}
|
|
||||||
for _, ruler in ipairs(rulers) do
|
|
||||||
local number = tonumber(ruler)
|
|
||||||
if number then
|
|
||||||
table.insert(new_rulers, number)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #new_rulers == 0 then
|
|
||||||
table.insert(new_rulers, config.line_limit)
|
|
||||||
end
|
|
||||||
return new_rulers
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, config.plugins.lineguide)
|
|
||||||
|
|
||||||
local function get_ruler(v)
|
|
||||||
local result = nil
|
|
||||||
if type(v) == 'number' then
|
|
||||||
result = { columns = v }
|
|
||||||
elseif type(v) == 'table' then
|
|
||||||
result = v
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
local draw_overlay = DocView.draw_overlay
|
|
||||||
function DocView:draw_overlay(...)
|
|
||||||
draw_overlay(self, ...)
|
|
||||||
|
|
||||||
if
|
|
||||||
type(config.plugins.lineguide) == "table"
|
|
||||||
and
|
|
||||||
config.plugins.lineguide.enabled
|
|
||||||
and
|
|
||||||
not self:is(CommandView)
|
|
||||||
then
|
|
||||||
local line_x = self:get_line_screen_position(1)
|
|
||||||
local character_width = self:get_font():get_width("n")
|
|
||||||
local ruler_width = config.plugins.lineguide.width
|
|
||||||
local ruler_color = style.guide or style.selection
|
|
||||||
|
|
||||||
for k,v in ipairs(config.plugins.lineguide.rulers) do
|
|
||||||
local ruler = get_ruler(v)
|
|
||||||
|
|
||||||
if ruler then
|
|
||||||
local x = line_x + (character_width * ruler.columns)
|
|
||||||
local y = self.position.y
|
|
||||||
local w = ruler_width
|
|
||||||
local h = self.size.y
|
|
||||||
|
|
||||||
renderer.draw_rect(x, y, w, h, ruler.color or ruler_color)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
command.add(nil, {
|
|
||||||
["lineguide:toggle"] = function()
|
|
||||||
config.plugins.lineguide.enabled = not config.plugins.lineguide.enabled
|
|
||||||
end
|
|
||||||
})
|
|
|
@ -1,581 +0,0 @@
|
||||||
-- mod-version:3 --priority:10
|
|
||||||
local core = require "core"
|
|
||||||
local common = require "core.common"
|
|
||||||
local DocView = require "core.docview"
|
|
||||||
local Doc = require "core.doc"
|
|
||||||
local style = require "core.style"
|
|
||||||
local config = require "core.config"
|
|
||||||
local command = require "core.command"
|
|
||||||
local keymap = require "core.keymap"
|
|
||||||
local translate = require "core.doc.translate"
|
|
||||||
|
|
||||||
|
|
||||||
config.plugins.linewrapping = common.merge({
|
|
||||||
-- The type of wrapping to perform. Can be "letter" or "word".
|
|
||||||
mode = "letter",
|
|
||||||
-- If nil, uses the DocView's size, otherwise, uses this exact width. Can be a function.
|
|
||||||
width_override = nil,
|
|
||||||
-- Whether or not to draw a guide
|
|
||||||
guide = true,
|
|
||||||
-- Whether or not we should indent ourselves like the first line of a wrapped block.
|
|
||||||
indent = true,
|
|
||||||
-- Whether or not to enable wrapping by default when opening files.
|
|
||||||
enable_by_default = false,
|
|
||||||
-- Requires tokenization
|
|
||||||
require_tokenization = false,
|
|
||||||
-- The config specification used by gui generators
|
|
||||||
config_spec = {
|
|
||||||
name = "Line Wrapping",
|
|
||||||
{
|
|
||||||
label = "Mode",
|
|
||||||
description = "The type of wrapping to perform.",
|
|
||||||
path = "mode",
|
|
||||||
type = "selection",
|
|
||||||
default = "letter",
|
|
||||||
values = {
|
|
||||||
{"Letters", "letter"},
|
|
||||||
{"Words", "word"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Guide",
|
|
||||||
description = "Whether or not to draw a guide.",
|
|
||||||
path = "guide",
|
|
||||||
type = "toggle",
|
|
||||||
default = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Indent",
|
|
||||||
description = "Whether or not to follow the indentation of wrapped line.",
|
|
||||||
path = "indent",
|
|
||||||
type = "toggle",
|
|
||||||
default = true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Enable by Default",
|
|
||||||
description = "Whether or not to enable wrapping by default when opening files.",
|
|
||||||
path = "enable_by_default",
|
|
||||||
type = "toggle",
|
|
||||||
default = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Require Tokenization",
|
|
||||||
description = "Use tokenization when applying wrapping.",
|
|
||||||
path = "require_tokenization",
|
|
||||||
type = "toggle",
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, config.plugins.linewrapping)
|
|
||||||
|
|
||||||
local LineWrapping = {}
|
|
||||||
|
|
||||||
-- Optimzation function. The tokenizer is relatively slow (at present), and
|
|
||||||
-- so if we don't need to run it, should be run sparingly.
|
|
||||||
local function spew_tokens(doc, line) if line < math.huge then return math.huge, "normal", doc.lines[line] end end
|
|
||||||
local function get_tokens(doc, line)
|
|
||||||
if config.plugins.linewrapping.require_tokenization then
|
|
||||||
return doc.highlighter:each_token(line)
|
|
||||||
end
|
|
||||||
return spew_tokens, doc, line
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Computes the breaks for a given line, width and mode. Returns a list of columns
|
|
||||||
-- at which the line should be broken.
|
|
||||||
function LineWrapping.compute_line_breaks(doc, default_font, line, width, mode)
|
|
||||||
local xoffset, last_i, i, last_space, last_width, begin_width = 0, 1, 1, nil, 0, 0
|
|
||||||
local splits = { 1 }
|
|
||||||
for idx, type, text in get_tokens(doc, line) do
|
|
||||||
local font = style.syntax_fonts[type] or default_font
|
|
||||||
if idx == 1 or idx == math.huge and config.plugins.linewrapping.indent then
|
|
||||||
local _, indent_end = text:find("^%s+")
|
|
||||||
if indent_end then begin_width = font:get_width(text:sub(1, indent_end)) end
|
|
||||||
end
|
|
||||||
local w = font:get_width(text)
|
|
||||||
if xoffset + w > width then
|
|
||||||
for char in common.utf8_chars(text) do
|
|
||||||
w = font:get_width(char)
|
|
||||||
xoffset = xoffset + w
|
|
||||||
if xoffset > width then
|
|
||||||
if mode == "word" and last_space then
|
|
||||||
table.insert(splits, last_space + 1)
|
|
||||||
xoffset = w + begin_width + (xoffset - last_width)
|
|
||||||
else
|
|
||||||
table.insert(splits, i)
|
|
||||||
xoffset = w + begin_width
|
|
||||||
end
|
|
||||||
last_space = nil
|
|
||||||
elseif char == ' ' then
|
|
||||||
last_space = i
|
|
||||||
last_width = xoffset
|
|
||||||
end
|
|
||||||
i = i + #char
|
|
||||||
end
|
|
||||||
else
|
|
||||||
xoffset = xoffset + w
|
|
||||||
i = i + #text
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return splits, begin_width
|
|
||||||
end
|
|
||||||
|
|
||||||
-- breaks are held in a single table that contains n*2 elements, where n is the amount of line breaks.
|
|
||||||
-- each element represents line and column of the break. line_offset will check from the specified line
|
|
||||||
-- if the first line has not changed breaks, it will stop there.
|
|
||||||
function LineWrapping.reconstruct_breaks(docview, default_font, width, line_offset)
|
|
||||||
if width ~= math.huge then
|
|
||||||
local doc = docview.doc
|
|
||||||
-- two elements per wrapped line; first maps to original line number, second to column number.
|
|
||||||
docview.wrapped_lines = { }
|
|
||||||
-- one element per actual line; maps to the first index of in wrapped_lines for this line
|
|
||||||
docview.wrapped_line_to_idx = { }
|
|
||||||
-- one element per actual line; gives the indent width for the acutal line
|
|
||||||
docview.wrapped_line_offsets = { }
|
|
||||||
docview.wrapped_settings = { ["width"] = width, ["font"] = default_font }
|
|
||||||
for i = line_offset or 1, #doc.lines do
|
|
||||||
local breaks, offset = LineWrapping.compute_line_breaks(doc, default_font, i, width, config.plugins.linewrapping.mode)
|
|
||||||
table.insert(docview.wrapped_line_offsets, offset)
|
|
||||||
for k, col in ipairs(breaks) do
|
|
||||||
table.insert(docview.wrapped_lines, i)
|
|
||||||
table.insert(docview.wrapped_lines, col)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- list of indices for wrapped_lines, that are based on original line number
|
|
||||||
-- holds the index to the first in the wrapped_lines list.
|
|
||||||
local last_wrap = nil
|
|
||||||
for i = 1, #docview.wrapped_lines, 2 do
|
|
||||||
if not last_wrap or last_wrap ~= docview.wrapped_lines[i] then
|
|
||||||
table.insert(docview.wrapped_line_to_idx, (i + 1) / 2)
|
|
||||||
last_wrap = docview.wrapped_lines[i]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
docview.wrapped_lines = nil
|
|
||||||
docview.wrapped_line_to_idx = nil
|
|
||||||
docview.wrapped_line_offsets = nil
|
|
||||||
docview.wrapped_settings = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- When we have an insertion or deletion, we have four sections of text.
|
|
||||||
-- 1. The unaffected section, located prior to the cursor. This is completely ignored.
|
|
||||||
-- 2. The beginning of the affected line prior to the insertion or deletion. Begins on column 1 of the selection.
|
|
||||||
-- 3. The removed/pasted lines.
|
|
||||||
-- 4. Every line after the modification, begins one line after the selection in the initial document.
|
|
||||||
function LineWrapping.update_breaks(docview, old_line1, old_line2, net_lines)
|
|
||||||
-- Step 1: Determine the index for the line for #2.
|
|
||||||
local old_idx1 = docview.wrapped_line_to_idx[old_line1] or 1
|
|
||||||
-- Step 2: Determine the index of the line for #4.
|
|
||||||
local old_idx2 = (docview.wrapped_line_to_idx[old_line2 + 1] or ((#docview.wrapped_lines / 2) + 1)) - 1
|
|
||||||
-- Step 3: Remove all old breaks for the old lines from the table, and all old widths from wrapped_line_offsets.
|
|
||||||
local offset = (old_idx1 - 1) * 2 + 1
|
|
||||||
for i = old_idx1, old_idx2 do
|
|
||||||
table.remove(docview.wrapped_lines, offset)
|
|
||||||
table.remove(docview.wrapped_lines, offset)
|
|
||||||
end
|
|
||||||
for i = old_line1, old_line2 do
|
|
||||||
table.remove(docview.wrapped_line_offsets, old_line1)
|
|
||||||
end
|
|
||||||
-- Step 4: Shift the line number of wrapped_lines past #4 by the amount of inserted/deleted lines.
|
|
||||||
if net_lines ~= 0 then
|
|
||||||
for i = offset, #docview.wrapped_lines, 2 do
|
|
||||||
docview.wrapped_lines[i] = docview.wrapped_lines[i] + net_lines
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Step 5: Compute the breaks and offsets for the lines for #2 and #3. Insert them into the table.
|
|
||||||
local new_line1 = old_line1
|
|
||||||
local new_line2 = old_line2 + net_lines
|
|
||||||
for line = new_line1, new_line2 do
|
|
||||||
local breaks, begin_width = LineWrapping.compute_line_breaks(docview.doc, docview.wrapped_settings.font, line, docview.wrapped_settings.width, config.plugins.linewrapping.mode)
|
|
||||||
table.insert(docview.wrapped_line_offsets, line, begin_width)
|
|
||||||
for i,b in ipairs(breaks) do
|
|
||||||
table.insert(docview.wrapped_lines, offset, b)
|
|
||||||
table.insert(docview.wrapped_lines, offset, line)
|
|
||||||
offset = offset + 2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Step 6: Recompute the wrapped_line_to_idx cache from #2.
|
|
||||||
local line = old_line1
|
|
||||||
offset = (old_idx1 - 1) * 2 + 1
|
|
||||||
while offset < #docview.wrapped_lines do
|
|
||||||
if docview.wrapped_lines[offset + 1] == 1 then
|
|
||||||
docview.wrapped_line_to_idx[line] = ((offset - 1) / 2) + 1
|
|
||||||
line = line + 1
|
|
||||||
end
|
|
||||||
offset = offset + 2
|
|
||||||
end
|
|
||||||
while line <= #docview.wrapped_line_to_idx do
|
|
||||||
table.remove(docview.wrapped_line_to_idx)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Draws a guide if applicable to show where wrapping is occurring.
|
|
||||||
function LineWrapping.draw_guide(docview)
|
|
||||||
if config.plugins.linewrapping.guide and docview.wrapped_settings.width ~= math.huge then
|
|
||||||
local x, y = docview:get_content_offset()
|
|
||||||
local gw = docview:get_gutter_width()
|
|
||||||
renderer.draw_rect(x + gw + docview.wrapped_settings.width, y, 1, core.root_view.size.y, style.selection)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function LineWrapping.update_docview_breaks(docview)
|
|
||||||
local x,y,w,h = docview:get_scrollbar_rect()
|
|
||||||
local width = (type(config.plugins.linewrapping.width_override) == "function" and config.plugins.linewrapping.width_override(docview))
|
|
||||||
or config.plugins.linewrapping.width_override or (docview.size.x - docview:get_gutter_width() - w)
|
|
||||||
if (not docview.wrapped_settings or docview.wrapped_settings.width == nil or width ~= docview.wrapped_settings.width) then
|
|
||||||
docview.scroll.to.x = 0
|
|
||||||
LineWrapping.reconstruct_breaks(docview, docview:get_font(), width)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_idx_line_col(docview, idx)
|
|
||||||
local doc = docview.doc
|
|
||||||
if not docview.wrapped_settings then
|
|
||||||
if idx > #doc.lines then return #doc.lines, #doc.lines[#doc.lines] + 1 end
|
|
||||||
return idx, 1
|
|
||||||
end
|
|
||||||
if idx < 1 then return 1, 1 end
|
|
||||||
local offset = (idx - 1) * 2 + 1
|
|
||||||
if offset > #docview.wrapped_lines then return #doc.lines, #doc.lines[#doc.lines] + 1 end
|
|
||||||
return docview.wrapped_lines[offset], docview.wrapped_lines[offset + 1]
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_idx_line_length(docview, idx)
|
|
||||||
local doc = docview.doc
|
|
||||||
if not docview.wrapped_settings then
|
|
||||||
if idx > #doc.lines then return #doc.lines[#doc.lines] + 1 end
|
|
||||||
return #doc.lines[idx]
|
|
||||||
end
|
|
||||||
local offset = (idx - 1) * 2 + 1
|
|
||||||
local start = docview.wrapped_lines[offset + 1]
|
|
||||||
if docview.wrapped_lines[offset + 2] and docview.wrapped_lines[offset + 2] == docview.wrapped_lines[offset] then
|
|
||||||
return docview.wrapped_lines[offset + 3] - docview.wrapped_lines[offset + 1]
|
|
||||||
else
|
|
||||||
return #doc.lines[docview.wrapped_lines[offset]] - docview.wrapped_lines[offset + 1] + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_total_wrapped_lines(docview)
|
|
||||||
if not docview.wrapped_settings then return docview.doc and #docview.doc.lines end
|
|
||||||
return #docview.wrapped_lines / 2
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If line end, gives the end of an index line, rather than the first character of the next line.
|
|
||||||
local function get_line_idx_col_count(docview, line, col, line_end, ndoc)
|
|
||||||
local doc = docview.doc
|
|
||||||
if not docview.wrapped_settings then return common.clamp(line, 1, #doc.lines), col, 1, 1 end
|
|
||||||
if line > #doc.lines then return get_line_idx_col_count(docview, #doc.lines, #doc.lines[#doc.lines] + 1) end
|
|
||||||
line = math.max(line, 1)
|
|
||||||
local idx = docview.wrapped_line_to_idx[line] or 1
|
|
||||||
local ncol, scol = 1, 1
|
|
||||||
if col then
|
|
||||||
local i = idx + 1
|
|
||||||
while line == docview.wrapped_lines[(i - 1) * 2 + 1] and col >= docview.wrapped_lines[(i - 1) * 2 + 2] do
|
|
||||||
local nscol = docview.wrapped_lines[(i - 1) * 2 + 2]
|
|
||||||
if line_end and col == nscol then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
scol = nscol
|
|
||||||
i = i + 1
|
|
||||||
idx = idx + 1
|
|
||||||
end
|
|
||||||
ncol = (col - scol) + 1
|
|
||||||
end
|
|
||||||
local count = (docview.wrapped_line_to_idx[line + 1] or (get_total_wrapped_lines(docview) + 1)) - (docview.wrapped_line_to_idx[line] or get_total_wrapped_lines(docview))
|
|
||||||
return idx, ncol, count, scol
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_line_col_from_index_and_x(docview, idx, x)
|
|
||||||
local doc = docview.doc
|
|
||||||
local line, col = get_idx_line_col(docview, idx)
|
|
||||||
if idx < 1 then return 1, 1 end
|
|
||||||
local xoffset, last_i, i = (col ~= 1 and docview.wrapped_line_offsets[line] or 0), col, 1
|
|
||||||
if x < xoffset then return line, col end
|
|
||||||
local default_font = docview:get_font()
|
|
||||||
for _, type, text in doc.highlighter:each_token(line) do
|
|
||||||
local font, w = style.syntax_fonts[type] or default_font, 0
|
|
||||||
for char in common.utf8_chars(text) do
|
|
||||||
if i >= col then
|
|
||||||
if xoffset >= x then
|
|
||||||
return line, (xoffset - x > (w / 2) and last_i or i)
|
|
||||||
end
|
|
||||||
w = font:get_width(char)
|
|
||||||
xoffset = xoffset + w
|
|
||||||
end
|
|
||||||
last_i = i
|
|
||||||
i = i + #char
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return line, #doc.lines[line]
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local open_files = {}
|
|
||||||
|
|
||||||
local old_doc_insert = Doc.raw_insert
|
|
||||||
function Doc:raw_insert(line, col, text, undo_stack, time)
|
|
||||||
local old_lines = #self.lines
|
|
||||||
old_doc_insert(self, line, col, text, undo_stack, time)
|
|
||||||
if open_files[self] then
|
|
||||||
for i,docview in ipairs(open_files[self]) do
|
|
||||||
if docview.wrapped_settings then
|
|
||||||
local lines = #self.lines - old_lines
|
|
||||||
LineWrapping.update_breaks(docview, line, line, lines)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_doc_remove = Doc.raw_remove
|
|
||||||
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
|
||||||
local old_lines = #self.lines
|
|
||||||
old_doc_remove(self, line1, col1, line2, col2, undo_stack, time)
|
|
||||||
if open_files[self] then
|
|
||||||
for i,docview in ipairs(open_files[self]) do
|
|
||||||
if docview.wrapped_settings then
|
|
||||||
local lines = #self.lines - old_lines
|
|
||||||
LineWrapping.update_breaks(docview, line1, line2, lines)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_doc_update = DocView.update
|
|
||||||
function DocView:update()
|
|
||||||
old_doc_update(self)
|
|
||||||
if self.wrapped_settings and self.size.x > 0 then
|
|
||||||
LineWrapping.update_docview_breaks(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function DocView:get_scrollable_size()
|
|
||||||
if not config.scroll_past_end then
|
|
||||||
return self:get_line_height() * get_total_wrapped_lines(self) + style.padding.y * 2
|
|
||||||
end
|
|
||||||
return self:get_line_height() * (get_total_wrapped_lines(self) - 1) + self.size.y
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_new = DocView.new
|
|
||||||
function DocView:new(doc)
|
|
||||||
old_new(self, doc)
|
|
||||||
if not open_files[doc] then open_files[doc] = {} end
|
|
||||||
table.insert(open_files[doc], self)
|
|
||||||
if config.plugins.linewrapping.enable_by_default then
|
|
||||||
LineWrapping.update_docview_breaks(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_scroll_to_make_visible = DocView.scroll_to_make_visible
|
|
||||||
function DocView:scroll_to_make_visible(line, col)
|
|
||||||
old_scroll_to_make_visible(self, line, col)
|
|
||||||
if self.wrapped_settings then self.scroll.to.x = 0 end
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_get_visible_line_range = DocView.get_visible_line_range
|
|
||||||
function DocView:get_visible_line_range()
|
|
||||||
if not self.wrapped_settings then return old_get_visible_line_range(self) end
|
|
||||||
local x, y, x2, y2 = self:get_content_bounds()
|
|
||||||
local lh = self:get_line_height()
|
|
||||||
local minline = get_idx_line_col(self, math.max(1, math.floor(y / lh)))
|
|
||||||
local maxline = get_idx_line_col(self, math.min(get_total_wrapped_lines(self), math.floor(y2 / lh) + 1))
|
|
||||||
return minline, maxline
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_get_x_offset_col = DocView.get_x_offset_col
|
|
||||||
function DocView:get_x_offset_col(line, x)
|
|
||||||
if not self.wrapped_settings then return old_get_x_offset_col(self, line, x) end
|
|
||||||
local idx = get_line_idx_col_count(self, line)
|
|
||||||
return get_line_col_from_index_and_x(self, idx, x)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If line end is true, returns the end of the previous line, in a multi-line break.
|
|
||||||
local old_get_col_x_offset = DocView.get_col_x_offset
|
|
||||||
function DocView:get_col_x_offset(line, col, line_end)
|
|
||||||
if not self.wrapped_settings then return old_get_col_x_offset(self, line, col) end
|
|
||||||
local idx, ncol, count, scol = get_line_idx_col_count(self, line, col, line_end)
|
|
||||||
local xoffset, i = (scol ~= 1 and self.wrapped_line_offsets[line] or 0), 1
|
|
||||||
local default_font = self:get_font()
|
|
||||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
|
||||||
if i + #text >= scol then
|
|
||||||
if i < scol then
|
|
||||||
text = text:sub(scol - i + 1)
|
|
||||||
i = scol
|
|
||||||
end
|
|
||||||
local font = style.syntax_fonts[type] or default_font
|
|
||||||
for char in common.utf8_chars(text) do
|
|
||||||
if i >= col then
|
|
||||||
return xoffset
|
|
||||||
end
|
|
||||||
xoffset = xoffset + font:get_width(char)
|
|
||||||
i = i + #char
|
|
||||||
end
|
|
||||||
else
|
|
||||||
i = i + #text
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return xoffset
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_get_line_screen_position = DocView.get_line_screen_position
|
|
||||||
function DocView:get_line_screen_position(line, col)
|
|
||||||
if not self.wrapped_settings then return old_get_line_screen_position(self, line, col) end
|
|
||||||
local idx, ncol, count = get_line_idx_col_count(self, line, col)
|
|
||||||
local x, y = self:get_content_offset()
|
|
||||||
local lh = self:get_line_height()
|
|
||||||
local gw = self:get_gutter_width()
|
|
||||||
return x + gw + (col and self:get_col_x_offset(line, col) or 0), y + (idx-1) * lh + style.padding.y
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_resolve_screen_position = DocView.resolve_screen_position
|
|
||||||
function DocView:resolve_screen_position(x, y)
|
|
||||||
if not self.wrapped_settings then return old_resolve_screen_position(self, x, y) end
|
|
||||||
local ox, oy = self:get_line_screen_position(1)
|
|
||||||
local idx = common.clamp(math.floor((y - oy) / self:get_line_height()) + 1, 1, get_total_wrapped_lines(self))
|
|
||||||
return get_line_col_from_index_and_x(self, idx, x - ox)
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_draw_line_text = DocView.draw_line_text
|
|
||||||
function DocView:draw_line_text(line, x, y)
|
|
||||||
if not self.wrapped_settings then return old_draw_line_text(self, line, x, y) end
|
|
||||||
local default_font = self:get_font()
|
|
||||||
local tx, ty, begin_width = x, y + self:get_line_text_y_offset(), self.wrapped_line_offsets[line]
|
|
||||||
local lh = self:get_line_height()
|
|
||||||
local idx, _, count = get_line_idx_col_count(self, line)
|
|
||||||
local total_offset = 1
|
|
||||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
|
||||||
local color = style.syntax[type]
|
|
||||||
local font = style.syntax_fonts[type] or default_font
|
|
||||||
local token_offset = 1
|
|
||||||
-- Split tokens if we're at the end of the document.
|
|
||||||
while text ~= nil and token_offset <= #text do
|
|
||||||
local next_line, next_line_start_col = get_idx_line_col(self, idx + 1)
|
|
||||||
if next_line ~= line then
|
|
||||||
next_line_start_col = #self.doc.lines[line]
|
|
||||||
end
|
|
||||||
local max_length = next_line_start_col - total_offset
|
|
||||||
local rendered_text = text:sub(token_offset, token_offset + max_length - 1)
|
|
||||||
tx = renderer.draw_text(font, rendered_text, tx, ty, color)
|
|
||||||
total_offset = total_offset + #rendered_text
|
|
||||||
if total_offset ~= next_line_start_col or max_length == 0 then break end
|
|
||||||
token_offset = token_offset + #rendered_text
|
|
||||||
idx = idx + 1
|
|
||||||
tx, ty = x + begin_width, ty + lh
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return lh * count
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_draw_line_body = DocView.draw_line_body
|
|
||||||
function DocView:draw_line_body(line, x, y)
|
|
||||||
if not self.wrapped_settings then return old_draw_line_body(self, line, x, y) end
|
|
||||||
local lh = self:get_line_height()
|
|
||||||
local idx0 = get_line_idx_col_count(self, line)
|
|
||||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
|
||||||
if line >= line1 and line <= line2 then
|
|
||||||
if line1 ~= line then col1 = 1 end
|
|
||||||
if line2 ~= line then col2 = #self.doc.lines[line] + 1 end
|
|
||||||
if col1 ~= col2 then
|
|
||||||
local idx1, ncol1 = get_line_idx_col_count(self, line, col1)
|
|
||||||
local idx2, ncol2 = get_line_idx_col_count(self, line, col2)
|
|
||||||
for i = idx1, idx2 do
|
|
||||||
local x1, x2 = x + (idx1 == i and self:get_col_x_offset(line1, col1) or 0)
|
|
||||||
if idx2 == i then
|
|
||||||
x2 = x + self:get_col_x_offset(line, col2)
|
|
||||||
else
|
|
||||||
x2 = x + self:get_col_x_offset(line, get_idx_line_length(self, i, line) + 1, true)
|
|
||||||
end
|
|
||||||
renderer.draw_rect(x1, y + (i - idx0) * lh, x2 - x1, lh, style.selection)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local draw_highlight = nil
|
|
||||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
|
||||||
-- draw line highlight if caret is on this line
|
|
||||||
if draw_highlight ~= false and config.highlight_current_line
|
|
||||||
and line1 == line and core.active_view == self then
|
|
||||||
draw_highlight = (line1 == line2 and col1 == col2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if draw_highlight then
|
|
||||||
local _, _, count = get_line_idx_col_count(self, line)
|
|
||||||
for i=1,count do
|
|
||||||
self:draw_line_highlight(x + self.scroll.x, y + lh * (i - 1))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- draw line's text
|
|
||||||
return self:draw_line_text(line, x, y)
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_draw = DocView.draw
|
|
||||||
function DocView:draw()
|
|
||||||
old_draw(self)
|
|
||||||
if self.wrapped_settings then
|
|
||||||
LineWrapping.draw_guide(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_draw_line_gutter = DocView.draw_line_gutter
|
|
||||||
function DocView:draw_line_gutter(line, x, y, width)
|
|
||||||
local lh = self:get_line_height()
|
|
||||||
local _, _, count = get_line_idx_col_count(self, line)
|
|
||||||
return (old_draw_line_gutter(self, line, x, y, width) or lh) * count
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_translate_end_of_line = translate.end_of_line
|
|
||||||
function translate.end_of_line(doc, line, col)
|
|
||||||
if not core.active_view or core.active_view.doc ~= doc or not core.active_view.wrapped_settings then old_translate_end_of_line(doc, line, col) end
|
|
||||||
local idx, ncol = get_line_idx_col_count(core.active_view, line, col)
|
|
||||||
local nline, ncol2 = get_idx_line_col(core.active_view, idx + 1)
|
|
||||||
if nline ~= line then return line, math.huge end
|
|
||||||
return line, ncol2 - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_translate_start_of_line = translate.start_of_line
|
|
||||||
function translate.start_of_line(doc, line, col)
|
|
||||||
if not core.active_view or core.active_view.doc ~= doc or not core.active_view.wrapped_settings then old_translate_start_of_line(doc, line, col) end
|
|
||||||
local idx, ncol = get_line_idx_col_count(core.active_view, line, col)
|
|
||||||
local nline, ncol2 = get_idx_line_col(core.active_view, idx - 1)
|
|
||||||
if nline ~= line then return line, 1 end
|
|
||||||
return line, ncol2 + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_previous_line = DocView.translate.previous_line
|
|
||||||
function DocView.translate.previous_line(doc, line, col, dv)
|
|
||||||
if not dv.wrapped_settings then return old_previous_line(doc, line, col, dv) end
|
|
||||||
local idx, ncol = get_line_idx_col_count(dv, line, col)
|
|
||||||
return get_line_col_from_index_and_x(dv, idx - 1, dv:get_col_x_offset(line, col))
|
|
||||||
end
|
|
||||||
|
|
||||||
local old_next_line = DocView.translate.next_line
|
|
||||||
function DocView.translate.next_line(doc, line, col, dv)
|
|
||||||
if not dv.wrapped_settings then return old_next_line(doc, line, col, dv) end
|
|
||||||
local idx, ncol = get_line_idx_col_count(dv, line, col)
|
|
||||||
return get_line_col_from_index_and_x(dv, idx + 1, dv:get_col_x_offset(line, col))
|
|
||||||
end
|
|
||||||
|
|
||||||
command.add(nil, {
|
|
||||||
["line-wrapping:enable"] = function()
|
|
||||||
if core.active_view and core.active_view.doc then
|
|
||||||
LineWrapping.update_docview_breaks(core.active_view)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
["line-wrapping:disable"] = function()
|
|
||||||
if core.active_view and core.active_view.doc then
|
|
||||||
LineWrapping.reconstruct_breaks(core.active_view, core.active_view:get_font(), math.huge)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
["line-wrapping:toggle"] = function()
|
|
||||||
if core.active_view and core.active_view.doc and core.active_view.wrapped_settings then
|
|
||||||
command.perform("line-wrapping:disable")
|
|
||||||
else
|
|
||||||
command.perform("line-wrapping:enable")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
keymap.add {
|
|
||||||
["f10"] = "line-wrapping:toggle",
|
|
||||||
}
|
|
||||||
|
|
||||||
return LineWrapping
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
|
@ -9,13 +9,12 @@ local View = require "core.view"
|
||||||
|
|
||||||
local ResultsView = View:extend()
|
local ResultsView = View:extend()
|
||||||
|
|
||||||
ResultsView.context = "session"
|
|
||||||
|
|
||||||
function ResultsView:new(path, text, fn)
|
function ResultsView:new(text, fn)
|
||||||
ResultsView.super.new(self)
|
ResultsView.super.new(self)
|
||||||
self.scrollable = true
|
self.scrollable = true
|
||||||
self.brightness = 0
|
self.brightness = 0
|
||||||
self:begin_search(path, text, fn)
|
self:begin_search(text, fn)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,8 +44,8 @@ local function find_all_matches_in_file(t, filename, fn)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function ResultsView:begin_search(path, text, fn)
|
function ResultsView:begin_search(text, fn)
|
||||||
self.search_args = { path, text, fn }
|
self.search_args = { text, fn }
|
||||||
self.results = {}
|
self.results = {}
|
||||||
self.last_file_idx = 1
|
self.last_file_idx = 1
|
||||||
self.query = text
|
self.query = text
|
||||||
|
@ -56,9 +55,9 @@ function ResultsView:begin_search(path, text, fn)
|
||||||
core.add_thread(function()
|
core.add_thread(function()
|
||||||
local i = 1
|
local i = 1
|
||||||
for dir_name, file in core.get_project_files() do
|
for dir_name, file in core.get_project_files() do
|
||||||
if file.type == "file" and (not path or (dir_name .. "/" .. file.filename):find(path, 1, true) == 1) then
|
if file.type == "file" then
|
||||||
local truncated_path = (dir_name == core.project_dir and "" or (dir_name .. PATHSEP))
|
local path = (dir_name == core.project_dir and "" or (dir_name .. PATHSEP))
|
||||||
find_all_matches_in_file(self.results, truncated_path .. file.filename, fn)
|
find_all_matches_in_file(self.results, path .. file.filename, fn)
|
||||||
end
|
end
|
||||||
self.last_file_idx = i
|
self.last_file_idx = i
|
||||||
i = i + 1
|
i = i + 1
|
||||||
|
@ -92,7 +91,7 @@ end
|
||||||
function ResultsView:on_mouse_pressed(...)
|
function ResultsView:on_mouse_pressed(...)
|
||||||
local caught = ResultsView.super.on_mouse_pressed(self, ...)
|
local caught = ResultsView.super.on_mouse_pressed(self, ...)
|
||||||
if not caught then
|
if not caught then
|
||||||
return self:open_selected_result()
|
self:open_selected_result()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -108,7 +107,6 @@ function ResultsView:open_selected_result()
|
||||||
dv.doc:set_selection(res.line, res.col)
|
dv.doc:set_selection(res.line, res.col)
|
||||||
dv:scroll_to_line(res.line, false, true)
|
dv:scroll_to_line(res.line, false, true)
|
||||||
end)
|
end)
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -172,11 +170,11 @@ function ResultsView:draw()
|
||||||
local ox, oy = self:get_content_offset()
|
local ox, oy = self:get_content_offset()
|
||||||
local x, y = ox + style.padding.x, oy + style.padding.y
|
local x, y = ox + style.padding.x, oy + style.padding.y
|
||||||
local files_number = core.project_files_number()
|
local files_number = core.project_files_number()
|
||||||
local per = common.clamp(files_number and self.last_file_idx / files_number or 1, 0, 1)
|
local per = files_number and self.last_file_idx / files_number or 1
|
||||||
local text
|
local text
|
||||||
if self.searching then
|
if self.searching then
|
||||||
if files_number then
|
if files_number then
|
||||||
text = string.format("Searching %.f%% (%d of %d files, %d matches) for %q...",
|
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
|
||||||
per * 100, self.last_file_idx, files_number,
|
per * 100, self.last_file_idx, files_number,
|
||||||
#self.results, self.query)
|
#self.results, self.query)
|
||||||
else
|
else
|
||||||
|
@ -219,72 +217,38 @@ function ResultsView:draw()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function begin_search(path, text, fn)
|
local function begin_search(text, fn)
|
||||||
if text == "" then
|
if text == "" then
|
||||||
core.error("Expected non-empty string")
|
core.error("Expected non-empty string")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local rv = ResultsView(path, text, fn)
|
local rv = ResultsView(text, fn)
|
||||||
core.root_view:get_active_node_default():add_view(rv)
|
core.root_view:get_active_node_default():add_view(rv)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_selected_text()
|
|
||||||
local view = core.active_view
|
|
||||||
local doc = (view and view.doc) and view.doc or nil
|
|
||||||
if doc then
|
|
||||||
return doc:get_text(table.unpack({ doc:get_selection() }))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function normalize_path(path)
|
|
||||||
if not path then return nil end
|
|
||||||
path = common.normalize_path(path)
|
|
||||||
for i, project_dir in ipairs(core.project_directories) do
|
|
||||||
if common.path_belongs_to(path, project_dir.name) then
|
|
||||||
return project_dir.item.filename .. PATHSEP .. common.relative_path(project_dir.name, path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return path
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
command.add(nil, {
|
command.add(nil, {
|
||||||
["project-search:find"] = function(path)
|
["project-search:find"] = function()
|
||||||
core.command_view:enter("Find Text In " .. (normalize_path(path) or "Project"), {
|
core.command_view:enter("Find Text In Project", function(text)
|
||||||
text = get_selected_text(),
|
text = text:lower()
|
||||||
select_text = true,
|
begin_search(text, function(line_text)
|
||||||
submit = function(text)
|
return line_text:lower():find(text, nil, true)
|
||||||
text = text:lower()
|
end)
|
||||||
begin_search(path, text, function(line_text)
|
end)
|
||||||
return line_text:lower():find(text, nil, true)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["project-search:find-regex"] = function(path)
|
["project-search:find-pattern"] = function()
|
||||||
core.command_view:enter("Find Regex In " .. (normalize_path(path) or "Project"), {
|
core.command_view:enter("Find Pattern In Project", function(text)
|
||||||
submit = function(text)
|
begin_search(text, function(line_text) return line_text:find(text) end)
|
||||||
local re = regex.compile(text, "i")
|
end)
|
||||||
begin_search(path, text, function(line_text)
|
|
||||||
return regex.cmatch(re, line_text)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["project-search:fuzzy-find"] = function(path)
|
["project-search:fuzzy-find"] = function()
|
||||||
core.command_view:enter("Fuzzy Find Text In " .. (normalize_path(path) or "Project"), {
|
core.command_view:enter("Fuzzy Find Text In Project", function(text)
|
||||||
text = get_selected_text(),
|
begin_search(text, function(line_text)
|
||||||
select_text = true,
|
return common.fuzzy_match(line_text, text) and 1
|
||||||
submit = function(text)
|
end)
|
||||||
begin_search(path, text, function(line_text)
|
end)
|
||||||
return common.fuzzy_match(line_text, text) and 1
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -309,22 +273,22 @@ command.add(ResultsView, {
|
||||||
["project-search:refresh"] = function()
|
["project-search:refresh"] = function()
|
||||||
core.active_view:refresh()
|
core.active_view:refresh()
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["project-search:move-to-previous-page"] = function()
|
["project-search:move-to-previous-page"] = function()
|
||||||
local view = core.active_view
|
local view = core.active_view
|
||||||
view.scroll.to.y = view.scroll.to.y - view.size.y
|
view.scroll.to.y = view.scroll.to.y - view.size.y
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["project-search:move-to-next-page"] = function()
|
["project-search:move-to-next-page"] = function()
|
||||||
local view = core.active_view
|
local view = core.active_view
|
||||||
view.scroll.to.y = view.scroll.to.y + view.size.y
|
view.scroll.to.y = view.scroll.to.y + view.size.y
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["project-search:move-to-start-of-doc"] = function()
|
["project-search:move-to-start-of-doc"] = function()
|
||||||
local view = core.active_view
|
local view = core.active_view
|
||||||
view.scroll.to.y = 0
|
view.scroll.to.y = 0
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["project-search:move-to-end-of-doc"] = function()
|
["project-search:move-to-end-of-doc"] = function()
|
||||||
local view = core.active_view
|
local view = core.active_view
|
||||||
view.scroll.to.y = view:get_scrollable_size()
|
view.scroll.to.y = view:get_scrollable_size()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
|
@ -19,8 +19,8 @@ end
|
||||||
|
|
||||||
|
|
||||||
command.add("core.docview", {
|
command.add("core.docview", {
|
||||||
["quote:quote"] = function(dv)
|
["quote:quote"] = function()
|
||||||
dv.doc:replace(function(text)
|
core.active_view.doc:replace(function(text)
|
||||||
return '"' .. text:gsub("[\0-\31\\\"]", replace) .. '"'
|
return '"' .. text:gsub("[\0-\31\\\"]", replace) .. '"'
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
@ -25,8 +25,8 @@ end
|
||||||
|
|
||||||
|
|
||||||
command.add("core.docview", {
|
command.add("core.docview", {
|
||||||
["reflow:reflow"] = function(dv)
|
["reflow:reflow"] = function()
|
||||||
local doc = dv.doc
|
local doc = core.active_view.doc
|
||||||
doc:replace(function(text)
|
doc:replace(function(text)
|
||||||
local prefix_set = "[^%w\n%[%](){}`'\"]*"
|
local prefix_set = "[^%w\n%[%](){}`'\"]*"
|
||||||
|
|
||||||
|
|
|
@ -1,180 +0,0 @@
|
||||||
-- mod-version:3
|
|
||||||
local core = require "core"
|
|
||||||
local common = require "core.common"
|
|
||||||
local command = require "core.command"
|
|
||||||
local config = require "core.config"
|
|
||||||
local keymap = require "core.keymap"
|
|
||||||
local style = require "core.style"
|
|
||||||
local CommandView = require "core.commandview"
|
|
||||||
|
|
||||||
config.plugins.scale = common.merge({
|
|
||||||
-- The method used to apply the scaling: "code", "ui"
|
|
||||||
mode = "code",
|
|
||||||
-- Default scale applied at startup.
|
|
||||||
default_scale = "autodetect",
|
|
||||||
-- Allow using CTRL + MouseWheel for changing the scale.
|
|
||||||
use_mousewheel = true
|
|
||||||
}, config.plugins.scale)
|
|
||||||
|
|
||||||
local scale_steps = 0.05
|
|
||||||
|
|
||||||
local current_scale = SCALE
|
|
||||||
local default_scale = SCALE
|
|
||||||
|
|
||||||
local function set_scale(scale)
|
|
||||||
scale = common.clamp(scale, 0.2, 6)
|
|
||||||
|
|
||||||
-- save scroll positions
|
|
||||||
local scrolls = {}
|
|
||||||
for _, view in ipairs(core.root_view.root_node:get_children()) do
|
|
||||||
local n = view:get_scrollable_size()
|
|
||||||
if n ~= math.huge and not view:is(CommandView) and n > view.size.y then
|
|
||||||
scrolls[view] = view.scroll.y / (n - view.size.y)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local s = scale / current_scale
|
|
||||||
current_scale = scale
|
|
||||||
|
|
||||||
if config.plugins.scale.mode == "ui" then
|
|
||||||
SCALE = scale
|
|
||||||
|
|
||||||
style.padding.x = style.padding.x * s
|
|
||||||
style.padding.y = style.padding.y * s
|
|
||||||
style.divider_size = style.divider_size * s
|
|
||||||
style.scrollbar_size = style.scrollbar_size * s
|
|
||||||
style.caret_width = style.caret_width * s
|
|
||||||
style.tab_width = style.tab_width * s
|
|
||||||
|
|
||||||
for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do
|
|
||||||
style[name]:set_size(s * style[name]:get_size())
|
|
||||||
end
|
|
||||||
else
|
|
||||||
style.code_font:set_size(s * style.code_font:get_size())
|
|
||||||
end
|
|
||||||
|
|
||||||
for name, font in pairs(style.syntax_fonts) do
|
|
||||||
style.syntax_fonts[name]:set_size(s * font:get_size())
|
|
||||||
end
|
|
||||||
|
|
||||||
-- restore scroll positions
|
|
||||||
for view, n in pairs(scrolls) do
|
|
||||||
view.scroll.y = n * (view:get_scrollable_size() - view.size.y)
|
|
||||||
view.scroll.to.y = view.scroll.y
|
|
||||||
end
|
|
||||||
|
|
||||||
core.redraw = true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_scale()
|
|
||||||
return current_scale
|
|
||||||
end
|
|
||||||
|
|
||||||
local function res_scale()
|
|
||||||
set_scale(default_scale)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function inc_scale()
|
|
||||||
set_scale(current_scale + scale_steps)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dec_scale()
|
|
||||||
set_scale(current_scale - scale_steps)
|
|
||||||
end
|
|
||||||
|
|
||||||
if default_scale ~= config.plugins.scale.default_scale then
|
|
||||||
if type(config.plugins.scale.default_scale) == "number" then
|
|
||||||
set_scale(config.plugins.scale.default_scale)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- The config specification used by gui generators
|
|
||||||
config.plugins.scale.config_spec = {
|
|
||||||
name = "Scale",
|
|
||||||
{
|
|
||||||
label = "Mode",
|
|
||||||
description = "The method used to apply the scaling.",
|
|
||||||
path = "mode",
|
|
||||||
type = "selection",
|
|
||||||
default = "code",
|
|
||||||
values = {
|
|
||||||
{"Everything", "ui"},
|
|
||||||
{"Code Only", "code"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Default Scale",
|
|
||||||
description = "The scaling factor applied to lite-xl.",
|
|
||||||
path = "default_scale",
|
|
||||||
type = "selection",
|
|
||||||
default = "autodetect",
|
|
||||||
values = {
|
|
||||||
{"Autodetect", "autodetect"},
|
|
||||||
{"80%", 0.80},
|
|
||||||
{"90%", 0.90},
|
|
||||||
{"100%", 1.00},
|
|
||||||
{"110%", 1.10},
|
|
||||||
{"120%", 1.20},
|
|
||||||
{"125%", 1.25},
|
|
||||||
{"130%", 1.30},
|
|
||||||
{"140%", 1.40},
|
|
||||||
{"150%", 1.50},
|
|
||||||
{"175%", 1.75},
|
|
||||||
{"200%", 2.00},
|
|
||||||
{"250%", 2.50},
|
|
||||||
{"300%", 3.00}
|
|
||||||
},
|
|
||||||
on_apply = function(value)
|
|
||||||
if type(value) == "string" then value = default_scale end
|
|
||||||
if value ~= current_scale then
|
|
||||||
set_scale(value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Use MouseWheel",
|
|
||||||
description = "Allow using CTRL + MouseWheel for changing the scale.",
|
|
||||||
path = "use_mousewheel",
|
|
||||||
type = "toggle",
|
|
||||||
default = true,
|
|
||||||
on_apply = function(enabled)
|
|
||||||
if enabled then
|
|
||||||
keymap.add {
|
|
||||||
["ctrl+wheelup"] = "scale:increase",
|
|
||||||
["ctrl+wheeldown"] = "scale:decrease"
|
|
||||||
}
|
|
||||||
else
|
|
||||||
keymap.unbind("ctrl+wheelup", "scale:increase")
|
|
||||||
keymap.unbind("ctrl+wheeldown", "scale:decrease")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
command.add(nil, {
|
|
||||||
["scale:reset" ] = function() res_scale() end,
|
|
||||||
["scale:decrease"] = function() dec_scale() end,
|
|
||||||
["scale:increase"] = function() inc_scale() end,
|
|
||||||
})
|
|
||||||
|
|
||||||
keymap.add {
|
|
||||||
["ctrl+0"] = "scale:reset",
|
|
||||||
["ctrl+-"] = "scale:decrease",
|
|
||||||
["ctrl+="] = "scale:increase"
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.plugins.scale.use_mousewheel then
|
|
||||||
keymap.add {
|
|
||||||
["ctrl+wheelup"] = "scale:increase",
|
|
||||||
["ctrl+wheeldown"] = "scale:decrease"
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
["set"] = set_scale,
|
|
||||||
["get"] = get_scale,
|
|
||||||
["increase"] = inc_scale,
|
|
||||||
["decrease"] = dec_scale,
|
|
||||||
["reset"] = res_scale
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local translate = require "core.doc.translate"
|
local translate = require "core.doc.translate"
|
||||||
|
@ -41,23 +41,21 @@ end
|
||||||
|
|
||||||
|
|
||||||
command.add("core.docview", {
|
command.add("core.docview", {
|
||||||
["tabularize:tabularize"] = function(dv)
|
["tabularize:tabularize"] = function()
|
||||||
core.command_view:enter("Tabularize On Delimiter", {
|
core.command_view:enter("Tabularize On Delimiter", function(delim)
|
||||||
submit = function(delim)
|
if delim == "" then delim = " " end
|
||||||
if delim == "" then delim = " " end
|
|
||||||
|
|
||||||
local doc = dv.doc
|
local doc = core.active_view.doc
|
||||||
local line1, col1, line2, col2, swap = doc:get_selection(true)
|
local line1, col1, line2, col2, swap = doc:get_selection(true)
|
||||||
line1, col1 = doc:position_offset(line1, col1, translate.start_of_line)
|
line1, col1 = doc:position_offset(line1, col1, translate.start_of_line)
|
||||||
line2, col2 = doc:position_offset(line2, col2, translate.end_of_line)
|
line2, col2 = doc:position_offset(line2, col2, translate.end_of_line)
|
||||||
doc:set_selection(line1, col1, line2, col2, swap)
|
doc:set_selection(line1, col1, line2, col2, swap)
|
||||||
|
|
||||||
doc:replace(function(text)
|
doc:replace(function(text)
|
||||||
local lines = gmatch_to_array(text, "[^\n]*\n?")
|
local lines = gmatch_to_array(text, "[^\n]*\n?")
|
||||||
tabularize_lines(lines, delim)
|
tabularize_lines(lines, delim)
|
||||||
return table.concat(lines)
|
return table.concat(lines)
|
||||||
end)
|
end)
|
||||||
end
|
end)
|
||||||
})
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
@ -7,26 +7,31 @@ local View = require "core.view"
|
||||||
|
|
||||||
local ToolbarView = View:extend()
|
local ToolbarView = View:extend()
|
||||||
|
|
||||||
|
local toolbar_commands = {
|
||||||
|
{symbol = "f", command = "core:new-doc"},
|
||||||
|
{symbol = "D", command = "core:open-file"},
|
||||||
|
{symbol = "S", command = "doc:save"},
|
||||||
|
{symbol = "L", command = "core:find-file"},
|
||||||
|
{symbol = "B", command = "core:find-command"},
|
||||||
|
{symbol = "P", command = "core:open-user-module"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local function toolbar_height()
|
||||||
|
return style.icon_big_font:get_height() + style.padding.y * 2
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:new()
|
function ToolbarView:new()
|
||||||
ToolbarView.super.new(self)
|
ToolbarView.super.new(self)
|
||||||
self.visible = true
|
self.visible = true
|
||||||
self.init_size = true
|
self.init_size = true
|
||||||
self.tooltip = false
|
self.tooltip = false
|
||||||
self.toolbar_font = style.icon_big_font
|
|
||||||
self.toolbar_commands = {
|
|
||||||
{symbol = "f", command = "core:new-doc"},
|
|
||||||
{symbol = "D", command = "core:open-file"},
|
|
||||||
{symbol = "S", command = "doc:save"},
|
|
||||||
{symbol = "L", command = "core:find-file"},
|
|
||||||
{symbol = "B", command = "core:find-command"},
|
|
||||||
{symbol = "P", command = "core:open-user-module"},
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:update()
|
function ToolbarView:update()
|
||||||
local dest_size = self.visible and (self.toolbar_font:get_height() + style.padding.y * 2) or 0
|
local dest_size = self.visible and toolbar_height() or 0
|
||||||
if self.init_size then
|
if self.init_size then
|
||||||
self.size.y = dest_size
|
self.size.y = dest_size
|
||||||
self.init_size = nil
|
self.init_size = nil
|
||||||
|
@ -41,24 +46,19 @@ function ToolbarView:toggle_visible()
|
||||||
self.visible = not self.visible
|
self.visible = not self.visible
|
||||||
end
|
end
|
||||||
|
|
||||||
function ToolbarView:get_icon_width()
|
|
||||||
local max_width = 0
|
|
||||||
for i,v in ipairs(self.toolbar_commands) do max_width = math.max(max_width, self.toolbar_font:get_width(v.symbol)) end
|
|
||||||
return max_width
|
|
||||||
end
|
|
||||||
|
|
||||||
function ToolbarView:each_item()
|
function ToolbarView:each_item()
|
||||||
local icon_h, icon_w = self.toolbar_font:get_height(), self:get_icon_width()
|
local icon_h, icon_w = style.icon_big_font:get_height(), style.icon_big_font:get_width("D")
|
||||||
local toolbar_spacing = icon_w / 2
|
local toolbar_spacing = icon_w / 2
|
||||||
local ox, oy = self:get_content_offset()
|
local ox, oy = self:get_content_offset()
|
||||||
local index = 0
|
local index = 0
|
||||||
local iter = function()
|
local iter = function()
|
||||||
index = index + 1
|
index = index + 1
|
||||||
if index <= #self.toolbar_commands then
|
if index <= #toolbar_commands then
|
||||||
local dx = style.padding.x + (icon_w + toolbar_spacing) * (index - 1)
|
local dx = style.padding.x + (icon_w + toolbar_spacing) * (index - 1)
|
||||||
local dy = style.padding.y
|
local dy = style.padding.y
|
||||||
if dx + icon_w > self.size.x then return end
|
if dx + icon_w > self.size.x then return end
|
||||||
return self.toolbar_commands[index], ox + dx, oy + dy, icon_w, icon_h
|
return toolbar_commands[index], ox + dx, oy + dy, icon_w, icon_h
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return iter
|
return iter
|
||||||
|
@ -66,9 +66,9 @@ end
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:get_min_width()
|
function ToolbarView:get_min_width()
|
||||||
local icon_w = self:get_icon_width()
|
local icon_w = style.icon_big_font:get_width("D")
|
||||||
local space = icon_w / 2
|
local space = icon_w / 2
|
||||||
return 2 * style.padding.x + (icon_w + space) * #self.toolbar_commands - space
|
return 2 * style.padding.x + (icon_w + space) * #toolbar_commands - space
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,20 +76,19 @@ function ToolbarView:draw()
|
||||||
self:draw_background(style.background2)
|
self:draw_background(style.background2)
|
||||||
|
|
||||||
for item, x, y, w, h in self:each_item() do
|
for item, x, y, w, h in self:each_item() do
|
||||||
local color = item == self.hovered_item and command.is_valid(item.command) and style.text or style.dim
|
local color = item == self.hovered_item and style.text or style.dim
|
||||||
common.draw_text(self.toolbar_font, color, item.symbol, nil, x, y, 0, h)
|
common.draw_text(style.icon_big_font, color, item.symbol, nil, x, y, 0, h)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:on_mouse_pressed(button, x, y, clicks)
|
function ToolbarView:on_mouse_pressed(button, x, y, clicks)
|
||||||
local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks)
|
local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||||
if caught then return caught end
|
if caught then return end
|
||||||
core.set_active_view(core.last_active_view)
|
core.set_active_view(core.last_active_view)
|
||||||
if self.hovered_item and command.is_valid(self.hovered_item.command) then
|
if self.hovered_item then
|
||||||
command.perform(self.hovered_item.command)
|
command.perform(self.hovered_item.command)
|
||||||
end
|
end
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
@ -6,16 +6,9 @@ local config = require "core.config"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
local style = require "core.style"
|
local style = require "core.style"
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
local ContextMenu = require "core.contextmenu"
|
|
||||||
local RootView = require "core.rootview"
|
|
||||||
local CommandView = require "core.commandview"
|
|
||||||
|
|
||||||
config.plugins.treeview = common.merge({
|
local default_treeview_size = 200 * SCALE
|
||||||
-- Default treeview width
|
local tooltip_offset = style.font:get_height("A")
|
||||||
size = 200 * SCALE
|
|
||||||
}, config.plugins.treeview)
|
|
||||||
|
|
||||||
local tooltip_offset = style.font:get_height()
|
|
||||||
local tooltip_border = 1
|
local tooltip_border = 1
|
||||||
local tooltip_delay = 0.5
|
local tooltip_delay = 0.5
|
||||||
local tooltip_alpha = 255
|
local tooltip_alpha = 255
|
||||||
|
@ -43,27 +36,10 @@ function TreeView:new()
|
||||||
self.scrollable = true
|
self.scrollable = true
|
||||||
self.visible = true
|
self.visible = true
|
||||||
self.init_size = true
|
self.init_size = true
|
||||||
self.target_size = config.plugins.treeview.size
|
self.target_size = default_treeview_size
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
|
self.last = {}
|
||||||
self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 }
|
self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 }
|
||||||
self.cursor_pos = { x = 0, y = 0 }
|
|
||||||
|
|
||||||
self.item_icon_width = 0
|
|
||||||
self.item_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
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,7 +51,7 @@ function TreeView:set_target_size(axis, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:get_cached(dir, item, dirname)
|
function TreeView:get_cached(item, dirname)
|
||||||
local dir_cache = self.cache[dirname]
|
local dir_cache = self.cache[dirname]
|
||||||
if not dir_cache then
|
if not dir_cache then
|
||||||
dir_cache = {}
|
dir_cache = {}
|
||||||
|
@ -101,7 +77,6 @@ function TreeView:get_cached(dir, item, dirname)
|
||||||
end
|
end
|
||||||
t.name = basename
|
t.name = basename
|
||||||
t.type = item.type
|
t.type = item.type
|
||||||
t.dir_name = dir.name -- points to top level "dir" item
|
|
||||||
dir_cache[cache_name] = t
|
dir_cache[cache_name] = t
|
||||||
end
|
end
|
||||||
return t
|
return t
|
||||||
|
@ -109,7 +84,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:get_name()
|
function TreeView:get_name()
|
||||||
return nil
|
return "Project"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,13 +101,18 @@ end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:check_cache()
|
function TreeView:check_cache()
|
||||||
|
-- invalidate cache's skip values if project_files has changed
|
||||||
for i = 1, #core.project_directories do
|
for i = 1, #core.project_directories do
|
||||||
local dir = core.project_directories[i]
|
local dir = core.project_directories[i]
|
||||||
-- invalidate cache's skip values if directory is declared dirty
|
local last_files = self.last[dir.name]
|
||||||
if dir.is_dirty and self.cache[dir.name] then
|
if not last_files then
|
||||||
self:invalidate_cache(dir.name)
|
self.last[dir.name] = dir.files
|
||||||
|
else
|
||||||
|
if dir.files ~= last_files then
|
||||||
|
self:invalidate_cache(dir.name)
|
||||||
|
self.last[dir.name] = dir.files
|
||||||
|
end
|
||||||
end
|
end
|
||||||
dir.is_dirty = false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -148,56 +128,39 @@ function TreeView:each_item()
|
||||||
|
|
||||||
for k = 1, #core.project_directories do
|
for k = 1, #core.project_directories do
|
||||||
local dir = core.project_directories[k]
|
local dir = core.project_directories[k]
|
||||||
local dir_cached = self:get_cached(dir, dir.item, dir.name)
|
local dir_cached = self:get_cached(dir.item, dir.name)
|
||||||
coroutine.yield(dir_cached, ox, y, w, h)
|
coroutine.yield(dir_cached, ox, y, w, h)
|
||||||
count_lines = count_lines + 1
|
count_lines = count_lines + 1
|
||||||
y = y + h
|
y = y + h
|
||||||
local i = 1
|
local i = 1
|
||||||
if dir.files then -- if consumed max sys file descriptors this can be nil
|
while i <= #dir.files and dir_cached.expanded do
|
||||||
while i <= #dir.files and dir_cached.expanded do
|
local item = dir.files[i]
|
||||||
local item = dir.files[i]
|
local cached = self:get_cached(item, dir.name)
|
||||||
local cached = self:get_cached(dir, item, dir.name)
|
|
||||||
|
|
||||||
coroutine.yield(cached, ox, y, w, h)
|
coroutine.yield(cached, ox, y, w, h)
|
||||||
count_lines = count_lines + 1
|
count_lines = count_lines + 1
|
||||||
y = y + h
|
y = y + h
|
||||||
i = i + 1
|
i = i + 1
|
||||||
|
|
||||||
if not cached.expanded then
|
if not cached.expanded then
|
||||||
if cached.skip then
|
if cached.skip then
|
||||||
i = cached.skip
|
i = cached.skip
|
||||||
else
|
else
|
||||||
local depth = cached.depth
|
local depth = cached.depth
|
||||||
while i <= #dir.files do
|
while i <= #dir.files do
|
||||||
if get_depth(dir.files[i].filename) <= depth then break end
|
if get_depth(dir.files[i].filename) <= depth then break end
|
||||||
i = i + 1
|
i = i + 1
|
||||||
end
|
|
||||||
cached.skip = i
|
|
||||||
end
|
end
|
||||||
|
cached.skip = i
|
||||||
end
|
end
|
||||||
end -- while files
|
end
|
||||||
end
|
end -- while files
|
||||||
end -- for directories
|
end -- for directories
|
||||||
self.count_lines = count_lines
|
self.count_lines = count_lines
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:set_selection(selection, selection_y)
|
|
||||||
self.selected_item = selection
|
|
||||||
if selection and selection_y
|
|
||||||
and (selection_y <= 0 or selection_y >= self.size.y) then
|
|
||||||
|
|
||||||
local lh = self:get_item_height()
|
|
||||||
if selection_y >= self.size.y - lh then
|
|
||||||
selection_y = selection_y - self.size.y + lh
|
|
||||||
end
|
|
||||||
local _, y = self:get_content_offset()
|
|
||||||
self.scroll.to.y = selection and (selection_y - y)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:get_text_bounding_box(item, x, y, w, h)
|
function TreeView:get_text_bounding_box(item, x, y, w, h)
|
||||||
local icon_width = style.icon_font:get_width("D")
|
local icon_width = style.icon_font:get_width("D")
|
||||||
local xoffset = item.depth * style.padding.x + style.padding.x + icon_width
|
local xoffset = item.depth * style.padding.x + style.padding.x + icon_width
|
||||||
|
@ -208,21 +171,15 @@ end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:on_mouse_moved(px, py, ...)
|
function TreeView:on_mouse_moved(px, py, ...)
|
||||||
if not self.visible then return end
|
|
||||||
TreeView.super.on_mouse_moved(self, px, py, ...)
|
TreeView.super.on_mouse_moved(self, px, py, ...)
|
||||||
self.cursor_pos.x = px
|
if self.dragging_scrollbar then return end
|
||||||
self.cursor_pos.y = py
|
|
||||||
if self.dragging_scrollbar then
|
|
||||||
self.hovered_item = nil
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local item_changed, tooltip_changed
|
local item_changed, tooltip_changed
|
||||||
for item, x,y,w,h in self:each_item() do
|
for item, x,y,w,h in self:each_item() do
|
||||||
if px > x and py > y and px <= x + w and py <= y + h then
|
if px > x and py > y and px <= x + w and py <= y + h then
|
||||||
item_changed = true
|
item_changed = true
|
||||||
self.hovered_item = item
|
self.hovered_item = item
|
||||||
|
|
||||||
x,y,w,h = self:get_text_bounding_box(item, x,y,w,h)
|
x,y,w,h = self:get_text_bounding_box(item, x,y,w,h)
|
||||||
if px > x and py > y and px <= x + w and py <= y + h then
|
if px > x and py > y and px <= x + w and py <= y + h then
|
||||||
tooltip_changed = true
|
tooltip_changed = true
|
||||||
|
@ -237,6 +194,59 @@ function TreeView:on_mouse_moved(px, py, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function create_directory_in(item)
|
||||||
|
local path = item.abs_filename
|
||||||
|
core.command_view:enter("Create directory in " .. path, function(text)
|
||||||
|
local dirname = path .. PATHSEP .. text
|
||||||
|
local success, err = system.mkdir(dirname)
|
||||||
|
if not success then
|
||||||
|
core.error("cannot create directory %q: %s", dirname, err)
|
||||||
|
end
|
||||||
|
item.expanded = true
|
||||||
|
core.reschedule_project_scan()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function TreeView:on_mouse_pressed(button, x, y, clicks)
|
||||||
|
local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||||
|
if caught then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local hovered_item = self.hovered_item
|
||||||
|
if not hovered_item then
|
||||||
|
return
|
||||||
|
elseif hovered_item.type == "dir" then
|
||||||
|
if keymap.modkeys["ctrl"] and button == "left" then
|
||||||
|
create_directory_in(hovered_item)
|
||||||
|
else
|
||||||
|
if core.project_files_limit and not hovered_item.expanded then
|
||||||
|
local filename, abs_filename = hovered_item.filename, hovered_item.abs_filename
|
||||||
|
local index = 0
|
||||||
|
-- The loop below is used to find the first match starting from the end
|
||||||
|
-- in case there are multiple matches.
|
||||||
|
while index and index + #filename < #abs_filename do
|
||||||
|
index = string.find(abs_filename, filename, index + 1, true)
|
||||||
|
end
|
||||||
|
-- we assume here index is not nil because the abs_filename must contain the
|
||||||
|
-- relative filename
|
||||||
|
local dirname = string.sub(abs_filename, 1, index - 2)
|
||||||
|
if core.is_project_folder(dirname) then
|
||||||
|
core.scan_project_folder(dirname, filename)
|
||||||
|
self:invalidate_cache(dirname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
hovered_item.expanded = not hovered_item.expanded
|
||||||
|
end
|
||||||
|
else
|
||||||
|
core.try(function()
|
||||||
|
local doc_filename = common.relative_path(core.project_dir, hovered_item.abs_filename)
|
||||||
|
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:update()
|
function TreeView:update()
|
||||||
-- update width
|
-- update width
|
||||||
local dest = self.visible and self.target_size or 0
|
local dest = self.visible and self.target_size or 0
|
||||||
|
@ -244,28 +254,16 @@ function TreeView:update()
|
||||||
self.size.x = dest
|
self.size.x = dest
|
||||||
self.init_size = false
|
self.init_size = false
|
||||||
else
|
else
|
||||||
self:move_towards(self.size, "x", dest, nil, "treeview")
|
self:move_towards(self.size, "x", dest)
|
||||||
end
|
end
|
||||||
|
|
||||||
if not self.visible then return end
|
|
||||||
|
|
||||||
local duration = system.get_time() - self.tooltip.begin
|
local duration = system.get_time() - self.tooltip.begin
|
||||||
if self.hovered_item and self.tooltip.x and duration > tooltip_delay then
|
if self.hovered_item and self.tooltip.x and duration > tooltip_delay then
|
||||||
self:move_towards(self.tooltip, "alpha", tooltip_alpha, tooltip_alpha_rate, "treeview")
|
self:move_towards(self.tooltip, "alpha", tooltip_alpha, tooltip_alpha_rate)
|
||||||
else
|
else
|
||||||
self.tooltip.alpha = 0
|
self.tooltip.alpha = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
self.item_icon_width = style.icon_font:get_width("D")
|
|
||||||
self.item_text_spacing = style.icon_font:get_width("f") / 2
|
|
||||||
|
|
||||||
-- this will make sure hovered_item is updated
|
|
||||||
-- we don't want events when the thing is scrolling fast
|
|
||||||
local dy = math.abs(self.scroll.to.y - self.scroll.y)
|
|
||||||
if self.scroll.to.y ~= 0 and dy < self:get_item_height() then
|
|
||||||
self:on_mouse_moved(self.cursor_pos.x, self.cursor_pos.y, 0, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
TreeView.super.update(self)
|
TreeView.super.update(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -294,171 +292,60 @@ function TreeView:draw_tooltip()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:get_item_icon(item, active, hovered)
|
|
||||||
local character = "f"
|
|
||||||
if item.type == "dir" then
|
|
||||||
character = item.expanded and "D" or "d"
|
|
||||||
end
|
|
||||||
local font = style.icon_font
|
|
||||||
local color = style.text
|
|
||||||
if active or hovered then
|
|
||||||
color = style.accent
|
|
||||||
end
|
|
||||||
return character, font, color
|
|
||||||
end
|
|
||||||
|
|
||||||
function TreeView:get_item_text(item, active, hovered)
|
|
||||||
local text = item.name
|
|
||||||
local font = style.font
|
|
||||||
local color = style.text
|
|
||||||
if active or hovered then
|
|
||||||
color = style.accent
|
|
||||||
end
|
|
||||||
return text, font, color
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:draw_item_text(item, active, hovered, x, y, w, h)
|
|
||||||
local item_text, item_font, item_color = self:get_item_text(item, active, hovered)
|
|
||||||
common.draw_text(item_font, item_color, item_text, nil, x, y, 0, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:draw_item_icon(item, active, hovered, x, y, w, h)
|
|
||||||
local icon_char, icon_font, icon_color = self:get_item_icon(item, active, hovered)
|
|
||||||
common.draw_text(icon_font, icon_color, icon_char, nil, x, y, 0, h)
|
|
||||||
return self.item_icon_width + self.item_text_spacing
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:draw_item_body(item, active, hovered, x, y, w, h)
|
|
||||||
x = x + self:draw_item_icon(item, active, hovered, x, y, w, h)
|
|
||||||
self:draw_item_text(item, active, hovered, x, y, w, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:draw_item_chevron(item, active, hovered, x, y, w, h)
|
|
||||||
if item.type == "dir" then
|
|
||||||
local chevron_icon = item.expanded and "-" or "+"
|
|
||||||
local chevron_color = hovered and style.accent or style.text
|
|
||||||
common.draw_text(style.icon_font, chevron_color, chevron_icon, nil, x, y, 0, h)
|
|
||||||
end
|
|
||||||
return style.padding.x
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:draw_item_background(item, active, hovered, x, y, w, h)
|
|
||||||
if hovered then
|
|
||||||
local hover_color = { table.unpack(style.line_highlight) }
|
|
||||||
hover_color[4] = 160
|
|
||||||
renderer.draw_rect(x, y, w, h, hover_color)
|
|
||||||
elseif active then
|
|
||||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:draw_item(item, active, hovered, x, y, w, h)
|
|
||||||
self:draw_item_background(item, active, hovered, x, y, w, h)
|
|
||||||
|
|
||||||
x = x + item.depth * style.padding.x + style.padding.x
|
|
||||||
x = x + self:draw_item_chevron(item, active, hovered, x, y, w, h)
|
|
||||||
|
|
||||||
self:draw_item_body(item, active, hovered, x, y, w, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:draw()
|
function TreeView:draw()
|
||||||
if not self.visible then return end
|
|
||||||
self:draw_background(style.background2)
|
self:draw_background(style.background2)
|
||||||
local _y, _h = self.position.y, self.size.y
|
|
||||||
|
local icon_width = style.icon_font:get_width("D")
|
||||||
|
local spacing = style.icon_font:get_width("f") / 2
|
||||||
|
|
||||||
local doc = core.active_view.doc
|
local doc = core.active_view.doc
|
||||||
local active_filename = doc and system.absolute_path(doc.filename or "")
|
local active_filename = doc and system.absolute_path(doc.filename or "")
|
||||||
|
|
||||||
for item, x,y,w,h in self:each_item() do
|
for item, x,y,w,h in self:each_item() do
|
||||||
if y + h >= _y and y < _y + _h then
|
local color = style.text
|
||||||
self:draw_item(item,
|
|
||||||
item == self.selected_item,
|
-- highlight active_view doc
|
||||||
item == self.hovered_item,
|
if item.abs_filename == active_filename then
|
||||||
x, y, w, h)
|
color = style.accent
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- hovered item background
|
||||||
|
if item == self.hovered_item then
|
||||||
|
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||||
|
color = style.accent
|
||||||
|
end
|
||||||
|
|
||||||
|
-- icons
|
||||||
|
x = x + item.depth * style.padding.x + style.padding.x
|
||||||
|
if item.type == "dir" then
|
||||||
|
local icon1 = item.expanded and "-" or "+"
|
||||||
|
local icon2 = item.expanded and "D" or "d"
|
||||||
|
common.draw_text(style.icon_font, color, icon1, nil, x, y, 0, h)
|
||||||
|
x = x + style.padding.x
|
||||||
|
common.draw_text(style.icon_font, color, icon2, nil, x, y, 0, h)
|
||||||
|
x = x + icon_width
|
||||||
|
else
|
||||||
|
x = x + style.padding.x
|
||||||
|
common.draw_text(style.icon_font, color, "f", nil, x, y, 0, h)
|
||||||
|
x = x + icon_width
|
||||||
|
end
|
||||||
|
|
||||||
|
-- text
|
||||||
|
x = x + spacing
|
||||||
|
x = common.draw_text(style.font, color, item.name, nil, x, y, 0, h)
|
||||||
end
|
end
|
||||||
|
|
||||||
self:draw_scrollbar()
|
self:draw_scrollbar()
|
||||||
if self.hovered_item and self.tooltip.x and self.tooltip.alpha > 0 then
|
if self.hovered_item and self.tooltip.alpha > 0 then
|
||||||
core.root_view:defer_draw(self.draw_tooltip, self)
|
core.root_view:defer_draw(self.draw_tooltip, self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:get_parent(item)
|
|
||||||
local parent_path = common.dirname(item.abs_filename)
|
|
||||||
if not parent_path then return end
|
|
||||||
for it, _, y in self:each_item() do
|
|
||||||
if it.abs_filename == parent_path then
|
|
||||||
return it, y
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:get_item(item, where)
|
|
||||||
local last_item, last_x, last_y, last_w, last_h
|
|
||||||
local stop = false
|
|
||||||
|
|
||||||
for it, x, y, w, h in self:each_item() do
|
|
||||||
if not item and where >= 0 then
|
|
||||||
return it, x, y, w, h
|
|
||||||
end
|
|
||||||
if item == it then
|
|
||||||
if where < 0 and last_item then
|
|
||||||
break
|
|
||||||
elseif where == 0 or (where < 0 and not last_item) then
|
|
||||||
return it, x, y, w, h
|
|
||||||
end
|
|
||||||
stop = true
|
|
||||||
elseif stop then
|
|
||||||
item = it
|
|
||||||
return it, x, y, w, h
|
|
||||||
end
|
|
||||||
last_item, last_x, last_y, last_w, last_h = it, x, y, w, h
|
|
||||||
end
|
|
||||||
return last_item, last_x, last_y, last_w, last_h
|
|
||||||
end
|
|
||||||
|
|
||||||
function TreeView:get_next(item)
|
|
||||||
return self:get_item(item, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function TreeView:get_previous(item)
|
|
||||||
return self:get_item(item, -1)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:toggle_expand(toggle)
|
|
||||||
local item = self.selected_item
|
|
||||||
|
|
||||||
if not item then return end
|
|
||||||
|
|
||||||
if item.type == "dir" then
|
|
||||||
if type(toggle) == "boolean" then
|
|
||||||
item.expanded = toggle
|
|
||||||
else
|
|
||||||
item.expanded = not item.expanded
|
|
||||||
end
|
|
||||||
local hovered_dir = core.project_dir_by_name(item.dir_name)
|
|
||||||
if hovered_dir and hovered_dir.files_limit then
|
|
||||||
core.update_project_subdir(hovered_dir, item.depth == 0 and "" or item.filename, item.expanded)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- init
|
-- init
|
||||||
local view = TreeView()
|
local view = TreeView()
|
||||||
local node = core.root_view:get_active_node()
|
local node = core.root_view:get_active_node()
|
||||||
view.node = node:split("left", view, {x = true}, true)
|
local treeview_node = node:split("left", view, {x = true}, true)
|
||||||
|
|
||||||
-- The toolbarview plugin is special because it is plugged inside
|
-- The toolbarview plugin is special because it is plugged inside
|
||||||
-- a treeview pane which is itelf provided in a plugin.
|
-- a treeview pane which is itelf provided in a plugin.
|
||||||
|
@ -466,13 +353,12 @@ view.node = node:split("left", view, {x = true}, true)
|
||||||
-- plugin to be independent of each other. In addition it is not the
|
-- plugin to be independent of each other. In addition it is not the
|
||||||
-- plugin module that plug itself in the active node but it is plugged here
|
-- plugin module that plug itself in the active node but it is plugged here
|
||||||
-- in the treeview node.
|
-- in the treeview node.
|
||||||
local toolbar_view = nil
|
local toolbar_plugin, ToolbarView = core.try(require, "plugins.toolbarview")
|
||||||
local toolbar_plugin, ToolbarView = pcall(require, "plugins.toolbarview")
|
if config.toolbarview ~= false and toolbar_plugin then
|
||||||
if config.plugins.toolbarview ~= false and toolbar_plugin then
|
local toolbar_view = ToolbarView()
|
||||||
toolbar_view = ToolbarView()
|
treeview_node:split("down", toolbar_view, {y = true})
|
||||||
view.node:split("down", toolbar_view, {y = true})
|
|
||||||
local min_toolbar_width = toolbar_view:get_min_width()
|
local min_toolbar_width = toolbar_view:get_min_width()
|
||||||
view:set_target_size("x", math.max(config.plugins.treeview.size, min_toolbar_width))
|
view:set_target_size("x", math.max(default_treeview_size, min_toolbar_width))
|
||||||
command.add(nil, {
|
command.add(nil, {
|
||||||
["toolbar:toggle"] = function()
|
["toolbar:toggle"] = function()
|
||||||
toolbar_view:toggle_visible()
|
toolbar_view:toggle_visible()
|
||||||
|
@ -480,429 +366,12 @@ if config.plugins.toolbarview ~= false and toolbar_plugin then
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Add a context menu to the treeview
|
|
||||||
local menu = ContextMenu()
|
|
||||||
|
|
||||||
local on_view_mouse_pressed = RootView.on_view_mouse_pressed
|
-- register commands and keymap
|
||||||
local on_mouse_moved = RootView.on_mouse_moved
|
|
||||||
local root_view_update = RootView.update
|
|
||||||
local root_view_draw = RootView.draw
|
|
||||||
|
|
||||||
function RootView:on_mouse_moved(...)
|
|
||||||
if menu:on_mouse_moved(...) then return end
|
|
||||||
on_mouse_moved(self, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
function RootView.on_view_mouse_pressed(button, x, y, clicks)
|
|
||||||
-- We give the priority to the menu to process mouse pressed events.
|
|
||||||
if button == "right" then
|
|
||||||
view.tooltip.alpha = 0
|
|
||||||
view.tooltip.x, view.tooltip.y = nil, nil
|
|
||||||
end
|
|
||||||
local handled = menu:on_mouse_pressed(button, x, y, clicks)
|
|
||||||
return handled or on_view_mouse_pressed(button, x, y, clicks)
|
|
||||||
end
|
|
||||||
|
|
||||||
function RootView:update(...)
|
|
||||||
root_view_update(self, ...)
|
|
||||||
menu:update()
|
|
||||||
end
|
|
||||||
|
|
||||||
function RootView:draw(...)
|
|
||||||
root_view_draw(self, ...)
|
|
||||||
menu:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
local on_quit_project = core.on_quit_project
|
|
||||||
function core.on_quit_project()
|
|
||||||
view.cache = {}
|
|
||||||
on_quit_project()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_project_folder(path)
|
|
||||||
for _,dir in pairs(core.project_directories) do
|
|
||||||
if dir.name == path then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_primary_project_folder(path)
|
|
||||||
return core.project_dir == path
|
|
||||||
end
|
|
||||||
|
|
||||||
menu:register(function() return view.hovered_item end, {
|
|
||||||
{ text = "Open in System", command = "treeview:open-in-system" },
|
|
||||||
ContextMenu.DIVIDER
|
|
||||||
})
|
|
||||||
|
|
||||||
menu:register(
|
|
||||||
function()
|
|
||||||
return view.hovered_item
|
|
||||||
and not is_project_folder(view.hovered_item.abs_filename)
|
|
||||||
end,
|
|
||||||
{
|
|
||||||
{ text = "Rename", command = "treeview:rename" },
|
|
||||||
{ text = "Delete", command = "treeview:delete" },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
menu:register(
|
|
||||||
function()
|
|
||||||
return view.hovered_item and view.hovered_item.type == "dir"
|
|
||||||
end,
|
|
||||||
{
|
|
||||||
{ text = "New File", command = "treeview:new-file" },
|
|
||||||
{ text = "New Folder", command = "treeview:new-folder" },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
menu:register(
|
|
||||||
function()
|
|
||||||
return view.hovered_item
|
|
||||||
and not is_primary_project_folder(view.hovered_item.abs_filename)
|
|
||||||
and is_project_folder(view.hovered_item.abs_filename)
|
|
||||||
end,
|
|
||||||
{
|
|
||||||
{ text = "Remove directory", command = "treeview:remove-project-directory" },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
local previous_view = nil
|
|
||||||
|
|
||||||
-- Register the TreeView commands and keymap
|
|
||||||
command.add(nil, {
|
command.add(nil, {
|
||||||
["treeview:toggle"] = function()
|
["treeview:toggle"] = function()
|
||||||
view.visible = not view.visible
|
view.visible = not view.visible
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["treeview:toggle-focus"] = function()
|
|
||||||
if not core.active_view:is(TreeView) then
|
|
||||||
if core.active_view:is(CommandView) then
|
|
||||||
previous_view = core.last_active_view
|
|
||||||
else
|
|
||||||
previous_view = core.active_view
|
|
||||||
end
|
|
||||||
if not previous_view then
|
|
||||||
previous_view = core.root_view:get_primary_node().active_view
|
|
||||||
end
|
|
||||||
core.set_active_view(view)
|
|
||||||
if not view.selected_item then
|
|
||||||
for it, _, y in view:each_item() do
|
|
||||||
view:set_selection(it, y)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
core.set_active_view(
|
|
||||||
previous_view or core.root_view:get_primary_node().active_view
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
})
|
})
|
||||||
|
|
||||||
command.add(TreeView, {
|
keymap.add { ["ctrl+\\"] = "treeview:toggle" }
|
||||||
["treeview:next"] = function()
|
|
||||||
local item, _, item_y = view:get_next(view.selected_item)
|
|
||||||
view:set_selection(item, item_y)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:previous"] = function()
|
|
||||||
local item, _, item_y = view:get_previous(view.selected_item)
|
|
||||||
view:set_selection(item, item_y)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:open"] = function()
|
|
||||||
local item = view.selected_item
|
|
||||||
if not item then return end
|
|
||||||
if item.type == "dir" then
|
|
||||||
view:toggle_expand()
|
|
||||||
else
|
|
||||||
core.try(function()
|
|
||||||
if core.last_active_view and core.active_view == view then
|
|
||||||
core.set_active_view(core.last_active_view)
|
|
||||||
end
|
|
||||||
local doc_filename = core.normalize_to_project_dir(item.abs_filename)
|
|
||||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:deselect"] = function()
|
|
||||||
view.selected_item = nil
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:select"] = function()
|
|
||||||
view:set_selection(view.hovered_item)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:select-and-open"] = function()
|
|
||||||
if view.hovered_item then
|
|
||||||
view:set_selection(view.hovered_item)
|
|
||||||
command.perform "treeview:open"
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:collapse"] = function()
|
|
||||||
if view.selected_item then
|
|
||||||
if view.selected_item.type == "dir" and view.selected_item.expanded then
|
|
||||||
view:toggle_expand(false)
|
|
||||||
else
|
|
||||||
local parent_item, y = view:get_parent(view.selected_item)
|
|
||||||
if parent_item then
|
|
||||||
view:set_selection(parent_item, y)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:expand"] = function()
|
|
||||||
local item = view.selected_item
|
|
||||||
if not item or item.type ~= "dir" then return end
|
|
||||||
|
|
||||||
if item.expanded then
|
|
||||||
local next_item, _, next_y = view:get_next(item)
|
|
||||||
if next_item.depth > item.depth then
|
|
||||||
view:set_selection(next_item, next_y)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
view:toggle_expand(true)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
local function treeitem() return view.hovered_item or view.selected_item end
|
|
||||||
|
|
||||||
|
|
||||||
command.add(
|
|
||||||
function()
|
|
||||||
local item = treeitem()
|
|
||||||
return item ~= nil
|
|
||||||
and (
|
|
||||||
core.active_view == view or core.active_view == menu
|
|
||||||
or (view.toolbar and core.active_view == view.toolbar)
|
|
||||||
-- sometimes the context menu is shown on top of statusbar
|
|
||||||
or core.active_view == core.status_view
|
|
||||||
), item
|
|
||||||
end, {
|
|
||||||
["treeview:delete"] = function(item)
|
|
||||||
local filename = item.abs_filename
|
|
||||||
local relfilename = item.filename
|
|
||||||
if item.dir_name ~= core.project_dir then
|
|
||||||
-- add secondary project dirs names to the file path to show
|
|
||||||
relfilename = common.basename(item.dir_name) .. PATHSEP .. relfilename
|
|
||||||
end
|
|
||||||
local file_info = system.get_file_info(filename)
|
|
||||||
local file_type = file_info.type == "dir" and "Directory" or "File"
|
|
||||||
-- Ask before deleting
|
|
||||||
local opt = {
|
|
||||||
{ font = style.font, text = "Yes", default_yes = true },
|
|
||||||
{ font = style.font, text = "No" , default_no = true }
|
|
||||||
}
|
|
||||||
core.nag_view:show(
|
|
||||||
string.format("Delete %s", file_type),
|
|
||||||
string.format(
|
|
||||||
"Are you sure you want to delete the %s?\n%s: %s",
|
|
||||||
file_type:lower(), file_type, relfilename
|
|
||||||
),
|
|
||||||
opt,
|
|
||||||
function(item)
|
|
||||||
if item.text == "Yes" then
|
|
||||||
if file_info.type == "dir" then
|
|
||||||
local deleted, error, path = common.rm(filename, true)
|
|
||||||
if not deleted then
|
|
||||||
core.error("Error: %s - \"%s\" ", error, path)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local removed, error = os.remove(filename)
|
|
||||||
if not removed then
|
|
||||||
core.error("Error: %s - \"%s\"", error, filename)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
core.log("Deleted \"%s\"", filename)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
command.add(function()
|
|
||||||
if not (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) then return end
|
|
||||||
if core.root_view.overlapping_node.active_view ~= view then return end
|
|
||||||
local item = treeitem()
|
|
||||||
return item ~= nil, item
|
|
||||||
end, {
|
|
||||||
["treeview:rename"] = function(item)
|
|
||||||
local old_filename = item.filename
|
|
||||||
local old_abs_filename = item.abs_filename
|
|
||||||
core.command_view:enter("Rename", {
|
|
||||||
text = old_filename,
|
|
||||||
submit = function(filename)
|
|
||||||
local abs_filename = filename
|
|
||||||
if not common.is_absolute_path(filename) then
|
|
||||||
abs_filename = item.dir_name .. PATHSEP .. filename
|
|
||||||
end
|
|
||||||
local res, err = os.rename(old_abs_filename, abs_filename)
|
|
||||||
if res then -- successfully renamed
|
|
||||||
for _, doc in ipairs(core.docs) do
|
|
||||||
if doc.abs_filename and old_abs_filename == doc.abs_filename then
|
|
||||||
doc:set_filename(filename, abs_filename) -- make doc point to the new filename
|
|
||||||
doc:reset_syntax()
|
|
||||||
break -- only first needed
|
|
||||||
end
|
|
||||||
end
|
|
||||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
|
||||||
else
|
|
||||||
core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
suggest = function(text)
|
|
||||||
return common.path_suggest(text, item.dir_name)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:new-file"] = function(item)
|
|
||||||
local text
|
|
||||||
if not is_project_folder(item.abs_filename) then
|
|
||||||
text = item.filename .. PATHSEP
|
|
||||||
end
|
|
||||||
core.command_view:enter("Filename", {
|
|
||||||
text = text,
|
|
||||||
submit = function(filename)
|
|
||||||
local doc_filename = item.dir_name .. PATHSEP .. filename
|
|
||||||
core.log(doc_filename)
|
|
||||||
local file = io.open(doc_filename, "a+")
|
|
||||||
file:write("")
|
|
||||||
file:close()
|
|
||||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
|
||||||
core.log("Created %s", doc_filename)
|
|
||||||
end,
|
|
||||||
suggest = function(text)
|
|
||||||
return common.path_suggest(text, item.dir_name)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:new-folder"] = function(item)
|
|
||||||
local text
|
|
||||||
if not is_project_folder(item.abs_filename) then
|
|
||||||
text = item.filename .. PATHSEP
|
|
||||||
end
|
|
||||||
core.command_view:enter("Folder Name", {
|
|
||||||
text = text,
|
|
||||||
submit = function(filename)
|
|
||||||
local dir_path = item.dir_name .. PATHSEP .. filename
|
|
||||||
common.mkdirp(dir_path)
|
|
||||||
core.log("Created %s", dir_path)
|
|
||||||
end,
|
|
||||||
suggest = function(text)
|
|
||||||
return common.path_suggest(text, item.dir_name)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:open-in-system"] = function(item)
|
|
||||||
if PLATFORM == "Windows" then
|
|
||||||
system.exec(string.format("start \"\" %q", item.abs_filename))
|
|
||||||
elseif string.find(PLATFORM, "Mac") or PLATFORM == "MorphOS" then
|
|
||||||
system.exec(string.format("open %q", item.abs_filename))
|
|
||||||
elseif PLATFORM == "Linux" or string.find(PLATFORM, "BSD") then
|
|
||||||
system.exec(string.format("xdg-open %q", item.abs_filename))
|
|
||||||
elseif PLATFORM == "AmigaOS 4" then
|
|
||||||
system.exec(string.format("WBRUN %q SHOW=all VIEWBY=name", item.abs_filename))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
local projectsearch = pcall(require, "plugins.projectsearch")
|
|
||||||
if projectsearch then
|
|
||||||
menu:register(function()
|
|
||||||
return view.hovered_item and view.hovered_item.type == "dir"
|
|
||||||
end, {
|
|
||||||
{ text = "Find in directory", command = "treeview:search-in-directory" }
|
|
||||||
})
|
|
||||||
command.add(function()
|
|
||||||
return view.hovered_item and view.hovered_item.type == "dir"
|
|
||||||
end, {
|
|
||||||
["treeview:search-in-directory"] = function(item)
|
|
||||||
command.perform("project-search:find", view.hovered_item.abs_filename)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
command.add(function()
|
|
||||||
local item = treeitem()
|
|
||||||
return item
|
|
||||||
and not is_primary_project_folder(item.abs_filename)
|
|
||||||
and is_project_folder(item.abs_filename), item
|
|
||||||
end, {
|
|
||||||
["treeview:remove-project-directory"] = function(item)
|
|
||||||
core.remove_project_directory(item.dir_name)
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
keymap.add {
|
|
||||||
["ctrl+\\"] = "treeview:toggle",
|
|
||||||
["up"] = "treeview:previous",
|
|
||||||
["down"] = "treeview:next",
|
|
||||||
["left"] = "treeview:collapse",
|
|
||||||
["right"] = "treeview:expand",
|
|
||||||
["return"] = "treeview:open",
|
|
||||||
["escape"] = "treeview:deselect",
|
|
||||||
["delete"] = "treeview:delete",
|
|
||||||
["ctrl+return"] = "treeview:new-folder",
|
|
||||||
["lclick"] = "treeview:select-and-open",
|
|
||||||
["mclick"] = "treeview:select",
|
|
||||||
["ctrl+lclick"] = "treeview:new-folder"
|
|
||||||
}
|
|
||||||
|
|
||||||
-- The config specification used by gui generators
|
|
||||||
config.plugins.treeview.config_spec = {
|
|
||||||
name = "Treeview",
|
|
||||||
{
|
|
||||||
label = "Size",
|
|
||||||
description = "Default treeview width.",
|
|
||||||
path = "size",
|
|
||||||
type = "number",
|
|
||||||
default = toolbar_view and math.ceil(toolbar_view:get_min_width() / SCALE)
|
|
||||||
or 200 * SCALE,
|
|
||||||
min = toolbar_view and toolbar_view:get_min_width() / SCALE
|
|
||||||
or 200 * SCALE,
|
|
||||||
get_value = function(value)
|
|
||||||
return value / SCALE
|
|
||||||
end,
|
|
||||||
set_value = function(value)
|
|
||||||
return value * SCALE
|
|
||||||
end,
|
|
||||||
on_apply = function(value)
|
|
||||||
view:set_target_size("x", math.max(
|
|
||||||
value, toolbar_view and toolbar_view:get_min_width() or 200 * SCALE
|
|
||||||
))
|
|
||||||
end
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label = "Hide on Startup",
|
|
||||||
description = "Show or hide the treeview on startup.",
|
|
||||||
path = "visible",
|
|
||||||
type = "toggle",
|
|
||||||
default = false,
|
|
||||||
on_apply = function(value)
|
|
||||||
view.visible = not value
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Return the treeview with toolbar and contextmenu to allow
|
|
||||||
-- user or plugin modifications
|
|
||||||
view.toolbar = toolbar_view
|
|
||||||
view.contextmenu = menu
|
|
||||||
|
|
||||||
return view
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local Doc = require "core.doc"
|
local Doc = require "core.doc"
|
||||||
|
@ -24,8 +24,8 @@ end
|
||||||
|
|
||||||
|
|
||||||
command.add("core.docview", {
|
command.add("core.docview", {
|
||||||
["trim-whitespace:trim-trailing-whitespace"] = function(dv)
|
["trim-whitespace:trim-trailing-whitespace"] = function()
|
||||||
trim_trailing_whitespace(dv.doc)
|
trim_trailing_whitespace(core.active_view.doc)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- mod-version:3
|
-- mod-version:1 -- lite-xl 1.16
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
|
@ -117,7 +117,7 @@ local function load_view(t)
|
||||||
-- cannot be read.
|
-- cannot be read.
|
||||||
if dv and dv.doc then
|
if dv and dv.doc then
|
||||||
dv.doc:set_selection(table.unpack(t.selection))
|
dv.doc:set_selection(table.unpack(t.selection))
|
||||||
dv.last_line1, dv.last_col1, dv.last_line2, dv.last_col2 = dv.doc:get_selection()
|
dv.last_line, dv.last_col = dv.doc:get_selection()
|
||||||
dv.scroll.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
|
dv.scroll.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
|
||||||
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
|
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,32 +2,25 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>lite-xl</string>
|
<string>lite-xl</string>
|
||||||
<key>CFBundleGetInfoString</key>
|
<key>CFBundleGetInfoString</key>
|
||||||
<string>lite-xl</string>
|
<string>lite-xl</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>icon.icns</string>
|
<string>icon</string>
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>com.lite-xl</string>
|
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>Lite XL</string>
|
<string>lite-xl</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>MinimumOSVersion</key><string>10.13</string>
|
||||||
<string>10.11</string>
|
<key>NSDocumentsFolderUsageDescription</key><string>To access, edit and index your projects.</string>
|
||||||
<key>NSDocumentsFolderUsageDescription</key>
|
<key>NSDesktopFolderUsageDescription</key><string>To access, edit and index your projects.</string>
|
||||||
<string>To access, edit and index your projects.</string>
|
<key>NSDownloadsFolderUsageDescription</key><string>To access, edit and index your projects.</string>
|
||||||
<key>NSDesktopFolderUsageDescription</key>
|
|
||||||
<string>To access, edit and index your projects.</string>
|
|
||||||
<key>NSDownloadsFolderUsageDescription</key>
|
|
||||||
<string>To access, edit and index your projects.</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>@PROJECT_VERSION@</string>
|
<string>1.16.10</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>© 2019-2021 Francesco Abbate</string>
|
<string>© 2019-2021 Francesco Abbate</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
Binary file not shown.
|
@ -2,9 +2,10 @@
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=Lite XL
|
Name=Lite XL
|
||||||
Comment=A lightweight text editor written in Lua
|
Comment=A lightweight text editor written in Lua
|
||||||
Exec=lite-xl %F
|
Exec=lite %F
|
||||||
Icon=lite-xl
|
Icon=lite-xl
|
||||||
Terminal=false
|
Terminal=false
|
||||||
StartupWMClass=lite-xl
|
StartupNotify=false
|
||||||
Categories=Development;IDE;
|
Categories=Utility;TextEditor;Development;
|
||||||
MimeType=text/plain;
|
MimeType=text/plain;
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="640"
|
||||||
|
height="640.00006"
|
||||||
|
viewBox="0 0 169.33333 169.33334"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||||
|
sodipodi:docname="lite.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.83124992"
|
||||||
|
inkscape:cx="320"
|
||||||
|
inkscape:cy="320.00003"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1299"
|
||||||
|
inkscape:window-height="713"
|
||||||
|
inkscape:window-x="67"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:lockguides="false" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-18.891505,-52.827384)">
|
||||||
|
<rect
|
||||||
|
style="opacity:1;fill:#302e31;fill-opacity:1;stroke:none;stroke-width:0.50622272;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect905"
|
||||||
|
width="169.33333"
|
||||||
|
height="169.33333"
|
||||||
|
x="18.891504"
|
||||||
|
y="52.827408"
|
||||||
|
ry="9.2305775" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 40.05817,87.223213 h 13.229166 v 7.937499 H 40.05817 Z"
|
||||||
|
id="rect821" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:none;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 61.224838,87.223213 h 18.520834 v 7.937499 H 61.224838 Z"
|
||||||
|
id="rect823" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="M 79.745674,87.223213 H 122.079 v 7.937499 H 79.745674 Z"
|
||||||
|
id="rect825" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 124.72483,87.223213 h 5.29167 v 7.937499 h -5.29167 z"
|
||||||
|
id="rect827" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#f1a04a;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 132.66232,87.223213 h 21.16667 v 7.937499 h -21.16667 z"
|
||||||
|
id="rect829" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 40.05817,100.45238 h 13.229166 v 7.93749 H 40.05817 Z"
|
||||||
|
id="rect831" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 40.05817,113.68155 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||||
|
id="rect833" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 58.579006,113.68155 h 13.229166 v 7.9375 H 58.579006 Z"
|
||||||
|
id="rect835" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 74.454002,113.68155 h 15.875 v 7.9375 h -15.875 z"
|
||||||
|
id="rect837" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 92.974831,113.68155 h 26.458339 v 7.9375 H 92.974831 Z"
|
||||||
|
id="rect839" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 122.07899,113.68155 h 10.58333 v 7.9375 h -10.58333 z"
|
||||||
|
id="rect841" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 137.95399,113.68155 h 15.875 v 7.9375 h -15.875 z"
|
||||||
|
id="rect843" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 40.05817,126.91071 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||||
|
id="rect845" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#e77280;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="M 58.579006,126.91071 H 77.09984 v 7.9375 H 58.579006 Z"
|
||||||
|
id="rect847" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 77.099838,126.91071 h 58.208332 v 7.93751 H 77.099838 Z"
|
||||||
|
id="rect849" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 40.05817,140.13988 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||||
|
id="rect851" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 58.579006,140.13988 h 66.145824 v 7.9375 H 58.579006 Z"
|
||||||
|
id="rect853"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#d682be;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 124.72483,140.13988 h 37.04167 v 7.9375 h -37.04167 z"
|
||||||
|
id="rect855" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 161.76651,140.13988 h 5.29165 v 7.9375 h -5.29165 z"
|
||||||
|
id="rect857" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 40.05817,153.36905 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||||
|
id="rect859" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#d682be;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 69.162338,153.36905 h 23.812498 v 7.9375 H 69.162338 Z"
|
||||||
|
id="rect861" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#f1a04a;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 95.620674,153.36905 h 13.229156 v 7.9375 H 95.620674 Z"
|
||||||
|
id="rect863" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#d682be;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="M 111.49567,153.36905 H 122.079 v 7.9375 h -10.58333 z"
|
||||||
|
id="rect865" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 40.05817,166.59822 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||||
|
id="rect867" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#49464b;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 40.05817,179.82738 h 13.229166 v 7.9375 H 40.05817 Z"
|
||||||
|
id="rect869" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#d682be;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 58.579006,179.82738 h 13.229166 v 7.9375 H 58.579006 Z"
|
||||||
|
id="rect871" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#e77280;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="m 74.454002,179.82738 h 15.875 v 7.9375 h -15.875 z"
|
||||||
|
id="rect873" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#d4d2d7;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
d="M 90.329002,179.82738 H 137.954 v 7.9375 H 90.329002 Z"
|
||||||
|
id="rect875"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<rect
|
||||||
|
style="opacity:1;fill:#e77280;fill-opacity:1;stroke:none;stroke-width:0.52916676;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect14"
|
||||||
|
width="18.520836"
|
||||||
|
height="7.9375005"
|
||||||
|
x="61.224838"
|
||||||
|
y="87.223213" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 12 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue