Compare commits
1 Commits
os4
...
files-comm
Author | SHA1 | Date |
---|---|---|
Francesco Abbate | 63331ac50c |
|
@ -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,3 +1,3 @@
|
||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: franko
|
github: rxi
|
||||||
|
|
|
@ -1,35 +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/**/*
|
|
|
@ -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,252 +0,0 @@
|
||||||
name: CI
|
|
||||||
|
|
||||||
# All builds use lhelper only for releases,
|
|
||||||
# otherwise for normal builds dependencies are dynamically linked.
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '*'
|
|
||||||
# tags:
|
|
||||||
# - 'v[0-9]*'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
archive_source_code:
|
|
||||||
name: Source Code Tarball
|
|
||||||
runs-on: ubuntu-18.04
|
|
||||||
# Only on tags/releases
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Python Setup
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.6
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get install -qq ninja-build
|
|
||||||
pip3 install meson
|
|
||||||
- name: Package
|
|
||||||
shell: bash
|
|
||||||
run: bash scripts/package.sh --version ${GITHUB_REF##*/} --debug --source
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: Source Code Tarball
|
|
||||||
path: "lite-xl-*-src.tar.gz"
|
|
||||||
|
|
||||||
build_linux:
|
|
||||||
name: Linux
|
|
||||||
runs-on: ubuntu-18.04
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
config:
|
|
||||||
- { name: "GCC", cc: gcc, cxx: g++ }
|
|
||||||
- { name: "clang", cc: clang, cxx: clang++ }
|
|
||||||
env:
|
|
||||||
CC: ${{ matrix.config.cc }}
|
|
||||||
CXX: ${{ matrix.config.cxx }}
|
|
||||||
steps:
|
|
||||||
- name: Set Environment Variables
|
|
||||||
if: ${{ matrix.config.cc == 'gcc' }}
|
|
||||||
run: |
|
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
||||||
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
|
||||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-linux-$(uname -m)" >> "$GITHUB_ENV"
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Python Setup
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.6
|
|
||||||
- name: Update Packages
|
|
||||||
run: sudo apt-get update
|
|
||||||
- name: Install Dependencies
|
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
run: bash scripts/install-dependencies.sh --debug
|
|
||||||
- name: Install Release Dependencies
|
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
run: |
|
|
||||||
bash scripts/install-dependencies.sh --debug --lhelper
|
|
||||||
bash scripts/lhelper.sh --debug
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
bash --version
|
|
||||||
bash scripts/build.sh --debug --forcefallback
|
|
||||||
- name: Package
|
|
||||||
if: ${{ matrix.config.cc == 'gcc' }}
|
|
||||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --binary
|
|
||||||
- name: AppImage
|
|
||||||
if: ${{ matrix.config.cc == 'gcc' && startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
run: bash scripts/appimage.sh --nobuild --version ${INSTALL_REF}
|
|
||||||
- name: Upload Artifacts
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
if: ${{ matrix.config.cc == 'gcc' }}
|
|
||||||
with:
|
|
||||||
name: Linux Artifacts
|
|
||||||
path: |
|
|
||||||
${{ env.INSTALL_NAME }}.tar.gz
|
|
||||||
LiteXL-${{ env.INSTALL_REF }}-x86_64.AppImage
|
|
||||||
|
|
||||||
build_macos:
|
|
||||||
name: macOS (x86_64)
|
|
||||||
runs-on: macos-10.15
|
|
||||||
env:
|
|
||||||
CC: clang
|
|
||||||
CXX: clang++
|
|
||||||
steps:
|
|
||||||
- name: System Information
|
|
||||||
run: |
|
|
||||||
system_profiler SPSoftwareDataType
|
|
||||||
bash --version
|
|
||||||
gcc -v
|
|
||||||
xcodebuild -version
|
|
||||||
- name: Set Environment Variables
|
|
||||||
run: |
|
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
||||||
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
|
||||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-macos-$(uname -m)" >> "$GITHUB_ENV"
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Python Setup
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
- name: Install Dependencies
|
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
run: bash scripts/install-dependencies.sh --debug
|
|
||||||
- name: Install Release Dependencies
|
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
run: |
|
|
||||||
bash scripts/install-dependencies.sh --debug --lhelper
|
|
||||||
bash scripts/lhelper.sh --debug
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
bash --version
|
|
||||||
bash scripts/build.sh --bundle --debug --forcefallback
|
|
||||||
- name: Error Logs
|
|
||||||
if: failure()
|
|
||||||
run: |
|
|
||||||
mkdir ${INSTALL_NAME}
|
|
||||||
cp /usr/var/lhenv/lite-xl/logs/* ${INSTALL_NAME}
|
|
||||||
tar czvf ${INSTALL_NAME}.tar.gz ${INSTALL_NAME}
|
|
||||||
# - name: Package
|
|
||||||
# if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
# run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons
|
|
||||||
- name: Create DMG Image
|
|
||||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --dmg
|
|
||||||
- name: Upload DMG Image
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: macOS DMG Image
|
|
||||||
path: ${{ env.INSTALL_NAME }}.dmg
|
|
||||||
- name: Upload Error Logs
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
if: failure()
|
|
||||||
with:
|
|
||||||
name: Error Logs
|
|
||||||
path: ${{ env.INSTALL_NAME }}.tar.gz
|
|
||||||
|
|
||||||
build_windows_msys2:
|
|
||||||
name: Windows
|
|
||||||
runs-on: windows-2019
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
msystem: [MINGW32, MINGW64]
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: msys2 {0}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: msys2/setup-msys2@v2
|
|
||||||
with:
|
|
||||||
#msystem: MINGW64
|
|
||||||
msystem: ${{ matrix.msystem }}
|
|
||||||
update: true
|
|
||||||
install: >-
|
|
||||||
base-devel
|
|
||||||
git
|
|
||||||
zip
|
|
||||||
- name: Set Environment Variables
|
|
||||||
run: |
|
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
||||||
echo "INSTALL_NAME=lite-xl-${GITHUB_REF##*/}-windows-$(uname -m)" >> "$GITHUB_ENV"
|
|
||||||
echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
|
||||||
- name: Install Dependencies
|
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
run: bash scripts/install-dependencies.sh --debug
|
|
||||||
- name: Install Release Dependencies
|
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
run: bash scripts/install-dependencies.sh --debug --lhelper
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
bash --version
|
|
||||||
bash scripts/build.sh --debug --forcefallback
|
|
||||||
- name: Error Logs
|
|
||||||
if: failure()
|
|
||||||
run: |
|
|
||||||
mkdir ${INSTALL_NAME}
|
|
||||||
cp /usr/var/lhenv/lite-xl/logs/* ${INSTALL_NAME}
|
|
||||||
tar czvf ${INSTALL_NAME}.tar.gz ${INSTALL_NAME}
|
|
||||||
- name: Package
|
|
||||||
run: bash scripts/package.sh --version ${INSTALL_REF} --debug --addons --binary
|
|
||||||
- name: Build Installer
|
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
run: bash scripts/innosetup/innosetup.sh --debug
|
|
||||||
- name: Upload Artifacts
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: Windows Artifacts
|
|
||||||
path: |
|
|
||||||
LiteXL*.exe
|
|
||||||
${{ env.INSTALL_NAME }}.zip
|
|
||||||
- name: Upload Error Logs
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
if: failure()
|
|
||||||
with:
|
|
||||||
name: Error Logs
|
|
||||||
path: ${{ env.INSTALL_NAME }}.tar.gz
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
name: Deployment
|
|
||||||
runs-on: ubuntu-18.04
|
|
||||||
# if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
if: false
|
|
||||||
needs:
|
|
||||||
- archive_source_code
|
|
||||||
- build_linux
|
|
||||||
- build_macos
|
|
||||||
- build_windows_msys2
|
|
||||||
steps:
|
|
||||||
- name: Set Environment Variables
|
|
||||||
run: echo "INSTALL_REF=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
|
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: Linux Artifacts
|
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: macOS DMG Image
|
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: Source Code Tarball
|
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: Windows Artifacts
|
|
||||||
- name: Display File Information
|
|
||||||
shell: bash
|
|
||||||
run: ls -lR
|
|
||||||
# Note: not using `actions/create-release@v1`
|
|
||||||
# because it cannot update an existing release
|
|
||||||
# see https://github.com/actions/create-release/issues/29
|
|
||||||
- uses: softprops/action-gh-release@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
tag_name: ${{ env.INSTALL_REF }}
|
|
||||||
name: Release ${{ env.INSTALL_REF }}
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
files: |
|
|
||||||
lite-xl-${{ env.INSTALL_REF }}-*
|
|
||||||
LiteXL*.AppImage
|
|
||||||
LiteXL*.exe
|
|
|
@ -1,26 +1,8 @@
|
||||||
build*/
|
build*
|
||||||
.build*/
|
.build*
|
||||||
lhelper/
|
|
||||||
submodules/
|
|
||||||
subprojects/lua/
|
|
||||||
subprojects/reproc/
|
|
||||||
/appimage*
|
|
||||||
.ccls-cache
|
|
||||||
.lite-debug.log
|
|
||||||
.run*
|
.run*
|
||||||
*.diff
|
|
||||||
*.exe
|
|
||||||
*.tar.gz
|
|
||||||
*.zip
|
*.zip
|
||||||
*.DS_Store
|
.lite-debug.log
|
||||||
*App*
|
subprojects/lua
|
||||||
compile_commands.json
|
subprojects/libagg
|
||||||
error.txt
|
sybprojects/lua
|
||||||
lite-xl*
|
|
||||||
LiteXL*
|
|
||||||
lite
|
|
||||||
.config/
|
|
||||||
*.lha
|
|
||||||
release_files
|
|
||||||
*.o
|
|
||||||
|
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2020-2021 Francesco Abbate
|
Copyright (c) 2020 rxi
|
||||||
|
|
||||||
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
|
||||||
|
|
82
Makefile.os4
82
Makefile.os4
|
@ -1,82 +0,0 @@
|
||||||
#
|
|
||||||
# Project: Lite XL
|
|
||||||
#
|
|
||||||
# Created on: 26-12-2021
|
|
||||||
#
|
|
||||||
|
|
||||||
LiteXL_OBJ := \
|
|
||||||
src/dirmonitor.o src/main.o src/rencache.o src/renderer.o \
|
|
||||||
src/renwindow.o src/api/api.o src/api/regex.o \
|
|
||||||
src/api/renderer.o src/api/system.o src/platform/amigaos4.o
|
|
||||||
|
|
||||||
|
|
||||||
outfile := lite
|
|
||||||
compiler := gcc
|
|
||||||
cxxcompiler := g++
|
|
||||||
|
|
||||||
INCPATH := -Isrc -Ilib/dmon -I/sdk/local/newlib/include/SDL2 -I/sdk/local/common/include/freetype2
|
|
||||||
DFLAGS := -D__USE_INLINE__ -DLITE_XL_DATA_USE_EXEDIR
|
|
||||||
# -DLITE_USE_SDL_RENDERER
|
|
||||||
# -Wextra -Wall
|
|
||||||
CFLAGS := -Werror -Wwrite-strings -O3 -g -std=gnu11 -fno-strict-aliasing
|
|
||||||
# "-gstabs -finstrument-functions -fno-inline -DPROFILING"
|
|
||||||
LFLAGS := -mcrt=newlib -static-libgcc -static-libstdc++ -lauto -lpcre2 -lSDL2 -llua -lagg -lfreetype -lm -lunix -lpthread -athread=native
|
|
||||||
# " -lprofyle"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.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/dirmonitor.o: src/dirmonitor.c src/platform/amigaos4.h
|
|
||||||
|
|
||||||
src/main.o: src/main.c src/api/api.h src/rencache.h \
|
|
||||||
src/renderer.h src/platform/amigaos4.h src/dirmonitor.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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
release:
|
|
||||||
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_OS4.md release/LiteXL2/
|
|
||||||
cp LICENSE release/LiteXL2/
|
|
||||||
lha -aeqr3 a LiteXL2_OS4.lha release/
|
|
||||||
|
|
193
README.md
193
README.md
|
@ -1,159 +1,104 @@
|
||||||
# Lite XL
|
# Lite XL
|
||||||
|
|
||||||
[![CI]](https://github.com/lite-xl/lite-xl/actions/workflows/build.yml)
|
![screenshot-dark](https://user-images.githubusercontent.com/433545/85227778-b42abc80-b3df-11ea-9dd3-e788f6c71882.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 and Linux
|
||||||
|
* **[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.
|
Please note that Lite XL is compatible with Lite for all the plugins and color themes.
|
||||||
* **[Get plugins]** — Add additional functionality, adapted for Lite XL.
|
Yet we provide a specific lite-plugins directory for Lite XL because in some cases some adaptations may be needed to make them work better with Lite XL.
|
||||||
* **[Get color themes]** — Add additional colors themes.
|
The address for modified plugins is http://github.com/franko/lite-plugins.
|
||||||
|
Currently only the "workspace" plugin needs a minor adjustment to restore the workspace when the command `core:restart` is used.
|
||||||
|
|
||||||
Please refer to our [website] for the user and developer documentation,
|
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).
|
||||||
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.
|
|
||||||
We provide a separate lite-xl-plugins repository for Lite XL, because in some cases
|
|
||||||
some adaptations may be needed to make them work better with Lite XL.
|
|
||||||
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
|
|
||||||
[changelog].
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
Lite XL is derived from Lite. It is a lightweight text editor written mostly in Lua — it aims to provide
|
||||||
|
something practical, pretty, *small* and fast easy to modify and extend, or to use without doing either.
|
||||||
|
|
||||||
Lite XL is derived from lite.
|
The aim of Lite XL compared to Lite is to be more user friendly, improve the quality of the font rendering and reduce CPU usage.
|
||||||
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.
|
|
||||||
|
|
||||||
The aim of Lite XL compared to lite is to be more user friendly,
|
|
||||||
improve the quality of font rendering, and reduce CPU usage.
|
|
||||||
|
|
||||||
## Customization
|
## Customization
|
||||||
|
Additional functionality can be added through plugins which are available from
|
||||||
|
the [plugins repository](https://github.com/rxi/lite-plugins) or from the [plugin repository adapted to Lite XL](https://github.com/franko/lite-plugins); additional color
|
||||||
|
themes can be found in the [colors repository](https://github.com/rxi/lite-colors).
|
||||||
|
The editor can be customized by making changes to the
|
||||||
|
[user module](data/user/init.lua).
|
||||||
|
|
||||||
Additional functionality can be added through plugins which are available in
|
## Building
|
||||||
the [plugins repository] or in the [Lite XL plugins repository].
|
|
||||||
|
|
||||||
Additional color themes can be found in the [colors repository].
|
You can build the project yourself using the Meson build.
|
||||||
These color themes are bundled with all releases of Lite XL by default.
|
|
||||||
|
|
||||||
## Quick Build Guide
|
In addition the script `build-packages.sh` can be used to compile Lite XL and create a package adapted to the OS, Linux, Windows or Mac OS X.
|
||||||
|
|
||||||
If you compile Lite XL yourself, it is recommended to use the script
|
The following libraries are required:
|
||||||
`build-packages.sh`:
|
|
||||||
|
- freetype2
|
||||||
|
- SDL2
|
||||||
|
|
||||||
|
The libraries libagg and Lua 5.2 are optional.
|
||||||
|
If they are not found they will be automatically downloaded and compiled by the Meson build system.
|
||||||
|
Otherwise, if they are present they will be used to compile Lite XL.
|
||||||
|
|
||||||
|
On a debian based systems the required library and Meson can be installed using the 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 use the commands:
|
||||||
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 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 `/`.
|
When performing the "meson setup" command you may enable the "portable" option.
|
||||||
To build a bundle application on macOS:
|
|
||||||
|
If this latter is enabled Lite XL is built to use a "data" and a "user" directory
|
||||||
|
from the same directory of the executable.
|
||||||
|
If "portable" is not enabled (this is the default) Lite XL will use unix-like
|
||||||
|
directory locations.
|
||||||
|
In this case the "data" directory will be `$prefix/share/lite-xl` and the "user"
|
||||||
|
directory will be `$HOME/.config/lite-xl`.
|
||||||
|
The `$prefix` is determined as the directory such as `$prefix/bin` corresponds to
|
||||||
|
the location of the executable.
|
||||||
|
The `$HOME` is determined from the corresponding environment variable.
|
||||||
|
As a special case on Windows the variable `$USERPROFILE` will be used instead.
|
||||||
|
|
||||||
|
If you want to install Lite XL on Windows or Mac OS X we suggest to use the script `build-packages.sh`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
meson setup --buildtype=release --Dbundle=true --prefix / build
|
bash build-packages.sh <version> <arch>
|
||||||
meson compile -C build
|
|
||||||
DESTDIR="$(pwd)/Lite XL.app" meson install --skip-subprojects -C build
|
# In alternative the -portable option can be used like below:
|
||||||
|
# bash build-packages.sh -portable <version> <arch>
|
||||||
```
|
```
|
||||||
|
|
||||||
Please note that the package is relocatable to any prefix and the option prefix
|
It will run meson and create a Zip file that can be easily installed or uninstalled.
|
||||||
affects only the place where the application is actually installed.
|
|
||||||
|
|
||||||
## Installing Prebuilt
|
|
||||||
|
|
||||||
Head over to [releases](https://github.com/lite-xl/lite-xl/releases) and download the version for your operating system.
|
|
||||||
|
|
||||||
### Linux
|
|
||||||
|
|
||||||
Unzip the file and `cd` into the `lite-xl` directory:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
tar -xzf <file>
|
|
||||||
cd lite-xl
|
|
||||||
```
|
|
||||||
|
|
||||||
To run lite-xl without installing:
|
|
||||||
```sh
|
|
||||||
cd bin
|
|
||||||
./lite-xl
|
|
||||||
```
|
|
||||||
|
|
||||||
To install lite-xl copy files over into appropriate directories:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
mkdir -p $HOME/.local/bin && cp bin/lite-xl $HOME/.local/bin
|
|
||||||
cp -r share $HOME/.local
|
|
||||||
```
|
|
||||||
|
|
||||||
If `$HOME/.local/bin` is not in PATH:
|
|
||||||
|
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
|
Please note the, while compiling Lite XL on Mac OS X should work Mac OS X
|
||||||
|
is not currently supported.
|
||||||
|
|
||||||
## 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.
|
so as a plugin, after which a pull request to the
|
||||||
|
[plugins repository](https://github.com/rxi/lite-plugins) can be made.
|
||||||
|
|
||||||
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
|
|
||||||
[colors repository]: https://github.com/lite-xl/lite-xl-colors
|
|
||||||
[LICENSE]: LICENSE
|
|
||||||
[licenses]: licenses/licenses.md
|
|
||||||
|
|
204
README_OS4.md
204
README_OS4.md
|
@ -1,204 +0,0 @@
|
||||||
# Lite XL v2 for AmigaOS 4.1 FE
|
|
||||||
|
|
||||||
Lite XL is a lightweight text editor written in Lua.
|
|
||||||
|
|
||||||
The port is not perfect and might has issues here and there. For example
|
|
||||||
the filesystem notifications are not working yet. So when you make changes
|
|
||||||
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.
|
|
||||||
|
|
||||||
## New features against Lite XL v1
|
|
||||||
- Faster file scrolling
|
|
||||||
- Faster switch between tabs
|
|
||||||
- Reposition tabs at the side or at the bottom of other tabs, making
|
|
||||||
multiple columns/rows of opened files
|
|
||||||
- Multiple cursor editing
|
|
||||||
- Better font manipulation and appearance
|
|
||||||
- Faster transitions
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
You can extract the Lite XL archive wherever you want and run the *lite*
|
|
||||||
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 2.0.4
|
|
||||||
and not the latest version that is available by the development team.
|
|
||||||
This means that some of the latest plugins might 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 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.
|
|
||||||
|
|
||||||
**autowrap**
|
|
||||||
Automatically hardwraps lines when typing
|
|
||||||
|
|
||||||
**bigclock**
|
|
||||||
Shows the current time and date in a view with large text
|
|
||||||
|
|
||||||
**bracketmatch**
|
|
||||||
Underlines matching pair for bracket under the caret
|
|
||||||
|
|
||||||
**colorpreview**
|
|
||||||
Underlays color values (eg. `#ff00ff` or `rgb(255, 0, 255)`) with their
|
|
||||||
resultant color.
|
|
||||||
|
|
||||||
**eofnewline-xl**
|
|
||||||
Make sure the file ends with one blank line.
|
|
||||||
|
|
||||||
**ephemeral_tabs**
|
|
||||||
Preview tabs. Opening a doc will replace the contents of the preview tab.
|
|
||||||
Marks tabs as non-preview on any change or tab double clicking.
|
|
||||||
|
|
||||||
**ghmarkdown**
|
|
||||||
Opens a preview of the current markdown file in a browser window
|
|
||||||
|
|
||||||
**indentguide**
|
|
||||||
Adds indent guides
|
|
||||||
|
|
||||||
**language_make**
|
|
||||||
Syntax for the Make build system language
|
|
||||||
|
|
||||||
**language_sh**
|
|
||||||
Syntax for shell scripting language
|
|
||||||
|
|
||||||
**lfautoinsert**
|
|
||||||
Automatically inserts indentation and closing bracket/text after newline
|
|
||||||
|
|
||||||
**markers**
|
|
||||||
Add markers to docs and jump between them quickly
|
|
||||||
|
|
||||||
**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
|
|
||||||
|
|
||||||
**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
|
|
||||||
|
|
||||||
**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
|
|
||||||
- Michael Trebilcock for his port on liblua
|
|
||||||
- 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.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 problem you might get
|
|
||||||
by using this software.
|
|
||||||
|
|
Binary file not shown.
|
@ -1,164 +1,209 @@
|
||||||
#!/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 master "$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 "-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
|
|
||||||
|
|
||||||
for i in "$@"; do
|
# Ordinary release build
|
||||||
case $i in
|
lite_build () {
|
||||||
-h|--help)
|
local meson_options=("-Dportable=$1")
|
||||||
show_help
|
local build="$2"
|
||||||
exit 0
|
build_dir_is_usable "$build" || exit 1
|
||||||
;;
|
rm -fr "$build"
|
||||||
-b|--builddir)
|
meson setup --buildtype=release "${meson_options[@]}" "$build" || exit 1
|
||||||
build_dir="$2"
|
ninja -C "$build" || exit 1
|
||||||
shift
|
}
|
||||||
shift
|
|
||||||
;;
|
# Build using Profile Guided Optimizations (PGO)
|
||||||
-d|--destdir)
|
lite_build_pgo () {
|
||||||
dest_dir="$2"
|
local meson_options=("-Dportable=$1")
|
||||||
shift
|
local build="$2"
|
||||||
shift
|
build_dir_is_usable "$build" || exit 1
|
||||||
;;
|
rm -fr "$build"
|
||||||
-f|--forcefallback)
|
meson setup --buildtype=release "${meson_options[@]}" -Db_pgo=generate "$build" || exit 1
|
||||||
force_fallback="--forcefallback"
|
ninja -C "$build" || exit 1
|
||||||
shift
|
copy_directory_from_repo data "$build/src"
|
||||||
;;
|
"$build/src/lite"
|
||||||
-p|--prefix)
|
meson configure -Db_pgo=use "$build"
|
||||||
prefix="$2"
|
ninja -C "$build" || exit 1
|
||||||
shift
|
}
|
||||||
shift
|
|
||||||
;;
|
lite_build_package_windows () {
|
||||||
-v|--version)
|
local portable="$1"
|
||||||
version="$2"
|
local build="$2"
|
||||||
shift
|
local version="$3"
|
||||||
shift
|
local arch="$4"
|
||||||
;;
|
local os="win"
|
||||||
-A|--appimage)
|
local pdir=".package-build/lite-xl"
|
||||||
appimage="--appimage"
|
if [ $portable == "true" ]; then
|
||||||
shift
|
local bindir="$pdir"
|
||||||
;;
|
local datadir="$pdir/data"
|
||||||
-B|--bundle)
|
else
|
||||||
bundle="--bundle"
|
echo "WARNING: using non portable option on unix-like system"
|
||||||
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
|
|
||||||
;;
|
|
||||||
-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.zip"
|
||||||
|
zip "$package_name" -r "lite-xl"
|
||||||
|
mv "$package_name" ..
|
||||||
|
popd
|
||||||
|
rm -fr ".package-build"
|
||||||
|
echo "created package $package_name"
|
||||||
|
}
|
||||||
|
|
||||||
if [[ -n $1 ]]; then
|
lite_build_package_macosx () {
|
||||||
show_help
|
local portable="$1"
|
||||||
|
local build="$2"
|
||||||
|
local version="$3"
|
||||||
|
local arch="$4"
|
||||||
|
local os="macosx"
|
||||||
|
local pdir=".package-build/lite-xl.app/Contents/MacOS"
|
||||||
|
if [ $portable == "true" ]; 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"
|
||||||
|
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="$1"
|
||||||
|
local build="$2"
|
||||||
|
local version="$3"
|
||||||
|
local arch="$4"
|
||||||
|
local os="linux"
|
||||||
|
local pdir=".package-build/lite-xl"
|
||||||
|
if [ $portable == "true" ]; then
|
||||||
|
echo "WARNING: using portable option on unix-like system"
|
||||||
|
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"
|
||||||
|
pushd ".package-build"
|
||||||
|
local package_name="lite-xl-$os-$arch.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_macosx "$@"
|
||||||
|
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 \
|
|
||||||
$pgo
|
|
||||||
|
|
||||||
source scripts/package.sh \
|
|
||||||
${build_dir_option[@]} \
|
|
||||||
${dest_dir_option[@]} \
|
|
||||||
${prefix_option[@]} \
|
|
||||||
${version_option[@]} \
|
|
||||||
--binary \
|
|
||||||
--addons \
|
|
||||||
$debug \
|
|
||||||
$appimage \
|
|
||||||
$dmg \
|
|
||||||
$innosetup \
|
|
||||||
$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"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "$1" || -z "$2" ]]; then
|
||||||
|
echo "usage: $0 [options] <version> <arch>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
portable=false
|
||||||
|
while [ ! -z {$1+x} ]; do
|
||||||
|
case $1 in
|
||||||
|
-pgo)
|
||||||
|
pgo=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-portable)
|
||||||
|
portable=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
version="$1"
|
||||||
|
arch="$2"
|
||||||
|
break
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
build_dir=".build-$arch"
|
||||||
|
|
||||||
|
if [ -z ${pgo+set} ]; then
|
||||||
|
lite_build "$portable" "$build_dir"
|
||||||
|
else
|
||||||
|
lite_build_pgo "$portable" "$build_dir"
|
||||||
|
fi
|
||||||
|
lite_copy_third_party_modules "$build_dir"
|
||||||
|
lite_build_package "$portable" "$build_dir" "$version" "$arch"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ $1 == -portable ]]; then
|
||||||
|
cflags="-DLITE_XL_DATA_USE_EXEDIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
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; 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"
|
||||||
|
|
319
changelog.md
319
changelog.md
|
@ -1,321 +1,6 @@
|
||||||
This files document the changes done in Lite XL for each release.
|
Lite XL is following closely [rxi/lite](https://github.com/rxi/lite) but with some enhancements.
|
||||||
|
|
||||||
### 2.0.3
|
This files document the differences between Lite XL and rxi/lite for each version.
|
||||||
|
|
||||||
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 immediatly 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 filesystems.
|
|
||||||
|
|
||||||
Fix problem with python syntax highliting, contributed by @dflock.
|
|
||||||
|
|
||||||
### 2.0.2
|
|
||||||
|
|
||||||
Fix problem project directory when starting the application from Launcher on macOS.
|
|
||||||
|
|
||||||
Improved LogView. Entries can now be expanded and there is a context menu to copy the item's content.
|
|
||||||
|
|
||||||
Change the behavior of `ctrl+d` to add a multi-cursor selection to the next occurrence.
|
|
||||||
The old behavior to move the selection to the next occurrence is now done using the shortcut `ctrl+f3`.
|
|
||||||
|
|
||||||
Added a command to create a multi-cursor with all the occurrences of the current selection.
|
|
||||||
Activated with the shortcut `ctrl+shift+l`.
|
|
||||||
|
|
||||||
Fix problem when trying to close an unsaved new document.
|
|
||||||
|
|
||||||
No longer shows an error for the `-psn` argument passed to the application on macOS.
|
|
||||||
|
|
||||||
Fix `treeview:open-in-system` command on Windows.
|
|
||||||
|
|
||||||
Fix rename command to update name of document if opened.
|
|
||||||
|
|
||||||
Improve the find and replace dialog so that previously used expressions can be recalled
|
|
||||||
using "up" and "down" keys.
|
|
||||||
|
|
||||||
Build package script rewrite with many improvements.
|
|
||||||
|
|
||||||
Use bigger fonts by default.
|
|
||||||
|
|
||||||
Other minor improvements and fixes.
|
|
||||||
|
|
||||||
With many thanks to the contributors: @adamharrison, @takase1121, @Guldoman, @redtide, @Timofffee, @boppyt, @Jan200101.
|
|
||||||
|
|
||||||
### 2.0.1
|
|
||||||
|
|
||||||
Fix a few bugs and we mandate the mod-version 2 for plugins.
|
|
||||||
This means that users should ensure they have up-to-date plugins for Lite XL 2.0.
|
|
||||||
|
|
||||||
Here some details about the bug fixes:
|
|
||||||
|
|
||||||
- fix a bug that created a fatal error when using the command to change project folder or when closing all the active documents
|
|
||||||
- add a limit to avoid scaling fonts too much and fix a related invalid memory access for very small fonts
|
|
||||||
- fix focus problem with NagView when switching project directory
|
|
||||||
- fix error that prevented the verification of plugins versions
|
|
||||||
- fix error on X11 that caused a bug window event on exit
|
|
||||||
|
|
||||||
### 2.0
|
|
||||||
|
|
||||||
The 2.0 version of lite contains *breaking changes* to lite, in terms of how plugin settings are structured;
|
|
||||||
any custom plugins may need to be adjusted accordingly (see note below about plugin namespacing).
|
|
||||||
|
|
||||||
Contains the following new features:
|
|
||||||
|
|
||||||
Full PCRE (regex) support for find and replace, as well as in language syntax definitions. Can be accessed
|
|
||||||
programatically via the lua `regex` module.
|
|
||||||
|
|
||||||
A full, finalized subprocess API, using libreproc. Subprocess can be started and interacted with using
|
|
||||||
`Process.new`.
|
|
||||||
|
|
||||||
Support for multi-cursor editing. Cursors can be created by either ctrl+clicking on the screen, or by using
|
|
||||||
the keyboard shortcuts ctrl+shift+up/down to create an additional cursor on the previous/next line.
|
|
||||||
|
|
||||||
All build systems other than meson removed.
|
|
||||||
|
|
||||||
A more organized directory structure has been implemented; in particular a docs folder which contains C api
|
|
||||||
documentation, and a resource folder which houses all build resources.
|
|
||||||
|
|
||||||
Plugin config namespacing has been implemented. This means that instead of using `config.myplugin.a`,
|
|
||||||
to read settings, and `config.myplugin = false` to disable plugins, this has been changed to
|
|
||||||
`config.plugins.myplugin.a`, and `config.plugins.myplugin = false` repsectively. This may require changes to
|
|
||||||
your user plugin, or to any custom plugins you have.
|
|
||||||
|
|
||||||
A context menu on right click has been added.
|
|
||||||
|
|
||||||
Changes to how we deal with indentation have been implemented; in particular, hitting home no longer brings you
|
|
||||||
to the start of a line, it'll bring you to the start of indentation, which is more in line with other editors.
|
|
||||||
|
|
||||||
Lineguide, and scale plugins moved into the core, and removed from `lite-plugins`. This may also require you to
|
|
||||||
adjust your personal plugin folder to remove these if they're present.
|
|
||||||
|
|
||||||
In addition, there have been many other small fixes and improvements, too numerous to list here.
|
|
||||||
|
|
||||||
### 1.16.11
|
|
||||||
|
|
||||||
When opening directories with too many files lite-xl now keep diplaying files and directories in the treeview.
|
|
||||||
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.
|
|
||||||
|
|
||||||
Example user module snippet that makes all comments italic:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local style = require "core.style"
|
|
||||||
|
|
||||||
-- italic.ttf must be provided by the user
|
|
||||||
local italic = renderer.font.load("italic.ttf", 14)
|
|
||||||
style.syntax_fonts["comment"] = italic
|
|
||||||
```
|
|
||||||
|
|
||||||
Improved indentation behavior by @adamharrison.
|
|
||||||
|
|
||||||
Fix bug with close button not working in borderless window mode.
|
|
||||||
|
|
||||||
Fix problem with normalization of filename for opened documents.
|
|
||||||
|
|
||||||
### 1.16.10
|
|
||||||
|
|
||||||
Improved syntax highlight system thanks to @liquidev and @adamharrison.
|
|
||||||
Thanks to the new system we provide more a accurate syntax highlighting for Lua, C and C++.
|
|
||||||
Other syntax improvements contributed by @vincens2005.
|
|
||||||
|
|
||||||
Move to JetBrains Mono and Fira Sans fonts for code and UI respectively.
|
|
||||||
Thet are provided under the SIL Open Font License, Version 1.1.
|
|
||||||
See `doc/licenses.md` for license details.
|
|
||||||
|
|
||||||
Fixed bug with fonts and rencache module.
|
|
||||||
Under very specific situations the application was crashing due to invalid memory access.
|
|
||||||
|
|
||||||
Add documentation for keymap binding, thanks to @Janis-Leuenberger.
|
|
||||||
|
|
||||||
Added a contributors page in `doc/contributors.md`.
|
|
||||||
|
|
||||||
### 1.16.9
|
|
||||||
|
|
||||||
Fix a bug related to nested panes resizing.
|
|
||||||
|
|
||||||
Fix problem preventing creating a new file.
|
|
||||||
|
|
||||||
### 1.16.8
|
|
||||||
|
|
||||||
Fix application crash when using the command `core:restart`.
|
|
||||||
|
|
||||||
Improve application startup to reduce "flashing".
|
|
||||||
|
|
||||||
Move to new plugins versioning using tag `mod-version:1`.
|
|
||||||
The mod-version is a single digit version that tracks the
|
|
||||||
plugins compatibility version independently from the lite-xl
|
|
||||||
version.
|
|
||||||
|
|
||||||
For backward compatibility the tag `-- lite-xl 1.16` is considered equivalent to
|
|
||||||
`mod-version:1` so users don't need to update their plugins.
|
|
||||||
|
|
||||||
Both kind of tags can appear in new plugins in the form:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
-- mod-version:1 -- lite-xl 1.16
|
|
||||||
```
|
|
||||||
|
|
||||||
where the old tag needs to appear at the end for compatibility.
|
|
||||||
|
|
||||||
### 1.16.7
|
|
||||||
|
|
||||||
Add support for retina displays on Mac OS X.
|
|
||||||
|
|
||||||
Fix a few problems related to file paths.
|
|
||||||
|
|
||||||
### 1.16.6
|
|
||||||
|
|
||||||
Implement a system to check the compatibility of plugins by checking a release tag.
|
|
||||||
Plugins that don't have the release tag will not be loaded.
|
|
||||||
|
|
||||||
Improve and extend the NagView with keyboard commands.
|
|
||||||
Special thanks to @takase1121 for the implementation and @liquidev for proposing and
|
|
||||||
discussing the enhancements.
|
|
||||||
|
|
||||||
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
|
|
||||||
resources and his support.
|
|
||||||
|
|
||||||
Add hook function `DocView.on_text_change` so that plugin can accurately react on document changes.
|
|
||||||
Thanks to @vincens2005 for the suggestion and testing the implementation.
|
|
||||||
|
|
||||||
Enable borderless window mode using the `config.borderless` variable.
|
|
||||||
If enable the system window's bar will be replaced by a title bar provided
|
|
||||||
by lite-xl itself.
|
|
||||||
|
|
||||||
Fix a drawing engine bug that caused increased CPU usage for drawing operations.
|
|
||||||
|
|
||||||
Add `system.set_window_opacity` function.
|
|
||||||
|
|
||||||
Add codepoint replacement API to support natively the "draw whitespaces" option.
|
|
||||||
It supersedes the `drawwhitespace` plugin. If can be configured using the
|
|
||||||
`config.draw_whitespace` boolean variable and enabled and disables using the
|
|
||||||
commands `draw-whitespace:toggle`, `draw-whitespace:enable`,
|
|
||||||
`draw-whitespace:disable`.
|
|
||||||
|
|
||||||
Improve the NagView to accept keyboard commands and introduce dialog commands.
|
|
||||||
|
|
||||||
Add hook function `Doc:on_text_change` called on document changes, to be used by plugins.
|
|
||||||
|
|
||||||
### 1.16.5
|
|
||||||
|
|
||||||
Hotfix for Github's issue https://github.com/franko/lite-xl/issues/122
|
|
||||||
|
|
||||||
### 1.16.4
|
|
||||||
|
|
||||||
Add tooltips to show full file names from the tree-view.
|
|
||||||
|
|
||||||
Introduce NagView to show warning dialog about unsaved files.
|
|
||||||
|
|
||||||
Detect High-DPI displays on Linux using Xft.dpi entry from xrdb's output.
|
|
||||||
|
|
||||||
Made animations independent of framerate, and added a config setting
|
|
||||||
`config.animation_rate` for customizing the speed of animations.
|
|
||||||
|
|
||||||
Made borders between tabs look cleaner.
|
|
||||||
|
|
||||||
Fix problem with files using hard tabs.
|
|
||||||
|
|
||||||
### 1.16.2
|
|
||||||
|
|
||||||
Implement close button for tabs.
|
|
||||||
|
|
||||||
Make the command view list of suggestion scrollable to see all the items.
|
|
||||||
|
|
||||||
Improve update/resize behavior of treeview and toolbar.
|
|
||||||
|
|
||||||
### 1.16.1
|
|
||||||
|
|
||||||
Improve behavior of commands to move, delete and duplicate multiple lines:
|
|
||||||
no longer include the last line if it does not contain any selection.
|
|
||||||
|
|
||||||
Fix graphical artefacts when rendering some fonts like FiraSans.
|
|
||||||
|
|
||||||
Introduce the `config.transitions` boolean variable.
|
|
||||||
When false the transitions will be disabled and changes will be done immediately.
|
|
||||||
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 problem with spacing in treeview when using monospace fonts.
|
|
||||||
|
|
||||||
### 1.16
|
|
||||||
|
|
||||||
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
|
|
||||||
to the more important commands.
|
|
||||||
|
|
||||||
Make the treeview actually resizable and shows the resize cursor only when panes
|
|
||||||
are actually resizable.
|
|
||||||
|
|
||||||
Add config mechanism to disable a plugin by setting
|
|
||||||
`config.<plugin-name> = false`.
|
|
||||||
|
|
||||||
Improve the "detect indent" plugin to take into account the syntax and exclude comments
|
|
||||||
for much accurate results.
|
|
||||||
|
|
||||||
Add command `root:close-all` to close all the documents currently opened.
|
|
||||||
|
|
||||||
Show the full path filename of the active document in the window's title.
|
|
||||||
|
|
||||||
Fix problem with user's module reload not always enabled.
|
|
||||||
|
|
||||||
### 1.15
|
|
||||||
|
|
||||||
**Project directories**
|
|
||||||
|
|
||||||
Extend your project by adding more directories using the command `core:add-directory`.
|
|
||||||
To remove them use the corresponding command `core:remove-directory`.
|
|
||||||
|
|
||||||
**Workspaces**
|
|
||||||
|
|
||||||
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 will
|
|
||||||
also remember the window size and position and the additonal project directories.
|
|
||||||
To not interfere with the project's files the workspace file is saved in the personal
|
|
||||||
Lite's configuration folder.
|
|
||||||
On unix-like systems it will be in: `$HOME/.config/lite-xl/ws`.
|
|
||||||
|
|
||||||
**Scrolling the Tree View**
|
|
||||||
|
|
||||||
It is now possible to scroll the tree view when there are too many visible items.
|
|
||||||
|
|
||||||
**Recognize `~` for the home directory**
|
|
||||||
|
|
||||||
As in the unix shell `~` is now used to identify the home directory.
|
|
||||||
|
|
||||||
**Files and Directories**
|
|
||||||
|
|
||||||
Add command to create a new empty directory within the project using the command
|
|
||||||
`files:create-directory`.
|
|
||||||
In addition a control-click on a project directory will prompt the user to create
|
|
||||||
a new directory inside the directory pointed.
|
|
||||||
|
|
||||||
**New welcome screen**
|
|
||||||
|
|
||||||
Show 'Lite XL' instead of 'lite' and the version number.
|
|
||||||
|
|
||||||
**Various fixes and improvements**
|
|
||||||
|
|
||||||
A few quirks previously with some of the new features have been fixed for a better user experience.
|
|
||||||
|
|
||||||
### 1.14
|
### 1.14
|
||||||
|
|
||||||
|
|
|
@ -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) }
|
|
|
@ -41,14 +41,11 @@ function command.get_all_valid()
|
||||||
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 cmd and cmd.predicate(...) then
|
if cmd and cmd.predicate() then
|
||||||
cmd.perform(...)
|
cmd.perform()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
|
@ -62,10 +59,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
function command.add_defaults()
|
function command.add_defaults()
|
||||||
local reg = {
|
local reg = { "core", "root", "command", "doc", "findreplace", "files" }
|
||||||
"core", "root", "command", "doc", "findreplace",
|
|
||||||
"files", "drawwhitespace", "dialog"
|
|
||||||
}
|
|
||||||
for _, name in ipairs(reg) do
|
for _, name in ipairs(reg) do
|
||||||
require("core.commands." .. name)
|
require("core.commands." .. name)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
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()
|
||||||
|
return core.active_view:is(CommandView)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
command.add(has_commandview, {
|
||||||
["command:submit"] = function()
|
["command:submit"] = function()
|
||||||
core.active_view:submit()
|
core.active_view:submit()
|
||||||
end,
|
end,
|
||||||
|
|
|
@ -6,12 +6,18 @@ local LogView = require "core.logview"
|
||||||
|
|
||||||
|
|
||||||
local fullscreen = false
|
local fullscreen = false
|
||||||
local restore_title_view = false
|
|
||||||
|
local function home_encode_list(paths)
|
||||||
|
local t = {}
|
||||||
|
for i = 1, #paths do
|
||||||
|
t[i] = common.home_encode(paths[i])
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
local function suggest_directory(text)
|
local function suggest_directory(text)
|
||||||
text = common.home_expand(text)
|
text = common.home_expand(text)
|
||||||
return common.home_encode_list((text == "" or text == common.home_expand(common.dirname(core.project_dir)))
|
return home_encode_list(text == "" and core.recent_projects or common.dir_path_suggest(text))
|
||||||
and core.recent_projects or common.dir_path_suggest(text))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
command.add(nil, {
|
command.add(nil, {
|
||||||
|
@ -29,12 +35,7 @@ command.add(nil, {
|
||||||
|
|
||||||
["core:toggle-fullscreen"] = function()
|
["core:toggle-fullscreen"] = function()
|
||||||
fullscreen = not fullscreen
|
fullscreen = not fullscreen
|
||||||
if fullscreen then
|
|
||||||
restore_title_view = core.title_view.visible
|
|
||||||
end
|
|
||||||
system.set_window_mode(fullscreen and "fullscreen" or "normal")
|
system.set_window_mode(fullscreen and "fullscreen" or "normal")
|
||||||
core.show_title_bar(not fullscreen and restore_title_view)
|
|
||||||
core.title_view:configure_hit_test(not fullscreen and restore_title_view)
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:reload-module"] = function()
|
["core:reload-module"] = function()
|
||||||
|
@ -71,9 +72,6 @@ command.add(nil, {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:find-file"] = function()
|
["core:find-file"] = function()
|
||||||
if not core.project_files_number() then
|
|
||||||
return command.perform "core:open-file"
|
|
||||||
end
|
|
||||||
local files = {}
|
local files = {}
|
||||||
for dir, item in core.get_project_files() do
|
for dir, item in core.get_project_files() do
|
||||||
if item.type == "file" then
|
if item.type == "file" then
|
||||||
|
@ -82,10 +80,22 @@ command.add(nil, {
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
core.command_view:enter("Open File From Project", function(text, item)
|
core.command_view:enter("Open File From Project", function(text, item)
|
||||||
text = item and item.text or text
|
|
||||||
core.root_view:open_doc(core.open_doc(common.home_expand(text)))
|
core.root_view:open_doc(core.open_doc(common.home_expand(text)))
|
||||||
end, function(text)
|
end, function(text)
|
||||||
return common.fuzzy_match_with_recents(files, core.visited_files, text)
|
if text == "" then
|
||||||
|
local recent_files = {}
|
||||||
|
for i = 2, #core.visited_files do
|
||||||
|
table.insert(recent_files, core.visited_files[i])
|
||||||
|
end
|
||||||
|
table.insert(recent_files, core.visited_files[1])
|
||||||
|
local other_files = common.fuzzy_match(files, "")
|
||||||
|
for i = 1, #other_files do
|
||||||
|
table.insert(recent_files, other_files[i])
|
||||||
|
end
|
||||||
|
return recent_files
|
||||||
|
else
|
||||||
|
return common.fuzzy_match(files, text)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -94,38 +104,10 @@ command.add(nil, {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:open-file"] = function()
|
["core:open-file"] = function()
|
||||||
local view = core.active_view
|
|
||||||
if view.doc and view.doc.abs_filename then
|
|
||||||
local dirname, filename = view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
|
||||||
if dirname then
|
|
||||||
dirname = core.normalize_to_project_dir(dirname)
|
|
||||||
local text = dirname == core.project_dir and "" or common.home_encode(dirname) .. PATHSEP
|
|
||||||
core.command_view:set_text(text)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
core.command_view:enter("Open File", function(text)
|
core.command_view:enter("Open File", function(text)
|
||||||
local filename = system.absolute_path(common.home_expand(text))
|
core.root_view:open_doc(core.open_doc(common.home_expand(text)))
|
||||||
core.root_view:open_doc(core.open_doc(filename))
|
|
||||||
end, function (text)
|
end, function (text)
|
||||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
return home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||||
end, nil, function(text)
|
|
||||||
local filename = common.home_expand(text)
|
|
||||||
local path_stat, err = system.get_file_info(filename)
|
|
||||||
if err then
|
|
||||||
if err:find("No such file", 1, true) then
|
|
||||||
-- check if the containing directory exists
|
|
||||||
local dirname = common.dirname(filename)
|
|
||||||
local dir_stat = dirname and system.get_file_info(dirname)
|
|
||||||
if not dirname or (dir_stat and dir_stat.type == 'dir') then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
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,
|
end,
|
||||||
|
|
||||||
|
@ -137,6 +119,12 @@ command.add(nil, {
|
||||||
["core:open-user-module"] = function()
|
["core:open-user-module"] = function()
|
||||||
local user_module_doc = core.open_doc(USERDIR .. "/init.lua")
|
local user_module_doc = core.open_doc(USERDIR .. "/init.lua")
|
||||||
if not user_module_doc then return end
|
if not user_module_doc then return end
|
||||||
|
local doc_save = user_module_doc.save
|
||||||
|
user_module_doc.save = function(self)
|
||||||
|
doc_save(self)
|
||||||
|
core.reload_module("core.style")
|
||||||
|
core.load_user_directory()
|
||||||
|
end
|
||||||
core.root_view:open_doc(user_module_doc)
|
core.root_view:open_doc(user_module_doc)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -152,29 +140,22 @@ command.add(nil, {
|
||||||
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)
|
||||||
if dirname then
|
text = common.home_expand(text)
|
||||||
core.command_view:set_text(common.home_encode(dirname))
|
|
||||||
end
|
|
||||||
core.command_view:enter("Change Project Folder", function(text, item)
|
|
||||||
text = system.absolute_path(common.home_expand(item and item.text or text))
|
|
||||||
if text == core.project_dir then return end
|
|
||||||
local path_stat = system.get_file_info(text)
|
local path_stat = system.get_file_info(text)
|
||||||
if not path_stat or path_stat.type ~= 'dir' then
|
if not path_stat or path_stat.type ~= 'dir' then
|
||||||
core.error("Cannot open folder %q", text)
|
core.error("Cannot open folder %q", text)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
core.confirm_close_docs(core.docs, core.open_folder_project, text)
|
if core.confirm_close_all() then
|
||||||
|
core.open_folder_project(text)
|
||||||
|
end
|
||||||
end, suggest_directory)
|
end, 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)
|
||||||
if dirname then
|
text = common.home_expand(text)
|
||||||
core.command_view:set_text(common.home_encode(dirname))
|
|
||||||
end
|
|
||||||
core.command_view:enter("Open Project", function(text, item)
|
|
||||||
text = common.home_expand(item and item.text or text)
|
|
||||||
local path_stat = system.get_file_info(text)
|
local path_stat = system.get_file_info(text)
|
||||||
if not path_stat or path_stat.type ~= 'dir' then
|
if not path_stat or path_stat.type ~= 'dir' then
|
||||||
core.error("Cannot open folder %q", text)
|
core.error("Cannot open folder %q", text)
|
||||||
|
@ -196,23 +177,24 @@ command.add(nil, {
|
||||||
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
|
||||||
|
core.request_project_scan()
|
||||||
end, suggest_directory)
|
end, suggest_directory)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["core:remove-directory"] = function()
|
["core:remove-directory"] = function()
|
||||||
local dir_list = {}
|
local dir_list = {}
|
||||||
local n = #core.project_directories
|
local n = #core.project_directories
|
||||||
for i = n, 2, -1 do
|
for i = n, 1, -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", function(text, item)
|
core.command_view:enter("Remove Directory", function(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("The project has no directory %q", text)
|
||||||
end
|
end
|
||||||
end, function(text)
|
end, function(text)
|
||||||
text = common.home_expand(text)
|
text = common.home_expand(text)
|
||||||
return common.home_encode_list(common.dir_list_suggest(text, dir_list))
|
return home_encode_list(common.dir_list_suggest(text, dir_list))
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
local core = require "core"
|
|
||||||
local command = require "core.command"
|
|
||||||
local common = require "core.common"
|
|
||||||
|
|
||||||
command.add("core.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(common.find_index(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(common.find_index(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
|
|
||||||
})
|
|
|
@ -16,88 +16,60 @@ 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
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function insert_at_start_of_selected_lines(text, skip_empty)
|
||||||
|
local line1, col1, line2, col2, swap = doc():get_selection(true)
|
||||||
|
if line2 > line1 and col2 == 1 then
|
||||||
|
line2 = line2 - 1
|
||||||
|
col2 = #doc().lines[line2]
|
||||||
|
end
|
||||||
|
for line = line1, line2 do
|
||||||
|
local line_text = doc().lines[line]
|
||||||
|
if (not skip_empty or line_text:find("%S")) then
|
||||||
|
doc():insert(line, 1, text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
doc():set_selection(line1, col1 + #text, line2, col2 + #text, swap)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function remove_from_start_of_selected_lines(text, skip_empty)
|
||||||
|
local line1, col1, line2, col2, swap = doc():get_selection(true)
|
||||||
|
if line2 > line1 and col2 == 1 then
|
||||||
|
line2 = line2 - 1
|
||||||
|
col2 = #doc().lines[line2]
|
||||||
|
end
|
||||||
|
for line = line1, line2 do
|
||||||
|
local line_text = doc().lines[line]
|
||||||
|
if line_text:sub(1, #text) == text
|
||||||
|
and (not skip_empty or line_text:find("%S"))
|
||||||
|
then
|
||||||
|
doc():remove(line, 1, line, #text + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
doc():set_selection(line1, col1 - #text, line2, col2 - #text, swap)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
local function append_line_if_last_line(line)
|
local function append_line_if_last_line(line)
|
||||||
if line >= #doc().lines then
|
if line >= #doc().lines then
|
||||||
doc():insert(line, math.huge, "\n")
|
doc():insert(line, math.huge, "\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function save(filename)
|
local function save(filename)
|
||||||
local abs_filename
|
doc():save(filename)
|
||||||
if filename then
|
core.log("Saved \"%s\"", doc().filename)
|
||||||
filename = core.normalize_to_project_dir(filename)
|
|
||||||
abs_filename = core.project_absolute_path(filename)
|
|
||||||
end
|
|
||||||
doc():save(filename, abs_filename)
|
|
||||||
local saved_filename = doc().filename
|
|
||||||
core.log("Saved \"%s\"", saved_filename)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function cut_or_copy(delete)
|
|
||||||
local full_text = ""
|
|
||||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
|
||||||
local text = doc():get_text(line1, col1, line2, col2)
|
|
||||||
if delete then
|
|
||||||
doc():delete_to_cursor(idx, 0)
|
|
||||||
end
|
|
||||||
full_text = full_text == "" and text or (full_text .. "\n" .. text)
|
|
||||||
doc().cursor_clipboard[idx] = text
|
|
||||||
else
|
|
||||||
doc().cursor_clipboard[idx] = ""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
doc().cursor_clipboard["full"] = full_text
|
|
||||||
system.set_clipboard(full_text)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function split_cursor(direction)
|
|
||||||
local new_cursors = {}
|
|
||||||
for _, line1, col1 in doc():get_selections() do
|
|
||||||
if line1 + 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(x, y, snap_type)
|
|
||||||
local line, col = dv():resolve_screen_position(x, y)
|
|
||||||
doc():set_selection(line, col, line, col)
|
|
||||||
if snap_type == "word" or snap_type == "lines" then
|
|
||||||
command.perform("doc:select-" .. snap_type)
|
|
||||||
end
|
|
||||||
dv().mouse_selecting = { line, col, snap_type }
|
|
||||||
core.blink_reset()
|
|
||||||
end
|
|
||||||
|
|
||||||
local selection_commands = {
|
|
||||||
["doc:cut"] = function()
|
|
||||||
cut_or_copy(true)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:copy"] = function()
|
|
||||||
cut_or_copy(false)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:select-none"] = function()
|
|
||||||
local line, col = doc():get_selection()
|
|
||||||
doc():set_selection(line, col)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
local commands = {
|
local commands = {
|
||||||
["doc:undo"] = function()
|
["doc:undo"] = function()
|
||||||
|
@ -108,191 +80,173 @@ local commands = {
|
||||||
doc():redo()
|
doc():redo()
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
["doc:cut"] = function()
|
||||||
|
if doc():has_selection() then
|
||||||
|
local text = doc():get_text(doc():get_selection())
|
||||||
|
system.set_clipboard(text)
|
||||||
|
doc():delete_to(0)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:copy"] = function()
|
||||||
|
if doc():has_selection() then
|
||||||
|
local text = doc():get_text(doc():get_selection())
|
||||||
|
system.set_clipboard(text)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
["doc:paste"] = function()
|
["doc:paste"] = function()
|
||||||
local clipboard = system.get_clipboard()
|
doc():text_input(system.get_clipboard():gsub("\r", ""))
|
||||||
-- If the clipboard has changed since our last look, use that instead
|
|
||||||
if doc().cursor_clipboard["full"] ~= clipboard then
|
|
||||||
doc().cursor_clipboard = {}
|
|
||||||
end
|
|
||||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
|
||||||
local value = doc().cursor_clipboard[idx] or clipboard
|
|
||||||
doc():text_input(value:gsub("\r", ""), idx)
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:newline"] = function()
|
["doc:newline"] = function()
|
||||||
for idx, line, col in doc():get_selections(false, true) do
|
local line, col = doc():get_selection()
|
||||||
local indent = doc().lines[line]:match("^[\t ]*")
|
local indent = doc().lines[line]:match("^[\t ]*")
|
||||||
if col <= #indent then
|
if col <= #indent then
|
||||||
indent = indent:sub(#indent + 2 - col)
|
indent = indent:sub(#indent + 2 - col)
|
||||||
end
|
|
||||||
doc():text_input("\n" .. indent, idx)
|
|
||||||
end
|
end
|
||||||
|
doc():text_input("\n" .. indent)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:newline-below"] = function()
|
["doc:newline-below"] = function()
|
||||||
for idx, line in doc():get_selections(false, true) do
|
local line = doc():get_selection()
|
||||||
local indent = doc().lines[line]:match("^[\t ]*")
|
local indent = doc().lines[line]:match("^[\t ]*")
|
||||||
doc():insert(line, math.huge, "\n" .. indent)
|
doc():insert(line, math.huge, "\n" .. indent)
|
||||||
doc():set_selections(idx, line + 1, math.huge)
|
doc():set_selection(line + 1, math.huge)
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:newline-above"] = function()
|
["doc:newline-above"] = function()
|
||||||
for idx, line in doc():get_selections(false, true) do
|
local line = doc():get_selection()
|
||||||
local indent = doc().lines[line]:match("^[\t ]*")
|
local indent = doc().lines[line]:match("^[\t ]*")
|
||||||
doc():insert(line, 1, indent .. "\n")
|
doc():insert(line, 1, indent .. "\n")
|
||||||
doc():set_selections(idx, line, math.huge)
|
doc():set_selection(line, math.huge)
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:delete"] = function()
|
["doc:delete"] = function()
|
||||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
local line, col = doc():get_selection()
|
||||||
if line1 == line2 and col1 == col2 and doc().lines[line1]:find("^%s*$", col1) then
|
if not doc():has_selection() and doc().lines[line]:find("^%s*$", col) then
|
||||||
doc():remove(line1, col1, line1, math.huge)
|
doc():remove(line, col, line, math.huge)
|
||||||
end
|
|
||||||
doc():delete_to_cursor(idx, translate.next_char)
|
|
||||||
end
|
end
|
||||||
|
doc():delete_to(translate.next_char)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:backspace"] = function()
|
["doc:backspace"] = function()
|
||||||
local _, indent_size = doc():get_indent_info()
|
local line, col = doc():get_selection()
|
||||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
if not doc():has_selection() then
|
||||||
if line1 == line2 and col1 == col2 then
|
local text = doc():get_text(line, 1, line, col)
|
||||||
local text = doc():get_text(line1, 1, line1, col1)
|
if #text >= config.indent_size and text:find("^ *$") then
|
||||||
if #text >= indent_size and text:find("^ *$") then
|
doc():delete_to(0, -config.indent_size)
|
||||||
doc():delete_to_cursor(idx, 0, -indent_size)
|
return
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
doc():delete_to_cursor(idx, translate.previous_char)
|
|
||||||
end
|
end
|
||||||
|
doc():delete_to(translate.previous_char)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:select-all"] = function()
|
["doc:select-all"] = function()
|
||||||
doc():set_selection(1, 1, math.huge, math.huge)
|
doc():set_selection(1, 1, math.huge, math.huge)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
["doc:select-none"] = function()
|
||||||
|
local line, col = doc():get_selection()
|
||||||
|
doc():set_selection(line, col)
|
||||||
|
end,
|
||||||
|
|
||||||
["doc:select-lines"] = function()
|
["doc:select-lines"] = function()
|
||||||
for idx, line1, _, line2 in doc():get_selections(true) do
|
local line1, _, line2, _, swap = doc():get_selection(true)
|
||||||
append_line_if_last_line(line2)
|
append_line_if_last_line(line2)
|
||||||
doc():set_selections(idx, line1, 1, line2 + 1, 1)
|
doc():set_selection(line1, 1, line2 + 1, 1, swap)
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:select-word"] = function()
|
["doc:select-word"] = function()
|
||||||
for idx, line1, col1 in doc():get_selections(true) do
|
local line1, col1 = doc():get_selection(true)
|
||||||
local line1, col1 = translate.start_of_word(doc(), line1, col1)
|
local line1, col1 = translate.start_of_word(doc(), line1, col1)
|
||||||
local line2, col2 = translate.end_of_word(doc(), line1, col1)
|
local line2, col2 = translate.end_of_word(doc(), line1, col1)
|
||||||
doc():set_selections(idx, line2, col2, line1, col1)
|
doc():set_selection(line2, col2, line1, col1)
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:join-lines"] = function()
|
["doc:join-lines"] = function()
|
||||||
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
local line1, _, line2 = doc():get_selection(true)
|
||||||
if line1 == line2 then line2 = line2 + 1 end
|
if line1 == line2 then line2 = line2 + 1 end
|
||||||
local text = doc():get_text(line1, 1, line2, math.huge)
|
local text = doc():get_text(line1, 1, line2, math.huge)
|
||||||
text = text:gsub("(.-)\n[\t ]*", function(x)
|
text = text:gsub("(.-)\n[\t ]*", function(x)
|
||||||
return x:find("^%s*$") and x or x .. " "
|
return x:find("^%s*$") and x or x .. " "
|
||||||
end)
|
end)
|
||||||
doc():insert(line1, 1, text)
|
doc():insert(line1, 1, text)
|
||||||
doc():remove(line1, #text + 1, line2, math.huge)
|
doc():remove(line1, #text + 1, line2, math.huge)
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
if doc():has_selection() then
|
||||||
doc():set_selections(idx, line1, math.huge)
|
doc():set_selection(line1, math.huge)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:indent"] = function()
|
["doc:indent"] = function()
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
local text = get_indent_string()
|
||||||
local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2)
|
if doc():has_selection() then
|
||||||
if l1 then
|
insert_at_start_of_selected_lines(text)
|
||||||
doc():set_selections(idx, l1, c1, l2, c2)
|
else
|
||||||
end
|
doc():text_input(text)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:unindent"] = function()
|
["doc:unindent"] = function()
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
local text = get_indent_string()
|
||||||
local l1, c1, l2, c2 = doc():indent_text(true, line1, col1, line2, col2)
|
remove_from_start_of_selected_lines(text)
|
||||||
if l1 then
|
|
||||||
doc():set_selections(idx, l1, c1, l2, c2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:duplicate-lines"] = function()
|
["doc:duplicate-lines"] = function()
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
local line1, col1, line2, col2, swap = doc():get_selection(true)
|
||||||
append_line_if_last_line(line2)
|
append_line_if_last_line(line2)
|
||||||
local text = doc():get_text(line1, 1, line2 + 1, 1)
|
local text = doc():get_text(line1, 1, line2 + 1, 1)
|
||||||
doc():insert(line2 + 1, 1, text)
|
doc():insert(line2 + 1, 1, text)
|
||||||
local n = line2 - line1 + 1
|
local n = line2 - line1 + 1
|
||||||
doc():set_selections(idx, line1 + n, col1, line2 + n, col2)
|
doc():set_selection(line1 + n, col1, line2 + n, col2, swap)
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:delete-lines"] = function()
|
["doc:delete-lines"] = function()
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
local line1, col1, line2 = doc():get_selection(true)
|
||||||
append_line_if_last_line(line2)
|
append_line_if_last_line(line2)
|
||||||
doc():remove(line1, 1, line2 + 1, 1)
|
doc():remove(line1, 1, line2 + 1, 1)
|
||||||
doc():set_selections(idx, line1, col1)
|
doc():set_selection(line1, col1)
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:move-lines-up"] = function()
|
["doc:move-lines-up"] = function()
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
local line1, col1, line2, col2, swap = doc():get_selection(true)
|
||||||
append_line_if_last_line(line2)
|
append_line_if_last_line(line2)
|
||||||
if line1 > 1 then
|
if line1 > 1 then
|
||||||
local text = doc().lines[line1 - 1]
|
local text = doc().lines[line1 - 1]
|
||||||
doc():insert(line2 + 1, 1, text)
|
doc():insert(line2 + 1, 1, text)
|
||||||
doc():remove(line1 - 1, 1, line1, 1)
|
doc():remove(line1 - 1, 1, line1, 1)
|
||||||
doc():set_selections(idx, line1 - 1, col1, line2 - 1, col2)
|
doc():set_selection(line1 - 1, col1, line2 - 1, col2, swap)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:move-lines-down"] = function()
|
["doc:move-lines-down"] = function()
|
||||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
local line1, col1, line2, col2, swap = doc():get_selection(true)
|
||||||
append_line_if_last_line(line2 + 1)
|
append_line_if_last_line(line2 + 1)
|
||||||
if line2 < #doc().lines then
|
if line2 < #doc().lines then
|
||||||
local text = doc().lines[line2 + 1]
|
local text = doc().lines[line2 + 1]
|
||||||
doc():remove(line2 + 1, 1, line2 + 2, 1)
|
doc():remove(line2 + 1, 1, line2 + 2, 1)
|
||||||
doc():insert(line1, 1, text)
|
doc():insert(line1, 1, text)
|
||||||
doc():set_selections(idx, line1 + 1, col1, line2 + 1, col2)
|
doc():set_selection(line1 + 1, col1, line2 + 1, col2, swap)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:toggle-line-comments"] = function()
|
["doc:toggle-line-comments"] = function()
|
||||||
local comment = doc().syntax.comment
|
local comment = doc().syntax.comment
|
||||||
if not comment then return end
|
if not comment then return end
|
||||||
local indentation = doc():get_indent_string()
|
|
||||||
local comment_text = comment .. " "
|
local comment_text = comment .. " "
|
||||||
for idx, line1, _, line2 in doc_multiline_selections(true) do
|
local line1, _, line2 = doc():get_selection(true)
|
||||||
local uncomment = true
|
local uncomment = true
|
||||||
local start_offset = math.huge
|
for line = line1, line2 do
|
||||||
for line = line1, line2 do
|
local text = doc().lines[line]
|
||||||
local text = doc().lines[line]
|
if text:find("%S") and text:find(comment_text, 1, true) ~= 1 then
|
||||||
local s = text:find("%S")
|
uncomment = false
|
||||||
local cs, ce = text:find(comment_text, s, true)
|
|
||||||
if s and cs ~= s then
|
|
||||||
uncomment = false
|
|
||||||
start_offset = math.min(start_offset, s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for line = line1, line2 do
|
|
||||||
local text = doc().lines[line]
|
|
||||||
local s = text:find("%S")
|
|
||||||
if uncomment then
|
|
||||||
local cs, ce = text:find(comment_text, s, true)
|
|
||||||
if ce then
|
|
||||||
doc():remove(line, cs, line, ce + 1)
|
|
||||||
end
|
|
||||||
elseif s then
|
|
||||||
doc():insert(line, start_offset, comment_text)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if uncomment then
|
||||||
|
remove_from_start_of_selected_lines(comment_text, true)
|
||||||
|
else
|
||||||
|
insert_at_start_of_selected_lines(comment_text, true)
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:upper-case"] = function()
|
["doc:upper-case"] = function()
|
||||||
|
@ -339,18 +293,12 @@ local commands = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:save-as"] = function()
|
["doc:save-as"] = function()
|
||||||
local last_doc = core.last_active_view and core.last_active_view.doc
|
|
||||||
if doc().filename then
|
if doc().filename then
|
||||||
core.command_view:set_text(doc().filename)
|
core.command_view:set_text(doc().filename)
|
||||||
elseif last_doc and last_doc.filename then
|
|
||||||
local dirname, filename = core.last_active_view.doc.abs_filename:match("(.*)[/\\](.+)$")
|
|
||||||
core.command_view:set_text(core.normalize_to_project_dir(dirname) .. PATHSEP)
|
|
||||||
end
|
end
|
||||||
core.command_view:enter("Save As", function(filename)
|
core.command_view:enter("Save As", function(filename)
|
||||||
save(common.home_expand(filename))
|
save(filename)
|
||||||
end, function (text)
|
end, common.path_suggest)
|
||||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
|
||||||
end)
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["doc:save"] = function()
|
["doc:save"] = function()
|
||||||
|
@ -361,7 +309,7 @@ local commands = {
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["file:rename"] = function()
|
["doc:rename"] = function()
|
||||||
local old_filename = doc().filename
|
local old_filename = doc().filename
|
||||||
if not old_filename then
|
if not old_filename then
|
||||||
core.error("Cannot rename unsaved doc")
|
core.error("Cannot rename unsaved doc")
|
||||||
|
@ -369,65 +317,13 @@ local commands = {
|
||||||
end
|
end
|
||||||
core.command_view:set_text(old_filename)
|
core.command_view:set_text(old_filename)
|
||||||
core.command_view:enter("Rename", function(filename)
|
core.command_view:enter("Rename", function(filename)
|
||||||
save(common.home_expand(filename))
|
doc():save(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
|
||||||
end, function (text)
|
end, common.path_suggest)
|
||||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
|
||||||
end)
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
|
||||||
["file:delete"] = function()
|
|
||||||
local filename = doc().abs_filename
|
|
||||||
if not filename then
|
|
||||||
core.error("Cannot remove unsaved doc")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
for i,docview in ipairs(core.get_views_referencing_doc(doc())) do
|
|
||||||
local node = core.root_view.root_node:get_node_for_view(docview)
|
|
||||||
node:close_view(core.root_view, docview)
|
|
||||||
end
|
|
||||||
os.remove(filename)
|
|
||||||
core.log("Removed \"%s\"", filename)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:select-to-cursor"] = function(x, y, clicks)
|
|
||||||
local line1, col1 = select(3, doc():get_selection())
|
|
||||||
local line2, col2 = dv():resolve_screen_position(x, y)
|
|
||||||
dv().mouse_selecting = { line1, col1, nil }
|
|
||||||
doc():set_selection(line2, col2, line1, col1)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:set-cursor"] = function(x, y)
|
|
||||||
set_cursor(x, y, "set")
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:set-cursor-word"] = function(x, y)
|
|
||||||
set_cursor(x, y, "word")
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:set-cursor-line"] = function(x, y, clicks)
|
|
||||||
set_cursor(x, y, "lines")
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:split-cursor"] = function(x, y, clicks)
|
|
||||||
local line, col = dv():resolve_screen_position(x, y)
|
|
||||||
doc():add_selection(line, col, line, col)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:create-cursor-previous-line"] = function()
|
|
||||||
split_cursor(-1)
|
|
||||||
doc():merge_cursors()
|
|
||||||
end,
|
|
||||||
|
|
||||||
["doc:create-cursor-next-line"] = function()
|
|
||||||
split_cursor(1)
|
|
||||||
doc():merge_cursors()
|
|
||||||
end
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -443,7 +339,6 @@ local translations = {
|
||||||
["start-of-line"] = translate.start_of_line,
|
["start-of-line"] = translate.start_of_line,
|
||||||
["end-of-line"] = translate.end_of_line,
|
["end-of-line"] = translate.end_of_line,
|
||||||
["start-of-word"] = translate.start_of_word,
|
["start-of-word"] = translate.start_of_word,
|
||||||
["start-of-indentation"] = translate.start_of_indentation,
|
|
||||||
["end-of-word"] = translate.end_of_word,
|
["end-of-word"] = translate.end_of_word,
|
||||||
["previous-line"] = DocView.translate.previous_line,
|
["previous-line"] = DocView.translate.previous_line,
|
||||||
["next-line"] = DocView.translate.next_line,
|
["next-line"] = DocView.translate.next_line,
|
||||||
|
@ -458,24 +353,21 @@ for name, fn in pairs(translations) do
|
||||||
end
|
end
|
||||||
|
|
||||||
commands["doc:move-to-previous-char"] = function()
|
commands["doc:move-to-previous-char"] = function()
|
||||||
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
if doc():has_selection() then
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
local line, col = doc():get_selection(true)
|
||||||
doc():set_selections(idx, line1, col1)
|
doc():set_selection(line, col)
|
||||||
end
|
else
|
||||||
|
doc():move_to(translate.previous_char)
|
||||||
end
|
end
|
||||||
doc():move_to(translate.previous_char)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
commands["doc:move-to-next-char"] = function()
|
commands["doc:move-to-next-char"] = function()
|
||||||
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
|
if doc():has_selection() then
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
local _, _, line, col = doc():get_selection(true)
|
||||||
doc():set_selections(idx, line2, col2)
|
doc():set_selection(line, col)
|
||||||
end
|
else
|
||||||
|
doc():move_to(translate.next_char)
|
||||||
end
|
end
|
||||||
doc():move_to(translate.next_char)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
command.add("core.docview", commands)
|
command.add("core.docview", commands)
|
||||||
command.add(function()
|
|
||||||
return core.active_view:is(DocView) and core.active_view.doc:has_any_selection()
|
|
||||||
end ,selection_commands)
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
local command = require "core.command"
|
|
||||||
local config = require "core.config"
|
|
||||||
|
|
||||||
command.add(nil, {
|
|
||||||
["draw-whitespace:toggle"] = function()
|
|
||||||
config.draw_whitespace = not config.draw_whitespace
|
|
||||||
end,
|
|
||||||
|
|
||||||
["draw-whitespace:disable"] = function()
|
|
||||||
config.draw_whitespace = false
|
|
||||||
end,
|
|
||||||
|
|
||||||
["draw-whitespace:enable"] = function()
|
|
||||||
config.draw_whitespace = true
|
|
||||||
end,
|
|
||||||
})
|
|
|
@ -1,13 +1,12 @@
|
||||||
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", function(text)
|
core.command_view:enter("New directory name", function(text)
|
||||||
local success, err, path = common.mkdirp(text)
|
local success, err = system.mkdir(text)
|
||||||
if not success then
|
if not success then
|
||||||
core.error("cannot create directory %q: %s", path, err)
|
core.error("cannot create directory %q: %s", text, err)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
|
@ -2,85 +2,67 @@ 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 return end
|
|
||||||
end
|
end
|
||||||
t[n + 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(unpack(last_sel))
|
local text = dv.doc:get_text(table.unpack(sel))
|
||||||
found_expression = false
|
local found = false
|
||||||
|
|
||||||
core.command_view:set_text(text, true)
|
core.command_view:set_text(text, true)
|
||||||
core.status_view:show_tooltip(get_find_tooltip())
|
|
||||||
|
|
||||||
core.command_view:set_hidden_suggestions()
|
core.command_view:enter(label, function(text)
|
||||||
core.command_view:enter(label, function(text, item)
|
if found then
|
||||||
insert_unique(core.previous_find, text)
|
|
||||||
core.status_view:remove_tooltip()
|
|
||||||
if found_expression then
|
|
||||||
last_fn, last_text = search_fn, text
|
last_fn, last_text = search_fn, text
|
||||||
|
previous_finds = {}
|
||||||
|
push_previous_find(dv.doc, sel)
|
||||||
else
|
else
|
||||||
core.error("Couldn't find %q", text)
|
core.error("Couldn't find %q", text)
|
||||||
last_view.doc:set_selection(table.unpack(last_sel))
|
dv.doc:set_selection(table.unpack(sel))
|
||||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
dv:scroll_to_make_visible(sel[1], sel[2])
|
||||||
end
|
end
|
||||||
|
|
||||||
end, function(text)
|
end, function(text)
|
||||||
update_preview(last_sel, search_fn, text)
|
local ok, line1, col1, line2, col2 = pcall(search_fn, dv.doc, sel[1], sel[2], text)
|
||||||
last_fn, last_text = search_fn, text
|
if ok and line1 and text ~= "" then
|
||||||
return core.previous_find
|
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)
|
end, function(explicit)
|
||||||
core.status_view:remove_tooltip()
|
|
||||||
if explicit then
|
if explicit then
|
||||||
last_view.doc:set_selection(table.unpack(last_sel))
|
dv.doc:set_selection(table.unpack(sel))
|
||||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
dv:scroll_to_make_visible(sel[1], sel[2])
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -89,113 +71,82 @@ end
|
||||||
local function replace(kind, default, fn)
|
local function replace(kind, default, fn)
|
||||||
core.command_view:set_text(default, true)
|
core.command_view:set_text(default, true)
|
||||||
|
|
||||||
core.status_view:show_tooltip(get_find_tooltip())
|
|
||||||
core.command_view:set_hidden_suggestions()
|
|
||||||
core.command_view:enter("Find To Replace " .. kind, function(old)
|
core.command_view:enter("Find To Replace " .. kind, function(old)
|
||||||
insert_unique(core.previous_find, old)
|
|
||||||
core.command_view:set_text(old, true)
|
core.command_view:set_text(old, true)
|
||||||
|
|
||||||
local s = string.format("Replace %s %q With", kind, old)
|
local s = string.format("Replace %s %q With", kind, old)
|
||||||
core.command_view:set_hidden_suggestions()
|
|
||||||
core.command_view:enter(s, function(new)
|
core.command_view:enter(s, function(new)
|
||||||
core.status_view:remove_tooltip()
|
|
||||||
insert_unique(core.previous_replace, new)
|
|
||||||
local n = doc():replace(function(text)
|
local n = doc():replace(function(text)
|
||||||
return fn(text, old, new)
|
return fn(text, old, new)
|
||||||
end)
|
end)
|
||||||
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
|
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
|
||||||
end, function() return core.previous_replace end, function()
|
|
||||||
core.status_view:remove_tooltip()
|
|
||||||
end)
|
end)
|
||||||
end, function() return core.previous_find end, function()
|
|
||||||
core.status_view:remove_tooltip()
|
|
||||||
end)
|
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,
|
||||||
|
|
||||||
|
@ -217,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
|
|
||||||
})
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ 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 = {
|
||||||
|
@ -12,25 +11,6 @@ local t = {
|
||||||
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()
|
|
||||||
local node = core.root_view:get_active_node()
|
|
||||||
if node and (not node:is_empty() or not node.is_primary_node) then
|
|
||||||
node:close_active_view(core.root_view.root_node)
|
|
||||||
else
|
|
||||||
core.quit()
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
["root:close-all"] = function()
|
|
||||||
core.confirm_close_docs(core.docs, core.root_view.close_all_docviews, core.root_view)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["root:close-all-others"] = function()
|
|
||||||
local active_doc, docs = core.active_view and core.active_view.doc, {}
|
|
||||||
for i, v in ipairs(core.docs) do if v ~= active_doc then table.insert(docs, v) end end
|
|
||||||
core.confirm_close_docs(docs, core.root_view.close_all_docviews, core.root_view, true)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["root:switch-to-previous-tab"] = function()
|
["root:switch-to-previous-tab"] = function()
|
||||||
local node = core.root_view:get_active_node()
|
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)
|
||||||
|
@ -64,7 +44,7 @@ local t = {
|
||||||
table.insert(node.views, idx + 1, core.active_view)
|
table.insert(node.views, idx + 1, core.active_view)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["root:shrink"] = function()
|
["root:shrink"] = function()
|
||||||
local node = core.root_view:get_active_node()
|
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)
|
||||||
|
@ -77,7 +57,7 @@ local t = {
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,8 +93,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
|
||||||
|
@ -122,17 +101,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
|
|
||||||
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
|
|
||||||
})
|
|
||||||
|
|
|
@ -15,8 +15,6 @@ end
|
||||||
|
|
||||||
local CommandView = DocView:extend()
|
local CommandView = DocView:extend()
|
||||||
|
|
||||||
CommandView.context = "application"
|
|
||||||
|
|
||||||
local max_suggestions = 10
|
local max_suggestions = 10
|
||||||
|
|
||||||
local noop = function() end
|
local noop = function() end
|
||||||
|
@ -25,7 +23,6 @@ local default_state = {
|
||||||
submit = noop,
|
submit = noop,
|
||||||
suggest = noop,
|
suggest = noop,
|
||||||
cancel = noop,
|
cancel = noop,
|
||||||
validate = function() return true end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,7 +31,6 @@ function CommandView:new()
|
||||||
self.suggestion_idx = 1
|
self.suggestion_idx = 1
|
||||||
self.suggestions = {}
|
self.suggestions = {}
|
||||||
self.suggestions_height = 0
|
self.suggestions_height = 0
|
||||||
self.show_suggestions = true
|
|
||||||
self.last_change_id = 0
|
self.last_change_id = 0
|
||||||
self.gutter_width = 0
|
self.gutter_width = 0
|
||||||
self.gutter_text_brightness = 0
|
self.gutter_text_brightness = 0
|
||||||
|
@ -46,11 +42,6 @@ function CommandView:new()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function CommandView:set_hidden_suggestions()
|
|
||||||
self.show_suggestions = false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function CommandView:get_name()
|
function CommandView:get_name()
|
||||||
return View.get_name(self)
|
return View.get_name(self)
|
||||||
end
|
end
|
||||||
|
@ -89,29 +80,10 @@ end
|
||||||
|
|
||||||
|
|
||||||
function CommandView:move_suggestion_idx(dir)
|
function CommandView:move_suggestion_idx(dir)
|
||||||
if self.show_suggestions then
|
local n = self.suggestion_idx + dir
|
||||||
local n = self.suggestion_idx + dir
|
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
self:complete()
|
||||||
self:complete()
|
self.last_change_id = self.doc:get_change_id()
|
||||||
self.last_change_id = self.doc:get_change_id()
|
|
||||||
else
|
|
||||||
local current_suggestion = #self.suggestions > 0 and self.suggestions[self.suggestion_idx].text
|
|
||||||
local text = self:get_text()
|
|
||||||
if text == current_suggestion then
|
|
||||||
local n = self.suggestion_idx + dir
|
|
||||||
if n == 0 and self.save_suggestion then
|
|
||||||
self:set_text(self.save_suggestion)
|
|
||||||
else
|
|
||||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
|
||||||
self:complete()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.save_suggestion = text
|
|
||||||
self:complete()
|
|
||||||
end
|
|
||||||
self.last_change_id = self.doc:get_change_id()
|
|
||||||
self.state.suggest(self:get_text())
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -125,15 +97,13 @@ end
|
||||||
function CommandView:submit()
|
function CommandView:submit()
|
||||||
local suggestion = self.suggestions[self.suggestion_idx]
|
local suggestion = self.suggestions[self.suggestion_idx]
|
||||||
local text = self:get_text()
|
local text = self:get_text()
|
||||||
if self.state.validate(text) then
|
local submit = self.state.submit
|
||||||
local submit = self.state.submit
|
self:exit(true)
|
||||||
self:exit(true)
|
submit(text, suggestion)
|
||||||
submit(text, suggestion)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function CommandView:enter(text, submit, suggest, cancel, validate)
|
function CommandView:enter(text, submit, suggest, cancel)
|
||||||
if self.state ~= default_state then
|
if self.state ~= default_state then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -141,7 +111,6 @@ function CommandView:enter(text, submit, suggest, cancel, validate)
|
||||||
submit = submit or noop,
|
submit = submit or noop,
|
||||||
suggest = suggest or noop,
|
suggest = suggest or noop,
|
||||||
cancel = cancel or noop,
|
cancel = cancel or noop,
|
||||||
validate = validate or function() return true end
|
|
||||||
}
|
}
|
||||||
core.set_active_view(self)
|
core.set_active_view(self)
|
||||||
self:update_suggestions()
|
self:update_suggestions()
|
||||||
|
@ -159,8 +128,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.show_suggestions = true
|
|
||||||
self.save_suggestion = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -178,6 +145,9 @@ function CommandView:update_suggestions()
|
||||||
local t = self.state.suggest(self:get_text()) or {}
|
local t = self.state.suggest(self:get_text()) or {}
|
||||||
local res = {}
|
local res = {}
|
||||||
for i, item in ipairs(t) do
|
for i, item in ipairs(t) do
|
||||||
|
if i == max_suggestions then
|
||||||
|
break
|
||||||
|
end
|
||||||
if type(item) == "string" then
|
if type(item) == "string" then
|
||||||
item = { text = item }
|
item = { text = item }
|
||||||
end
|
end
|
||||||
|
@ -214,11 +184,11 @@ function CommandView:update()
|
||||||
|
|
||||||
-- 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.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0
|
local dest = #self.suggestions * lh
|
||||||
self:move_towards("suggestions_height", dest)
|
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 = self.suggestion_idx * self:get_suggestion_line_height()
|
||||||
self:move_towards("selection_offset", dest)
|
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
|
||||||
|
@ -262,20 +232,16 @@ local function draw_suggestions_box(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- draw suggestion text
|
-- draw suggestion text
|
||||||
local suggestion_offset = math.max(self.suggestion_idx - max_suggestions, 0)
|
|
||||||
core.push_clip_rect(rx, ry, rw, rh)
|
core.push_clip_rect(rx, ry, rw, rh)
|
||||||
local i = 1 + suggestion_offset
|
for i, item in ipairs(self.suggestions) do
|
||||||
while i <= #self.suggestions do
|
|
||||||
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 - suggestion_offset) * lh - dh
|
local y = self.position.y - i * 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
|
||||||
|
@ -283,9 +249,7 @@ end
|
||||||
|
|
||||||
function CommandView:draw()
|
function CommandView:draw()
|
||||||
CommandView.super.draw(self)
|
CommandView.super.draw(self)
|
||||||
if self.show_suggestions then
|
core.root_view:defer_draw(draw_suggestions_box, self)
|
||||||
core.root_view:defer_draw(draw_suggestions_box, self)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
local common = {}
|
local common = {}
|
||||||
|
|
||||||
|
|
||||||
function common.is_utf8_cont(s, offset)
|
function common.is_utf8_cont(char)
|
||||||
local byte = s:byte(offset or 1)
|
local byte = char:byte()
|
||||||
return byte >= 0x80 and byte < 0xc0
|
return byte >= 0x80 and byte < 0xc0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -22,13 +22,6 @@ function common.round(n)
|
||||||
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
|
||||||
|
@ -41,59 +34,34 @@ function common.lerp(a, b, t)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.distance(x1, y1, x2, y2)
|
|
||||||
return math.sqrt(math.pow(x2-x1, 2)+math.pow(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
|
||||||
|
|
||||||
local function fuzzy_match_items(items, needle, files)
|
local function fuzzy_match_items(items, needle)
|
||||||
local res = {}
|
local res = {}
|
||||||
for _, item in ipairs(items) do
|
for _, item in ipairs(items) do
|
||||||
local score = system.fuzzy_match(tostring(item), needle, files)
|
local score = system.fuzzy_match(tostring(item), needle)
|
||||||
if score then
|
if score then
|
||||||
table.insert(res, { text = item, score = score })
|
table.insert(res, { text = item, score = score })
|
||||||
end
|
end
|
||||||
|
@ -106,29 +74,11 @@ local function fuzzy_match_items(items, needle, files)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.fuzzy_match(haystack, needle, files)
|
function common.fuzzy_match(haystack, needle)
|
||||||
if type(haystack) == "table" then
|
if type(haystack) == "table" then
|
||||||
return fuzzy_match_items(haystack, needle, files)
|
return fuzzy_match_items(haystack, needle)
|
||||||
end
|
|
||||||
return system.fuzzy_match(haystack, needle, files)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.fuzzy_match_with_recents(haystack, recents, needle)
|
|
||||||
if needle == "" then
|
|
||||||
local recents_ext = {}
|
|
||||||
for i = 2, #recents do
|
|
||||||
table.insert(recents_ext, recents[i])
|
|
||||||
end
|
|
||||||
table.insert(recents_ext, recents[1])
|
|
||||||
local others = common.fuzzy_match(haystack, "", true)
|
|
||||||
for i = 1, #others do
|
|
||||||
table.insert(recents_ext, others[i])
|
|
||||||
end
|
|
||||||
return recents_ext
|
|
||||||
else
|
|
||||||
return fuzzy_match_items(haystack, needle, true)
|
|
||||||
end
|
end
|
||||||
|
return system.fuzzy_match(haystack, needle)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -214,227 +164,20 @@ function common.bench(name, fn, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.serialize(val)
|
|
||||||
if type(val) == "string" then
|
|
||||||
return string.format("%q", val)
|
|
||||||
elseif type(val) == "table" then
|
|
||||||
local t = {}
|
|
||||||
for k, v in pairs(val) do
|
|
||||||
table.insert(t, "[" .. common.serialize(k) .. "]=" .. common.serialize(v))
|
|
||||||
end
|
|
||||||
return "{" .. table.concat(t, ",") .. "}"
|
|
||||||
end
|
|
||||||
return tostring(val)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.basename(path)
|
|
||||||
-- a path should never end by / or \ except if it is '/' (unix root) or
|
|
||||||
-- 'X:\' (windows drive)
|
|
||||||
return path:match("[^\\/]+$") or path
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- can return nil if there is no directory part in the path
|
|
||||||
function common.dirname(path)
|
|
||||||
return path:match("(.+)[\\/][^\\/]+$")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.home_encode(text)
|
function common.home_encode(text)
|
||||||
if HOME and string.find(text, HOME, 1, true) == 1 then
|
if HOME then
|
||||||
local dir_pos = #HOME + 1
|
local n = #HOME
|
||||||
-- ensure we don't replace if the text is just "$HOME" or "$HOME/" so
|
if text:sub(1, n) == HOME and text:sub(n + 1, n + 1):match("[/\\\\]") then
|
||||||
-- it must have a "/" following the $HOME and some characters following.
|
return "~" .. text:sub(n + 1)
|
||||||
if string.find(text, PATHSEP, dir_pos, true) == dir_pos and #text > dir_pos then
|
|
||||||
return "~" .. text:sub(dir_pos)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return text
|
return text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function common.home_encode_list(paths)
|
|
||||||
local t = {}
|
|
||||||
for i = 1, #paths do
|
|
||||||
t[i] = common.home_encode(paths[i])
|
|
||||||
end
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.home_expand(text)
|
function common.home_expand(text)
|
||||||
return HOME and text:gsub("^~", HOME) or text
|
return HOME and text:gsub("^~", HOME) or text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function split_on_slash(s, sep_pattern)
|
|
||||||
local t = {}
|
|
||||||
if s:match("^[/\\]") then
|
|
||||||
t[#t + 1] = ""
|
|
||||||
end
|
|
||||||
for fragment in string.gmatch(s, "([^/\\]+)") do
|
|
||||||
t[#t + 1] = fragment
|
|
||||||
end
|
|
||||||
return t
|
|
||||||
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 to we normalize to upper case.
|
|
||||||
function common.normalize_volume(filename)
|
|
||||||
if not filename then return end
|
|
||||||
if PATHSEP == '\\' then
|
|
||||||
local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
|
|
||||||
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.path_belongs_to(filename, path)
|
|
||||||
return string.find(filename, path .. PATHSEP, 1, true) == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function common.relative_path(ref_dir, dir)
|
|
||||||
local drive_pattern = "^(%a):\\"
|
|
||||||
local drive, ref_drive = dir:match(drive_pattern), ref_dir:match(drive_pattern)
|
|
||||||
if drive and ref_drive and drive ~= ref_drive then
|
|
||||||
-- Windows, different drives, system.absolute_path fails for C:\..\D:\
|
|
||||||
return dir
|
|
||||||
end
|
|
||||||
local ref_ls = split_on_slash(ref_dir)
|
|
||||||
local dir_ls = split_on_slash(dir)
|
|
||||||
local i = 1
|
|
||||||
while i <= #ref_ls do
|
|
||||||
if dir_ls[i] ~= ref_ls[i] then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
local ups = ""
|
|
||||||
for k = i, #ref_ls do
|
|
||||||
ups = ups .. ".." .. PATHSEP
|
|
||||||
end
|
|
||||||
local rel_path = ups .. table.concat(dir_ls, PATHSEP, i)
|
|
||||||
return rel_path ~= "" and rel_path or "."
|
|
||||||
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,18 +1,16 @@
|
||||||
local config = {}
|
local config = {}
|
||||||
|
|
||||||
|
config.project_scan_rate = 5
|
||||||
config.fps = 60
|
config.fps = 60
|
||||||
config.max_log_items = 80
|
config.max_log_items = 80
|
||||||
config.message_timeout = 5
|
config.message_timeout = 3
|
||||||
config.mouse_wheel_scroll = 50 * SCALE
|
config.mouse_wheel_scroll = 50 * SCALE
|
||||||
config.scroll_past_end = true
|
|
||||||
config.file_size_limit = 10
|
config.file_size_limit = 10
|
||||||
config.ignore_files = "^%."
|
config.ignore_files = "^%."
|
||||||
config.symbol_pattern = "[%a_][%w_]*"
|
config.symbol_pattern = "[%a_][%w_]*"
|
||||||
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||||
config.undo_merge_timeout = 0.3
|
config.undo_merge_timeout = 0.3
|
||||||
config.max_undos = 10000
|
config.max_undos = 10000
|
||||||
config.max_tabs = 8
|
|
||||||
config.always_show_tabs = true
|
|
||||||
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
|
||||||
|
@ -20,21 +18,5 @@ config.tab_type = "soft"
|
||||||
config.line_limit = 80
|
config.line_limit = 80
|
||||||
config.max_symbols = 4000
|
config.max_symbols = 4000
|
||||||
config.max_project_files = 2000
|
config.max_project_files = 2000
|
||||||
config.transitions = true
|
|
||||||
config.animation_rate = 1.0
|
|
||||||
config.blink_period = 0.8
|
|
||||||
config.disable_blink = false
|
|
||||||
config.draw_whitespace = false
|
|
||||||
config.borderless = false
|
|
||||||
config.tab_close_button = true
|
|
||||||
config.max_clicks = 3
|
|
||||||
|
|
||||||
-- Disable plugin loading setting to false the config entry
|
|
||||||
-- of the same name.
|
|
||||||
config.plugins = {}
|
|
||||||
|
|
||||||
config.plugins.trimwhitespace = false
|
|
||||||
config.plugins.lineguide = false
|
|
||||||
config.plugins.drawwhitespace = false
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
|
@ -1,222 +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 border_width = 1
|
|
||||||
local divider_width = 1
|
|
||||||
local DIVIDER = {}
|
|
||||||
|
|
||||||
local ContextMenu = Object:extend()
|
|
||||||
|
|
||||||
ContextMenu.DIVIDER = DIVIDER
|
|
||||||
|
|
||||||
function ContextMenu:new()
|
|
||||||
self.itemset = {}
|
|
||||||
self.show_context_menu = false
|
|
||||||
self.selected = -1
|
|
||||||
self.height = 0
|
|
||||||
self.position = { x = 0, y = 0 }
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_item_size(item)
|
|
||||||
local lw, lh
|
|
||||||
if item == DIVIDER then
|
|
||||||
lw = 0
|
|
||||||
lh = divider_width
|
|
||||||
else
|
|
||||||
lw = style.font:get_width(item.text)
|
|
||||||
if item.info then
|
|
||||||
lw = lw + style.padding.x + style.font:get_width(item.info)
|
|
||||||
end
|
|
||||||
lh = style.font:get_height() + style.padding.y
|
|
||||||
end
|
|
||||||
return lw, lh
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:register(predicate, items)
|
|
||||||
if type(predicate) == "string" then
|
|
||||||
predicate = require(predicate)
|
|
||||||
end
|
|
||||||
if type(predicate) == "table" then
|
|
||||||
local class = predicate
|
|
||||||
predicate = function() return core.active_view:is(class) end
|
|
||||||
end
|
|
||||||
|
|
||||||
local width, height = 0, 0 --precalculate the size of context menu
|
|
||||||
for i, item in ipairs(items) do
|
|
||||||
if item ~= DIVIDER then
|
|
||||||
item.info = keymap.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
|
|
||||||
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
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:hide()
|
|
||||||
self.show_context_menu = false
|
|
||||||
self.items = nil
|
|
||||||
self.selected = -1
|
|
||||||
self.height = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:each_item()
|
|
||||||
local x, y, w = self.position.x, self.position.y, self.items.width
|
|
||||||
local oy = y
|
|
||||||
return coroutine.wrap(function()
|
|
||||||
for i, item in ipairs(self.items) do
|
|
||||||
local _, lh = get_item_size(item)
|
|
||||||
if y - oy > self.height then break end
|
|
||||||
coroutine.yield(i, item, x, y, w, lh)
|
|
||||||
y = y + lh
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:on_mouse_moved(px, py)
|
|
||||||
if not self.show_context_menu then return end
|
|
||||||
|
|
||||||
self.selected = -1
|
|
||||||
for i, item, x, y, w, h in self:each_item() do
|
|
||||||
if px > x and px <= x + w and py > y and py <= y + h then
|
|
||||||
self.selected = i
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if self.selected >= 0 then
|
|
||||||
core.request_cursor("arrow")
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:on_selected(item)
|
|
||||||
if type(item.command) == "string" then
|
|
||||||
command.perform(item.command)
|
|
||||||
else
|
|
||||||
item.command()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:on_mouse_pressed(button, x, y, clicks)
|
|
||||||
local selected = (self.items or {})[self.selected]
|
|
||||||
local caught = false
|
|
||||||
|
|
||||||
self:hide()
|
|
||||||
if button == "left" then
|
|
||||||
if selected then
|
|
||||||
self:on_selected(selected)
|
|
||||||
caught = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if button == "right" then
|
|
||||||
caught = self:show(x, y)
|
|
||||||
end
|
|
||||||
return caught
|
|
||||||
end
|
|
||||||
|
|
||||||
-- copied from core.docview
|
|
||||||
function ContextMenu:move_towards(t, k, dest, rate)
|
|
||||||
if type(t) ~= "table" then
|
|
||||||
return self:move_towards(self, t, k, dest, rate)
|
|
||||||
end
|
|
||||||
local val = t[k]
|
|
||||||
if not config.transitions or math.abs(val - dest) < 0.5 then
|
|
||||||
t[k] = dest
|
|
||||||
else
|
|
||||||
rate = rate or 0.5
|
|
||||||
if config.fps ~= 60 or config.animation_rate ~= 1 then
|
|
||||||
local dt = 60 / config.fps
|
|
||||||
rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt)
|
|
||||||
end
|
|
||||||
t[k] = common.lerp(val, dest, rate)
|
|
||||||
end
|
|
||||||
if val ~= dest then
|
|
||||||
core.redraw = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:update()
|
|
||||||
if self.show_context_menu then
|
|
||||||
self:move_towards("height", self.items.height)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:draw()
|
|
||||||
if not self.show_context_menu then return end
|
|
||||||
core.root_view:defer_draw(self.draw_context_menu, self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function ContextMenu:draw_context_menu()
|
|
||||||
if not self.items then return end
|
|
||||||
local bx, by, bw, bh = self.position.x, self.position.y, self.items.width, self.height
|
|
||||||
|
|
||||||
renderer.draw_rect(
|
|
||||||
bx - border_width,
|
|
||||||
by - border_width,
|
|
||||||
bw + (border_width * 2),
|
|
||||||
bh + (border_width * 2),
|
|
||||||
style.divider
|
|
||||||
)
|
|
||||||
renderer.draw_rect(bx, by, bw, bh, style.background3)
|
|
||||||
|
|
||||||
for i, item, x, y, w, h in self:each_item() do
|
|
||||||
if item == DIVIDER then
|
|
||||||
renderer.draw_rect(x, y, w, h, style.caret)
|
|
||||||
else
|
|
||||||
if i == self.selected then
|
|
||||||
renderer.draw_rect(x, y, w, h, style.selection)
|
|
||||||
end
|
|
||||||
|
|
||||||
common.draw_text(style.font, style.text, item.text, "left", x + style.padding.x, y, w, h)
|
|
||||||
if item.info then
|
|
||||||
common.draw_text(style.font, style.dim, item.info, "right", x, y, w - style.padding.x, h)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return ContextMenu
|
|
|
@ -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"
|
||||||
|
@ -25,7 +24,7 @@ function Highlighter:new(doc)
|
||||||
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
|
||||||
self.lines[i] = self:tokenize_line(i, state)
|
self.lines[i] = self:tokenize_line(i, state)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -41,36 +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:tokenize_line(idx, state)
|
function Highlighter:tokenize_line(idx, state)
|
||||||
local res = {}
|
local res = {}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
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"
|
||||||
|
@ -18,22 +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.cursor_clipboard = {}
|
|
||||||
self.undo_stack = { idx = 1 }
|
self.undo_stack = { idx = 1 }
|
||||||
self.redo_stack = { idx = 1 }
|
self.redo_stack = { idx = 1 }
|
||||||
self.clean_change_id = 1
|
self.clean_change_id = 1
|
||||||
|
@ -47,30 +60,22 @@ 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)
|
|
||||||
self.filename = filename
|
|
||||||
self.abs_filename = abs_filename
|
|
||||||
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.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,20 +85,15 @@ function Doc:load(filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:save(filename, abs_filename)
|
function Doc:save(filename)
|
||||||
if not filename then
|
filename = filename or assert(self.filename, "no filename set to default to")
|
||||||
assert(self.filename, "no filename set to default to")
|
|
||||||
filename = self.filename
|
|
||||||
abs_filename = self.abs_filename
|
|
||||||
end
|
|
||||||
local fp = assert( io.open(filename, "wb") )
|
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)
|
self.filename = filename or self.filename
|
||||||
self.new_file = false
|
|
||||||
self:reset_syntax()
|
self:reset_syntax()
|
||||||
self:clean()
|
self:clean()
|
||||||
end
|
end
|
||||||
|
@ -105,11 +105,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
|
||||||
|
|
||||||
|
|
||||||
|
@ -118,121 +114,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
|
|
||||||
-- curors can never swap positions; only merge or split, or change their position in cursor
|
|
||||||
-- order.
|
|
||||||
function Doc:get_selection(sort)
|
|
||||||
local idx, line1, col1, line2, col2 = self:get_selections(sort)({ self.selections, sort }, 0)
|
|
||||||
return line1, col1, line2, col2, sort
|
|
||||||
end
|
|
||||||
|
|
||||||
function Doc:get_selection_text(limit)
|
function Doc:set_selection(line1, col1, line2, col2, swap)
|
||||||
limit = limit or math.huge
|
assert(not line2 == not col2, "expected 2 or 4 arguments")
|
||||||
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)
|
|
||||||
if line1 > line2 or line1 == line2 and col1 > col2 then
|
|
||||||
return line2, col2, line1, col1
|
|
||||||
end
|
|
||||||
return line1, col1, line2, col2
|
|
||||||
end
|
|
||||||
|
|
||||||
function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm)
|
|
||||||
assert(not line2 == not col2, "expected 3 or 5 arguments")
|
|
||||||
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
|
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
|
||||||
line1, col1 = self:sanitize_position(line1, col1)
|
line1, col1 = self:sanitize_position(line1, col1)
|
||||||
line2, col2 = self:sanitize_position(line2 or line1, col2 or 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 })
|
self.selection.a.line, self.selection.a.col = line1, col1
|
||||||
|
self.selection.b.line, self.selection.b.col = line2, col2
|
||||||
end
|
end
|
||||||
|
|
||||||
function Doc:add_selection(line1, col1, line2, col2, swap)
|
|
||||||
local l1, c1 = sort_positions(line1, col1, line2 or line1, col2 or col1)
|
local function sort_positions(line1, col1, line2, col2)
|
||||||
local target = #self.selections / 4 + 1
|
if line1 > line2
|
||||||
for idx, tl1, tc1 in self:get_selections(true) do
|
or line1 == line2 and col1 > col2 then
|
||||||
if l1 < tl1 or l1 == tl1 and c1 < tc1 then
|
return line2, col2, line1, col1, true
|
||||||
target = idx
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
self:set_selections(target, line1, col1, line2, col2, swap, 0)
|
return line1, col1, line2, col2, false
|
||||||
end
|
end
|
||||||
|
|
||||||
function Doc:set_selection(line1, col1, line2, col2, swap)
|
|
||||||
self.selections, self.cursor_clipboard = {}, {}
|
|
||||||
self:set_selections(1, line1, col1, line2, col2, swap)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Doc:merge_cursors(idx)
|
function Doc:get_selection(sort)
|
||||||
for i = (idx or (#self.selections - 3)), (idx or 5), -4 do
|
local a, b = self.selection.a, self.selection.b
|
||||||
for j = 1, i - 4, 4 do
|
if sort then
|
||||||
if self.selections[i] == self.selections[j] and
|
return sort_positions(a.line, a.col, b.line, b.col)
|
||||||
self.selections[i+1] == self.selections[j+1] then
|
|
||||||
common.splice(self.selections, i, 4)
|
|
||||||
common.splice(self.cursor_clipboard, i, 1)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
if #self.selections <= 4 then self.cursor_clipboard = {} end
|
return a.line, a.col, b.line, b.col
|
||||||
end
|
end
|
||||||
|
|
||||||
local function selection_iterator(invariant, idx)
|
|
||||||
local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1)
|
function Doc:has_selection()
|
||||||
if target > #invariant[1] or target <= 0 or (type(invariant[3]) == "number" and invariant[3] ~= idx - 1) then return end
|
local a, b = self.selection.a, self.selection.b
|
||||||
if invariant[2] then
|
return not (a.line == b.line and a.col == b.col)
|
||||||
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
|
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:sanitize_selection()
|
||||||
function Doc:get_selections(sort_intra, idx_reverse)
|
self:set_selection(self:get_selection())
|
||||||
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
|
||||||
-- 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)
|
||||||
|
@ -309,7 +233,7 @@ local function push_undo(undo_stack, time, type, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function pop_undo(self, undo_stack, redo_stack, modified)
|
local function pop_undo(self, undo_stack, redo_stack)
|
||||||
-- pop command
|
-- pop command
|
||||||
local cmd = undo_stack[undo_stack.idx - 1]
|
local cmd = undo_stack[undo_stack.idx - 1]
|
||||||
if not cmd then return end
|
if not cmd then return end
|
||||||
|
@ -319,25 +243,21 @@ 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
|
|
||||||
self.selections = { table.unpack(cmd) }
|
|
||||||
self:sanitize_selection()
|
|
||||||
end
|
|
||||||
|
|
||||||
modified = modified or (cmd.type ~= "selection")
|
elseif cmd.type == "selection" then
|
||||||
|
self.selection.a.line, self.selection.a.col = cmd[1], cmd[2]
|
||||||
|
self.selection.b.line, self.selection.b.col = cmd[3], cmd[4]
|
||||||
|
end
|
||||||
|
|
||||||
-- if next undo command is within the merge timeout then treat as a single
|
-- if next undo command is within the merge timeout then treat as a single
|
||||||
-- command and continue to execute it
|
-- command and continue to execute it
|
||||||
local next = undo_stack[undo_stack.idx - 1]
|
local next = undo_stack[undo_stack.idx - 1]
|
||||||
if next and math.abs(cmd.time - next.time) < config.undo_merge_timeout then
|
if next and math.abs(cmd.time - next.time) < config.undo_merge_timeout then
|
||||||
return pop_undo(self, undo_stack, redo_stack, modified)
|
return pop_undo(self, undo_stack, redo_stack)
|
||||||
end
|
|
||||||
|
|
||||||
if modified then
|
|
||||||
self:on_text_change("undo")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -345,7 +265,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
|
||||||
|
@ -355,23 +274,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
|
||||||
|
|
||||||
|
@ -379,7 +290,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
|
||||||
|
@ -387,18 +298,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
|
||||||
|
|
||||||
|
@ -407,7 +310,6 @@ function Doc:insert(line, col, text)
|
||||||
self.redo_stack = { idx = 1 }
|
self.redo_stack = { idx = 1 }
|
||||||
line, col = self:sanitize_position(line, col)
|
line, col = self:sanitize_position(line, col)
|
||||||
self:raw_insert(line, col, text, self.undo_stack, system.get_time())
|
self:raw_insert(line, col, text, self.undo_stack, system.get_time())
|
||||||
self:on_text_change("insert")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -417,160 +319,74 @@ function Doc:remove(line1, col1, line2, col2)
|
||||||
line2, col2 = self:sanitize_position(line2, col2)
|
line2, col2 = self:sanitize_position(line2, col2)
|
||||||
line1, col1, line2, col2 = sort_positions(line1, col1, line2, col2)
|
line1, col1, line2, col2 = sort_positions(line1, col1, line2, col2)
|
||||||
self:raw_remove(line1, col1, line2, col2, self.undo_stack, system.get_time())
|
self:raw_remove(line1, col1, line2, col2, self.undo_stack, system.get_time())
|
||||||
self:on_text_change("remove")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:undo()
|
function Doc:undo()
|
||||||
pop_undo(self, self.undo_stack, self.redo_stack, false)
|
pop_undo(self, self.undo_stack, self.redo_stack)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function Doc:redo()
|
function Doc:redo()
|
||||||
pop_undo(self, self.redo_stack, self.undo_stack, false)
|
pop_undo(self, self.redo_stack, self.undo_stack)
|
||||||
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, n = 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 n
|
return n
|
||||||
end
|
end
|
||||||
|
|
||||||
function Doc:replace(fn)
|
|
||||||
local has_selection, n = false, 0
|
|
||||||
for idx, line1, col1, line2, col2 in self:get_selections(true) do
|
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
|
||||||
n = n + self:replace_cursor(idx, line1, col1, line2, col2, fn)
|
|
||||||
has_selection = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not has_selection then
|
|
||||||
self:set_selection(table.unpack(self.selections))
|
|
||||||
n = n + self:replace_cursor(1, 1, 1, #self.lines, #self.lines[#self.lines], fn)
|
|
||||||
end
|
|
||||||
return n
|
|
||||||
end
|
|
||||||
|
|
||||||
|
function Doc:delete_to(...)
|
||||||
function Doc:delete_to_cursor(idx, ...)
|
local line, col = self:get_selection(true)
|
||||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
|
if self:has_selection() then
|
||||||
if line1 ~= line2 or col1 ~= col2 then
|
self:remove(self:get_selection())
|
||||||
self:remove(line1, col1, line2, col2)
|
|
||||||
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
|
|
||||||
-- 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).
|
|
||||||
function Doc:indent_text(unindent, line1, col1, 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
|
|
||||||
local e, rnded = self:get_line_indent(self.lines[line], unindent)
|
|
||||||
self:remove(line, 1, line, (e or 0) + 1)
|
|
||||||
self:insert(line, 1,
|
|
||||||
unindent and rnded:sub(1, #rnded - #text) or rnded .. text)
|
|
||||||
end
|
|
||||||
l1d, l2d = #self.lines[line1] - l1d, #self.lines[line2] - l2d
|
|
||||||
if (unindent or in_beginning_whitespace) and not has_selection then
|
|
||||||
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
|
|
||||||
function Doc:on_text_change(type)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- For plugins to get notified when a document is closed
|
function Doc:select_to(...)
|
||||||
function Doc:on_close()
|
local line, col, line2, col2 = self:get_selection()
|
||||||
core.log_quiet("Closed doc \"%s\"", self:get_name())
|
line, col = self:position_offset(line, col, ...)
|
||||||
|
self:set_selection(line, col, line2, col2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -9,7 +9,6 @@ local View = require "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
|
||||||
|
@ -48,6 +47,8 @@ DocView.translate = {
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local blink_period = 0.8
|
||||||
|
|
||||||
|
|
||||||
function DocView:new(doc)
|
function DocView:new(doc)
|
||||||
DocView.super.new(self)
|
DocView.super.new(self)
|
||||||
|
@ -56,6 +57,7 @@ function DocView:new(doc)
|
||||||
self.doc = assert(doc)
|
self.doc = assert(doc)
|
||||||
self.font = "code_font"
|
self.font = "code_font"
|
||||||
self.last_x_offset = {}
|
self.last_x_offset = {}
|
||||||
|
self.blink_timer = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,19 +90,7 @@ function DocView:get_name()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:get_filename()
|
|
||||||
if self.doc.abs_filename then
|
|
||||||
local post = self.doc:is_dirty() and "*" or ""
|
|
||||||
return common.home_encode(self.doc.abs_filename) .. post
|
|
||||||
end
|
|
||||||
return self:get_name()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function DocView:get_scrollable_size()
|
function DocView:get_scrollable_size()
|
||||||
if not config.scroll_past_end then
|
|
||||||
return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2
|
|
||||||
end
|
|
||||||
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
|
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -116,8 +106,7 @@ 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
|
||||||
|
|
||||||
|
|
||||||
|
@ -146,43 +135,27 @@ end
|
||||||
|
|
||||||
|
|
||||||
function DocView:get_col_x_offset(line, col)
|
function DocView:get_col_x_offset(line, col)
|
||||||
local default_font = self:get_font()
|
local text = self.doc.lines[line]
|
||||||
local column = 1
|
if not text then return 0 end
|
||||||
local xoffset = 0
|
return self:get_font():get_width(text:sub(1, col - 1))
|
||||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
|
||||||
local font = style.syntax_fonts[type] or default_font
|
|
||||||
for char in common.utf8_chars(text) do
|
|
||||||
if column == col then
|
|
||||||
return xoffset
|
|
||||||
end
|
|
||||||
xoffset = xoffset + font:get_width(char)
|
|
||||||
column = column + #char
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return xoffset
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:get_x_offset_col(line, x)
|
function DocView:get_x_offset_col(line, x)
|
||||||
local line_text = self.doc.lines[line]
|
local text = self.doc.lines[line]
|
||||||
|
|
||||||
local xoffset, last_i, i = 0, 1, 1
|
local xoffset, last_i, i = 0, 1, 1
|
||||||
local default_font = self:get_font()
|
for char in common.utf8_chars(text) do
|
||||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
local w = self:get_font():get_width(char)
|
||||||
local font = style.syntax_fonts[type] or default_font
|
if xoffset >= x then
|
||||||
for char in common.utf8_chars(text) do
|
return (xoffset - x > w / 2) and last_i or i
|
||||||
local w = font:get_width(char)
|
|
||||||
if xoffset >= x then
|
|
||||||
return (xoffset - x > w / 2) and last_i or i
|
|
||||||
end
|
|
||||||
xoffset = xoffset + w
|
|
||||||
last_i = i
|
|
||||||
i = i + #char
|
|
||||||
end
|
end
|
||||||
|
xoffset = xoffset + w
|
||||||
|
last_i = i
|
||||||
|
i = i + #char
|
||||||
end
|
end
|
||||||
|
|
||||||
return #line_text
|
return #text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -225,6 +198,47 @@ function DocView:scroll_to_make_visible(line, col)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function mouse_selection(doc, clicks, line1, col1, line2, col2)
|
||||||
|
local swap = line2 < line1 or line2 == line1 and col2 <= col1
|
||||||
|
if swap then
|
||||||
|
line1, col1, line2, col2 = line2, col2, line1, col1
|
||||||
|
end
|
||||||
|
if clicks == 2 then
|
||||||
|
line1, col1 = translate.start_of_word(doc, line1, col1)
|
||||||
|
line2, col2 = translate.end_of_word(doc, line2, col2)
|
||||||
|
elseif clicks == 3 then
|
||||||
|
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
|
||||||
|
if swap then
|
||||||
|
return line2, col2, line1, col1
|
||||||
|
end
|
||||||
|
return line1, col1, line2, col2
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function DocView:on_mouse_pressed(button, x, y, clicks)
|
||||||
|
local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||||
|
if caught then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if keymap.modkeys["shift"] then
|
||||||
|
if clicks == 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
|
||||||
|
self.blink_timer = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:on_mouse_moved(x, y, ...)
|
function DocView:on_mouse_moved(x, y, ...)
|
||||||
DocView.super.on_mouse_moved(self, x, y, ...)
|
DocView.super.on_mouse_moved(self, x, y, ...)
|
||||||
|
|
||||||
|
@ -236,41 +250,13 @@ function DocView:on_mouse_moved(x, y, ...)
|
||||||
|
|
||||||
if self.mouse_selecting then
|
if self.mouse_selecting then
|
||||||
local l1, c1 = self:resolve_screen_position(x, y)
|
local l1, c1 = self:resolve_screen_position(x, y)
|
||||||
local l2, c2, snap_type = table.unpack(self.mouse_selecting)
|
local l2, c2 = table.unpack(self.mouse_selecting)
|
||||||
if keymap.modkeys["ctrl"] then
|
local clicks = self.mouse_selecting.clicks
|
||||||
if l1 > l2 then l1, l2 = l2, l1 end
|
self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2))
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function DocView:mouse_selection(doc, snap_type, line1, col1, line2, col2)
|
|
||||||
local swap = line2 < line1 or line2 == line1 and col2 <= col1
|
|
||||||
if swap then
|
|
||||||
line1, col1, line2, col2 = line2, col2, line1, col1
|
|
||||||
end
|
|
||||||
if snap_type == "word" then
|
|
||||||
line1, col1 = translate.start_of_word(doc, line1, col1)
|
|
||||||
line2, col2 = translate.end_of_word(doc, line2, col2)
|
|
||||||
elseif snap_type == "lines" then
|
|
||||||
col1, col2 = 1, math.huge
|
|
||||||
end
|
|
||||||
if swap then
|
|
||||||
return line2, col2, line1, col1
|
|
||||||
end
|
|
||||||
return line1, col1, line2, col2
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function DocView:on_mouse_released(button)
|
function DocView:on_mouse_released(button)
|
||||||
DocView.super.on_mouse_released(self, button)
|
DocView.super.on_mouse_released(self, button)
|
||||||
self.mouse_selecting = nil
|
self.mouse_selecting = nil
|
||||||
|
@ -289,18 +275,18 @@ function DocView:update()
|
||||||
if core.active_view == self then
|
if core.active_view == self then
|
||||||
self:scroll_to_make_visible(line, col)
|
self:scroll_to_make_visible(line, col)
|
||||||
end
|
end
|
||||||
core.blink_reset()
|
self.blink_timer = 0
|
||||||
self.last_line, self.last_col = line, col
|
self.last_line, self.last_col = line, col
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update blink timer
|
-- update blink timer
|
||||||
if self == core.active_view and not self.mouse_selecting then
|
if self == core.active_view and not self.mouse_selecting then
|
||||||
local T, t0 = config.blink_period, core.blink_start
|
local n = blink_period / 2
|
||||||
local ta, tb = core.blink_timer, system.get_time()
|
local prev = self.blink_timer
|
||||||
if ((tb - t0) % T < T / 2) ~= ((ta - t0) % T < T / 2) then
|
self.blink_timer = (self.blink_timer + 1 / config.fps) % blink_period
|
||||||
|
if (self.blink_timer > n) ~= (prev > n) then
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
end
|
end
|
||||||
core.blink_timer = tb
|
|
||||||
end
|
end
|
||||||
|
|
||||||
DocView.super.update(self)
|
DocView.super.update(self)
|
||||||
|
@ -314,110 +300,86 @@ end
|
||||||
|
|
||||||
|
|
||||||
function DocView:draw_line_text(idx, x, y)
|
function DocView:draw_line_text(idx, x, y)
|
||||||
local default_font = self:get_font()
|
|
||||||
local tx, ty = x, y + self:get_line_text_y_offset()
|
local tx, ty = x, y + self:get_line_text_y_offset()
|
||||||
|
local font = self:get_font()
|
||||||
for _, type, text in self.doc.highlighter:each_token(idx) do
|
for _, type, text in self.doc.highlighter:each_token(idx) do
|
||||||
local color = style.syntax[type]
|
local color = style.syntax[type]
|
||||||
local font = style.syntax_fonts[type] or default_font
|
|
||||||
tx = renderer.draw_text(font, text, tx, ty, color)
|
tx = renderer.draw_text(font, text, tx, ty, color)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
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(idx, x, y)
|
function DocView:draw_line_body(idx, x, y)
|
||||||
-- draw highlight if any selection ends on this line
|
local line, col = self.doc:get_selection()
|
||||||
local draw_highlight = false
|
|
||||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
|
|
||||||
if line1 == idx then
|
|
||||||
draw_highlight = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if draw_highlight and config.highlight_current_line and core.active_view == self then
|
|
||||||
self:draw_line_highlight(x + self.scroll.x, y)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- draw selection if it overlaps this line
|
-- draw selection if it overlaps this line
|
||||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
local line1, col1, line2, col2 = self.doc:get_selection(true)
|
||||||
if idx >= line1 and idx <= line2 then
|
if idx >= line1 and idx <= line2 then
|
||||||
local text = self.doc.lines[idx]
|
local text = self.doc.lines[idx]
|
||||||
if line1 ~= idx then col1 = 1 end
|
if line1 ~= idx then col1 = 1 end
|
||||||
if line2 ~= idx then col2 = #text + 1 end
|
if line2 ~= idx then col2 = #text + 1 end
|
||||||
local x1 = x + self:get_col_x_offset(idx, col1)
|
local x1 = x + self:get_col_x_offset(idx, col1)
|
||||||
local x2 = x + self:get_col_x_offset(idx, col2)
|
local x2 = x + self:get_col_x_offset(idx, col2)
|
||||||
local lh = self:get_line_height()
|
local lh = self:get_line_height()
|
||||||
if x1 ~= x2 then
|
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
end
|
||||||
end
|
|
||||||
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)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- draw line's text
|
-- draw line's text
|
||||||
self:draw_line_text(idx, x, y)
|
self:draw_line_text(idx, x, y)
|
||||||
|
|
||||||
|
-- draw caret if it overlaps this line
|
||||||
|
if line == idx and core.active_view == self
|
||||||
|
and self.blink_timer < blink_period / 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(idx, 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 idx >= line1 and idx <= 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()
|
local yoffset = self:get_line_text_y_offset()
|
||||||
x = x + style.padding.x
|
x = x + style.padding.x
|
||||||
common.draw_text(self:get_font(), color, idx, "right", x, y + yoffset, width, self:get_line_height())
|
renderer.draw_text(self:get_font(), idx, x, y + yoffset, color)
|
||||||
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
|
|
||||||
local x, y = self:get_line_screen_position(line)
|
|
||||||
self:draw_caret(x + self:get_col_x_offset(line, col), y)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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_width(font:get_width(" ") * 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
|
||||||
self:draw_line_gutter(i, self.position.x, y, gpad and gw - gpad or gw)
|
self:draw_line_gutter(i, x, y)
|
||||||
y = y + lh
|
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
|
||||||
self:draw_line_body(i, x, y)
|
self:draw_line_body(i, x, y)
|
||||||
y = y + lh
|
y = y + lh
|
||||||
end
|
end
|
||||||
self:draw_overlay()
|
|
||||||
core.pop_clip_rect()
|
core.pop_clip_rect()
|
||||||
|
|
||||||
self:draw_scrollbar()
|
self:draw_scrollbar()
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
local style = require "core.style"
|
|
||||||
local keymap = require "core.keymap"
|
|
||||||
local View = require "core.view"
|
|
||||||
|
|
||||||
local EmptyView = View:extend()
|
|
||||||
|
|
||||||
local function draw_text(x, y, color)
|
|
||||||
local th = style.big_font:get_height()
|
|
||||||
local dh = 2 * th + style.padding.y * 2
|
|
||||||
local x1, y1 = x, y + (dh - th) / 2
|
|
||||||
x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color)
|
|
||||||
renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color)
|
|
||||||
x = x + style.padding.x
|
|
||||||
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
|
|
||||||
local lines = {
|
|
||||||
{ fmt = "%s to run a command", cmd = "core:find-command" },
|
|
||||||
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
|
|
||||||
{ fmt = "%s to change project folder", cmd = "core:change-project-folder" },
|
|
||||||
{ fmt = "%s to open a project folder", cmd = "core:open-project-folder" },
|
|
||||||
}
|
|
||||||
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
|
|
1044
data/core/init.lua
1044
data/core/init.lua
File diff suppressed because it is too large
Load Diff
|
@ -1,125 +0,0 @@
|
||||||
local function keymap_macos(keymap)
|
|
||||||
keymap.add_direct {
|
|
||||||
["cmd+shift+p"] = "core:find-command",
|
|
||||||
["cmd+p"] = "core:find-file",
|
|
||||||
["cmd+o"] = "core:open-file",
|
|
||||||
["cmd+n"] = "core:new-doc",
|
|
||||||
["cmd+shift+c"] = "core:change-project-folder",
|
|
||||||
["cmd+shift+o"] = "core:open-project-folder",
|
|
||||||
["cmd+shift+r"] = "core:restart",
|
|
||||||
["cmd+ctrl+return"] = "core:toggle-fullscreen",
|
|
||||||
|
|
||||||
["cmd+ctrl+shift+j"] = "root:split-left",
|
|
||||||
["cmd+ctrl+shift+l"] = "root:split-right",
|
|
||||||
["cmd+ctrl+shift+i"] = "root:split-up",
|
|
||||||
["cmd+ctrl+shift+k"] = "root:split-down",
|
|
||||||
["cmd+ctrl+j"] = "root:switch-to-left",
|
|
||||||
["cmd+ctrl+l"] = "root:switch-to-right",
|
|
||||||
["cmd+ctrl+i"] = "root:switch-to-up",
|
|
||||||
["cmd+ctrl+k"] = "root:switch-to-down",
|
|
||||||
|
|
||||||
|
|
||||||
["cmd+w"] = "root:close-or-quit",
|
|
||||||
["ctrl+tab"] = "root:switch-to-next-tab",
|
|
||||||
["ctrl+shift+tab"] = "root:switch-to-previous-tab",
|
|
||||||
["cmd+pageup"] = "root:move-tab-left",
|
|
||||||
["cmd+pagedown"] = "root:move-tab-right",
|
|
||||||
["cmd+1"] = "root:switch-to-tab-1",
|
|
||||||
["cmd+2"] = "root:switch-to-tab-2",
|
|
||||||
["cmd+3"] = "root:switch-to-tab-3",
|
|
||||||
["cmd+4"] = "root:switch-to-tab-4",
|
|
||||||
["cmd+5"] = "root:switch-to-tab-5",
|
|
||||||
["cmd+6"] = "root:switch-to-tab-6",
|
|
||||||
["cmd+7"] = "root:switch-to-tab-7",
|
|
||||||
["cmd+8"] = "root:switch-to-tab-8",
|
|
||||||
["cmd+9"] = "root:switch-to-tab-9",
|
|
||||||
["wheel"] = "root:scroll",
|
|
||||||
|
|
||||||
["cmd+f"] = "find-replace:find",
|
|
||||||
["cmd+r"] = "find-replace:replace",
|
|
||||||
["f3"] = "find-replace:repeat-find",
|
|
||||||
["shift+f3"] = "find-replace:previous-find",
|
|
||||||
["cmd+g"] = "doc:go-to-line",
|
|
||||||
["cmd+s"] = "doc:save",
|
|
||||||
["cmd+shift+s"] = "doc:save-as",
|
|
||||||
|
|
||||||
["cmd+z"] = "doc:undo",
|
|
||||||
["cmd+y"] = "doc:redo",
|
|
||||||
["cmd+x"] = "doc:cut",
|
|
||||||
["cmd+c"] = "doc:copy",
|
|
||||||
["cmd+v"] = "doc:paste",
|
|
||||||
["ctrl+insert"] = "doc:copy",
|
|
||||||
["shift+insert"] = "doc:paste",
|
|
||||||
["escape"] = { "command:escape", "doc:select-none", "dialog:select-no" },
|
|
||||||
["tab"] = { "command:complete", "doc:indent" },
|
|
||||||
["shift+tab"] = "doc:unindent",
|
|
||||||
["backspace"] = "doc:backspace",
|
|
||||||
["shift+backspace"] = "doc:backspace",
|
|
||||||
["option+backspace"] = "doc:delete-to-previous-word-start",
|
|
||||||
["cmd+shift+backspace"] = "doc:delete-to-previous-word-start",
|
|
||||||
["cmd+backspace"] = "doc:delete-to-start-of-indentation",
|
|
||||||
["delete"] = "doc:delete",
|
|
||||||
["shift+delete"] = "doc:delete",
|
|
||||||
["option+delete"] = "doc:delete-to-next-word-end",
|
|
||||||
["cmd+shift+delete"] = "doc:delete-to-next-word-end",
|
|
||||||
["cmd+delete"] = "doc:delete-to-end-of-line",
|
|
||||||
["return"] = { "command:submit", "doc:newline", "dialog:select" },
|
|
||||||
["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" },
|
|
||||||
["cmd+return"] = "doc:newline-below",
|
|
||||||
["cmd+shift+return"] = "doc:newline-above",
|
|
||||||
["cmd+j"] = "doc:join-lines",
|
|
||||||
["cmd+a"] = "doc:select-all",
|
|
||||||
["cmd+d"] = { "find-replace:select-add-next", "doc:select-word" },
|
|
||||||
["cmd+f3"] = "find-replace:select-next",
|
|
||||||
["cmd+l"] = "doc:select-lines",
|
|
||||||
["cmd+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
|
||||||
["cmd+/"] = "doc:toggle-line-comments",
|
|
||||||
["option+up"] = "doc:move-lines-up",
|
|
||||||
["option+down"] = "doc:move-lines-down",
|
|
||||||
["cmd+shift+d"] = "doc:duplicate-lines",
|
|
||||||
["cmd+shift+k"] = "doc:delete-lines",
|
|
||||||
|
|
||||||
["left"] = { "doc:move-to-previous-char", "dialog:previous-entry" },
|
|
||||||
["right"] = { "doc:move-to-next-char", "dialog:next-entry"},
|
|
||||||
["up"] = { "command:select-previous", "doc:move-to-previous-line" },
|
|
||||||
["down"] = { "command:select-next", "doc:move-to-next-line" },
|
|
||||||
["option+left"] = "doc:move-to-previous-word-start",
|
|
||||||
["option+right"] = "doc:move-to-next-word-end",
|
|
||||||
["cmd+left"] = "doc:move-to-start-of-indentation",
|
|
||||||
["cmd+right"] = "doc:move-to-end-of-line",
|
|
||||||
["cmd+["] = "doc:move-to-previous-block-start",
|
|
||||||
["cmd+]"] = "doc:move-to-next-block-end",
|
|
||||||
["home"] = "doc:move-to-start-of-indentation",
|
|
||||||
["end"] = "doc:move-to-end-of-line",
|
|
||||||
["cmd+up"] = "doc:move-to-start-of-doc",
|
|
||||||
["cmd+down"] = "doc:move-to-end-of-doc",
|
|
||||||
["pageup"] = "doc:move-to-previous-page",
|
|
||||||
["pagedown"] = "doc:move-to-next-page",
|
|
||||||
|
|
||||||
["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+right"] = "doc:select-to-next-char",
|
|
||||||
["shift+up"] = "doc:select-to-previous-line",
|
|
||||||
["shift+down"] = "doc:select-to-next-line",
|
|
||||||
["option+shift+left"] = "doc:select-to-previous-word-start",
|
|
||||||
["option+shift+right"] = "doc:select-to-next-word-end",
|
|
||||||
["cmd+shift+left"] = "doc:select-to-start-of-indentation",
|
|
||||||
["cmd+shift+right"] = "doc:select-to-end-of-line",
|
|
||||||
["cmd+shift+["] = "doc:select-to-previous-block-start",
|
|
||||||
["cmd+shift+]"] = "doc:select-to-next-block-end",
|
|
||||||
["shift+home"] = "doc:select-to-start-of-indentation",
|
|
||||||
["shift+end"] = "doc:select-to-end-of-line",
|
|
||||||
["cmd+shift+up"] = "doc:select-to-start-of-doc",
|
|
||||||
["cmd+shift+down"] = "doc:select-to-end-of-doc",
|
|
||||||
["shift+pageup"] = "doc:select-to-previous-page",
|
|
||||||
["shift+pagedown"] = "doc:select-to-next-page",
|
|
||||||
["cmd+option+up"] = "doc:create-cursor-previous-line",
|
|
||||||
["cmd+option+down"] = "doc:create-cursor-next-line"
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return keymap_macos
|
|
|
@ -1,18 +1,20 @@
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local config = require "core.config"
|
|
||||||
local keymap = {}
|
local keymap = {}
|
||||||
|
|
||||||
keymap.modkeys = {}
|
keymap.modkeys = {}
|
||||||
keymap.map = {}
|
keymap.map = {}
|
||||||
keymap.reverse_map = {}
|
keymap.reverse_map = {}
|
||||||
|
|
||||||
local macos = PLATFORM == "Mac OS X"
|
local modkey_map = {
|
||||||
local os4 = PLATFORM == "AmigaOS 4"
|
["left ctrl"] = "ctrl",
|
||||||
|
["right ctrl"] = "ctrl",
|
||||||
|
["left shift"] = "shift",
|
||||||
|
["right shift"] = "shift",
|
||||||
|
["left alt"] = "alt",
|
||||||
|
["right alt"] = "altgr",
|
||||||
|
}
|
||||||
|
|
||||||
-- Thanks to mathewmariani, taken from his lite-macos github repository.
|
local modkeys = { "ctrl", "alt", "altgr", "shift" }
|
||||||
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or os4 and "os4" or "generic"))
|
|
||||||
local modkey_map = modkeys_os.map
|
|
||||||
local modkeys = modkeys_os.keys
|
|
||||||
|
|
||||||
local function key_to_stroke(k)
|
local function key_to_stroke(k)
|
||||||
local stroke = ""
|
local stroke = ""
|
||||||
|
@ -25,24 +27,8 @@ local function key_to_stroke(k)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function keymap.add_direct(map)
|
|
||||||
for stroke, commands in pairs(map) do
|
|
||||||
if type(commands) == "string" then
|
|
||||||
commands = { commands }
|
|
||||||
end
|
|
||||||
keymap.map[stroke] = commands
|
|
||||||
for _, cmd in ipairs(commands) do
|
|
||||||
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
|
|
||||||
table.insert(keymap.reverse_map[cmd], stroke)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function keymap.add(map, overwrite)
|
function keymap.add(map, overwrite)
|
||||||
for stroke, commands in pairs(map) do
|
for stroke, commands in pairs(map) do
|
||||||
if macos then
|
|
||||||
stroke = stroke:gsub("%f[%a]ctrl%f[%A]", "cmd")
|
|
||||||
end
|
|
||||||
if type(commands) == "string" then
|
if type(commands) == "string" then
|
||||||
commands = { commands }
|
commands = { commands }
|
||||||
end
|
end
|
||||||
|
@ -55,43 +41,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
|
||||||
|
|
||||||
|
|
||||||
local function remove_only(tbl, k, v)
|
|
||||||
for key, values in pairs(tbl) do
|
|
||||||
if key == k then
|
|
||||||
if v then
|
|
||||||
for i, value in ipairs(values) do
|
|
||||||
if value == v then
|
|
||||||
table.remove(values, i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
tbl[key] = nil
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function keymap.unbind(key, cmd)
|
|
||||||
remove_only(keymap.map, key, cmd)
|
|
||||||
remove_only(keymap.reverse_map, cmd, key)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function keymap.get_binding(cmd)
|
function keymap.get_binding(cmd)
|
||||||
return table.unpack(keymap.reverse_map[cmd] or {})
|
return keymap.reverse_map[cmd]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function keymap.on_key_pressed(k, ...)
|
function keymap.on_key_pressed(k)
|
||||||
local mk = modkey_map[k]
|
local mk = modkey_map[k]
|
||||||
if mk then
|
if mk then
|
||||||
keymap.modkeys[mk] = true
|
keymap.modkeys[mk] = true
|
||||||
|
@ -101,30 +62,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]
|
local commands = keymap.map[stroke]
|
||||||
if commands then
|
if commands then
|
||||||
for _, cmd in ipairs(commands) do
|
for _, cmd in ipairs(commands) do
|
||||||
performed = command.perform(cmd, ...)
|
local performed = command.perform(cmd)
|
||||||
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]
|
||||||
|
@ -134,22 +83,14 @@ function keymap.on_key_released(k)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
if macos then
|
keymap.add {
|
||||||
local keymap_macos = require("core.keymap-macos")
|
|
||||||
keymap_macos(keymap)
|
|
||||||
return keymap
|
|
||||||
end
|
|
||||||
|
|
||||||
keymap.add_direct {
|
|
||||||
["ctrl+shift+p"] = "core:find-command",
|
["ctrl+shift+p"] = "core:find-command",
|
||||||
["ctrl+p"] = "core:find-file",
|
["ctrl+p"] = "core:find-file",
|
||||||
["ctrl+o"] = "core:open-file",
|
["ctrl+o"] = "core:open-file",
|
||||||
["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+shift+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",
|
||||||
|
@ -174,14 +115,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",
|
||||||
|
@ -191,9 +129,7 @@ keymap.add_direct {
|
||||||
["ctrl+x"] = "doc:cut",
|
["ctrl+x"] = "doc:cut",
|
||||||
["ctrl+c"] = "doc:copy",
|
["ctrl+c"] = "doc:copy",
|
||||||
["ctrl+v"] = "doc:paste",
|
["ctrl+v"] = "doc:paste",
|
||||||
["ctrl+insert"] = "doc:copy",
|
["escape"] = { "command:escape", "doc:select-none" },
|
||||||
["shift+insert"] = "doc:paste",
|
|
||||||
["escape"] = { "command:escape", "doc:select-none", "dialog:select-no" },
|
|
||||||
["tab"] = { "command:complete", "doc:indent" },
|
["tab"] = { "command:complete", "doc:indent" },
|
||||||
["shift+tab"] = "doc:unindent",
|
["shift+tab"] = "doc:unindent",
|
||||||
["backspace"] = "doc:backspace",
|
["backspace"] = "doc:backspace",
|
||||||
|
@ -204,43 +140,35 @@ keymap.add_direct {
|
||||||
["shift+delete"] = "doc:delete",
|
["shift+delete"] = "doc:delete",
|
||||||
["ctrl+delete"] = "doc:delete-to-next-word-end",
|
["ctrl+delete"] = "doc:delete-to-next-word-end",
|
||||||
["ctrl+shift+delete"] = "doc:delete-to-next-word-end",
|
["ctrl+shift+delete"] = "doc:delete-to-next-word-end",
|
||||||
["return"] = { "command:submit", "doc:newline", "dialog:select" },
|
["return"] = { "command:submit", "doc:newline" },
|
||||||
["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" },
|
["keypad enter"] = { "command:submit", "doc:newline" },
|
||||||
["ctrl+return"] = "doc:newline-below",
|
["ctrl+return"] = "doc:newline-below",
|
||||||
["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+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",
|
||||||
["ctrl+shift+k"] = "doc:delete-lines",
|
["ctrl+shift+k"] = "doc:delete-lines",
|
||||||
|
|
||||||
["left"] = { "doc:move-to-previous-char", "dialog:previous-entry" },
|
["left"] = "doc:move-to-previous-char",
|
||||||
["right"] = { "doc:move-to-next-char", "dialog:next-entry"},
|
["right"] = "doc:move-to-next-char",
|
||||||
["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" },
|
||||||
["ctrl+left"] = "doc:move-to-previous-word-start",
|
["ctrl+left"] = "doc:move-to-previous-word-start",
|
||||||
["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",
|
||||||
|
@ -249,15 +177,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,45 +1,14 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
|
||||||
local style = require "core.style"
|
local style = require "core.style"
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
|
|
||||||
|
|
||||||
local 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
|
||||||
end
|
end
|
||||||
|
@ -50,55 +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:on_mouse_moved(px, py, ...)
|
|
||||||
LogView.super.on_mouse_moved(self, px, py, ...)
|
|
||||||
local hovered = false
|
|
||||||
for _, item, x, y, w, h in self:each_item() do
|
|
||||||
if px >= x and py >= y and px < x + w and py < y + h then
|
|
||||||
hovered = true
|
|
||||||
self.hovered_item = item
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not hovered then self.hovered_item = nil end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function LogView:on_mouse_pressed(button, mx, my, clicks)
|
|
||||||
if LogView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
|
|
||||||
if self.hovered_item then
|
|
||||||
self:expand_item(self.hovered_item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function LogView:update()
|
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
|
||||||
|
@ -107,14 +27,6 @@ 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]
|
|
||||||
if expanding then
|
|
||||||
self:move_towards(expanding, "current", expanding.target)
|
|
||||||
if expanding.current == expanding.target then
|
|
||||||
table.remove(self.expanding, 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:move_towards("yoffset", 0)
|
self:move_towards("yoffset", 0)
|
||||||
|
|
||||||
LogView.super.update(self)
|
LogView.super.update(self)
|
||||||
|
@ -123,48 +35,38 @@ 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
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
for _, item, x, y, w in self:each_item() do
|
|
||||||
x = x + style.padding.x
|
|
||||||
|
|
||||||
|
for i = #core.log_items, 1, -1 do
|
||||||
|
local x = ox + style.padding.x
|
||||||
|
local item = core.log_items[i]
|
||||||
local time = os.date(nil, item.time)
|
local time = os.date(nil, item.time)
|
||||||
x = common.draw_text(style.font, style.dim, time, "left", x, y, w, lh)
|
x = renderer.draw_text(style.font, time, x, y, style.dim)
|
||||||
x = x + style.padding.x
|
x = x + style.padding.x
|
||||||
|
local subx = x
|
||||||
x = common.draw_text(style.code_font, style.dim, is_expanded(item) and "-" or "+", "left", x, y, w, lh)
|
x, y = draw_text_multiline(style.font, item.text, x, y, style.text)
|
||||||
x = x + style.padding.x
|
renderer.draw_text(style.font, " at " .. item.at, x, y, style.dim)
|
||||||
w = w - (x - self:get_content_offset())
|
y = y + th
|
||||||
|
if item.info then
|
||||||
if is_expanded(item) then
|
subx, y = draw_text_multiline(style.font, item.info, subx, y, style.dim)
|
||||||
y = y + common.round(style.padding.y / 2)
|
y = y + th
|
||||||
_, y = draw_text_multiline(style.font, item.text, x, y, style.text)
|
|
||||||
|
|
||||||
local at = "at " .. common.home_encode(item.at)
|
|
||||||
_, y = common.draw_text(style.font, style.dim, at, "left", x, y, w, lh)
|
|
||||||
|
|
||||||
if item.info then
|
|
||||||
_, y = draw_text_multiline(style.font, item.info, x, y, style.dim)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local line, has_newline = string.match(item.text, "([^\n]+)(\n?)")
|
|
||||||
if has_newline ~= "" then
|
|
||||||
line = line .. " ..."
|
|
||||||
end
|
|
||||||
_, y = common.draw_text(style.font, style.text, line, "left", x, y, w, lh)
|
|
||||||
end
|
end
|
||||||
|
y = y + style.padding.y
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
local modkeys = {}
|
|
||||||
|
|
||||||
modkeys.map = {
|
|
||||||
["left ctrl"] = "ctrl",
|
|
||||||
["right ctrl"] = "ctrl",
|
|
||||||
["left shift"] = "shift",
|
|
||||||
["right shift"] = "shift",
|
|
||||||
["left alt"] = "alt",
|
|
||||||
["right alt"] = "altgr",
|
|
||||||
}
|
|
||||||
|
|
||||||
modkeys.keys = { "ctrl", "alt", "altgr", "shift" }
|
|
||||||
|
|
||||||
return modkeys
|
|
|
@ -1,18 +0,0 @@
|
||||||
local modkeys = {}
|
|
||||||
|
|
||||||
modkeys.map = {
|
|
||||||
["left command"] = "cmd",
|
|
||||||
["right command"] = "cmd",
|
|
||||||
["left ctrl"] = "ctrl",
|
|
||||||
["right ctrl"] = "ctrl",
|
|
||||||
["left shift"] = "shift",
|
|
||||||
["right shift"] = "shift",
|
|
||||||
["left option"] = "option",
|
|
||||||
["right option"] = "option",
|
|
||||||
["left alt"] = "alt",
|
|
||||||
["right alt"] = "altgr",
|
|
||||||
}
|
|
||||||
|
|
||||||
modkeys.keys = { "cmd", "ctrl", "alt", "option", "altgr", "shift" }
|
|
||||||
|
|
||||||
return modkeys
|
|
|
@ -1,15 +0,0 @@
|
||||||
local modkeys = {}
|
|
||||||
|
|
||||||
modkeys.map = {
|
|
||||||
["left amiga"] = "cmd",
|
|
||||||
["right amiga"] = "cmd",
|
|
||||||
["control"] = "ctrl",
|
|
||||||
["left shift"] = "shift",
|
|
||||||
["right shift"] = "shift",
|
|
||||||
["left alt"] = "alt",
|
|
||||||
["right alt"] = "altgr",
|
|
||||||
}
|
|
||||||
|
|
||||||
modkeys.keys = { "cmd", "ctrl", "alt", "altgr", "shift" }
|
|
||||||
|
|
||||||
return modkeys
|
|
|
@ -1,210 +0,0 @@
|
||||||
local core = require "core"
|
|
||||||
local command = require "core.command"
|
|
||||||
local common = require "core.common"
|
|
||||||
local config = require "core.config"
|
|
||||||
local View = require "core.view"
|
|
||||||
local style = require "core.style"
|
|
||||||
|
|
||||||
local BORDER_WIDTH = common.round(1 * SCALE)
|
|
||||||
local UNDERLINE_WIDTH = common.round(2 * SCALE)
|
|
||||||
local UNDERLINE_MARGIN = common.round(1 * SCALE)
|
|
||||||
|
|
||||||
local noop = function() end
|
|
||||||
|
|
||||||
local NagView = View:extend()
|
|
||||||
|
|
||||||
function NagView:new()
|
|
||||||
NagView.super.new(self)
|
|
||||||
self.size.y = 0
|
|
||||||
self.force_focus = false
|
|
||||||
self.queue = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:get_title()
|
|
||||||
return self.title
|
|
||||||
end
|
|
||||||
|
|
||||||
-- The two methods below are duplicated from DocView
|
|
||||||
function NagView:get_line_height()
|
|
||||||
return math.floor(style.font:get_height() * config.line_height)
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:get_line_text_y_offset()
|
|
||||||
local lh = self:get_line_height()
|
|
||||||
local th = style.font:get_height()
|
|
||||||
return (lh - th) / 2
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Buttons height without padding
|
|
||||||
function NagView:get_buttons_height()
|
|
||||||
local lh = style.font:get_height()
|
|
||||||
local bt_padding = lh / 2
|
|
||||||
return lh + 2 * BORDER_WIDTH + 2 * bt_padding
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:get_target_height()
|
|
||||||
return self.target_height + 2 * style.padding.y
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:update()
|
|
||||||
NagView.super.update(self)
|
|
||||||
|
|
||||||
if core.active_view == self and self.title then
|
|
||||||
self:move_towards(self.size, "y", self:get_target_height())
|
|
||||||
self:move_towards(self, "underline_progress", 1)
|
|
||||||
else
|
|
||||||
self:move_towards(self.size, "y", 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:draw_overlay()
|
|
||||||
local ox, oy = self:get_content_offset()
|
|
||||||
oy = oy + self.size.y
|
|
||||||
local w, h = core.root_view.size.x, core.root_view.size.y - oy
|
|
||||||
core.root_view:defer_draw(function()
|
|
||||||
renderer.draw_rect(ox, oy, w, h, style.nagbar_dim)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:change_hovered(i)
|
|
||||||
if i ~= self.hovered_item then
|
|
||||||
self.hovered_item = i
|
|
||||||
self.underline_progress = 0
|
|
||||||
core.redraw = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:each_option()
|
|
||||||
return coroutine.wrap(function()
|
|
||||||
if not self.options then return end
|
|
||||||
local opt, bw,bh,ox,oy
|
|
||||||
bh = self:get_buttons_height()
|
|
||||||
ox,oy = self:get_content_offset()
|
|
||||||
ox = ox + self.size.x
|
|
||||||
oy = oy + self.size.y - bh - style.padding.y
|
|
||||||
|
|
||||||
for i = #self.options, 1, -1 do
|
|
||||||
opt = self.options[i]
|
|
||||||
bw = opt.font:get_width(opt.text) + 2 * BORDER_WIDTH + style.padding.x
|
|
||||||
|
|
||||||
ox = ox - bw - style.padding.x
|
|
||||||
coroutine.yield(i, opt, ox,oy,bw,bh)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:on_mouse_moved(mx, my, ...)
|
|
||||||
NagView.super.on_mouse_moved(self, mx, my, ...)
|
|
||||||
for i, _, x,y,w,h in self:each_option() do
|
|
||||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
|
||||||
self:change_hovered(i)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:on_mouse_pressed(button, mx, my, clicks)
|
|
||||||
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
|
|
||||||
for i, _, x,y,w,h in self:each_option() do
|
|
||||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
|
||||||
self:change_hovered(i)
|
|
||||||
command.perform "dialog:select"
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:on_text_input(text)
|
|
||||||
if text:lower() == "y" then
|
|
||||||
command.perform "dialog:select-yes"
|
|
||||||
elseif text:lower() == "n" then
|
|
||||||
command.perform "dialog:select-no"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function NagView:draw()
|
|
||||||
if self.size.y <= 0 or not self.title then return end
|
|
||||||
|
|
||||||
self:draw_overlay()
|
|
||||||
self:draw_background(style.nagbar)
|
|
||||||
|
|
||||||
local ox, oy = self:get_content_offset()
|
|
||||||
ox = ox + style.padding.x
|
|
||||||
|
|
||||||
-- if there are other items, show it
|
|
||||||
if #self.queue > 0 then
|
|
||||||
local str = string.format("[%d]", #self.queue)
|
|
||||||
ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.size.y)
|
|
||||||
ox = ox + style.padding.x
|
|
||||||
end
|
|
||||||
|
|
||||||
-- draw message
|
|
||||||
local lh = style.font:get_height() * config.line_height
|
|
||||||
oy = oy + style.padding.y + (self.target_height - self:get_message_height()) / 2
|
|
||||||
for msg_line in self.message:gmatch("(.-)\n") do
|
|
||||||
local ty = oy + self:get_line_text_y_offset()
|
|
||||||
renderer.draw_text(style.font, msg_line, ox, ty, style.nagbar_text)
|
|
||||||
oy = oy + lh
|
|
||||||
end
|
|
||||||
|
|
||||||
-- draw buttons
|
|
||||||
for i, opt, bx,by,bw,bh in self:each_option() do
|
|
||||||
local fw,fh = bw - 2 * BORDER_WIDTH, bh - 2 * BORDER_WIDTH
|
|
||||||
local fx,fy = bx + BORDER_WIDTH, by + BORDER_WIDTH
|
|
||||||
|
|
||||||
-- draw the button
|
|
||||||
renderer.draw_rect(bx,by,bw,bh, style.nagbar_text)
|
|
||||||
renderer.draw_rect(fx,fy,fw,fh, style.nagbar)
|
|
||||||
|
|
||||||
if i == self.hovered_item then -- draw underline
|
|
||||||
local uw = fw - 2 * UNDERLINE_MARGIN
|
|
||||||
local halfuw = uw / 2
|
|
||||||
local lx = fx + UNDERLINE_MARGIN + halfuw - (halfuw * self.underline_progress)
|
|
||||||
local ly = fy + fh - UNDERLINE_MARGIN - UNDERLINE_WIDTH
|
|
||||||
uw = uw * self.underline_progress
|
|
||||||
renderer.draw_rect(lx,ly,uw,UNDERLINE_WIDTH, style.nagbar_text)
|
|
||||||
end
|
|
||||||
|
|
||||||
common.draw_text(opt.font, style.nagbar_text, opt.text, "center", fx,fy,fw,fh)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:get_message_height()
|
|
||||||
local h = 0
|
|
||||||
for str in string.gmatch(self.message, "(.-)\n") do
|
|
||||||
h = h + style.font:get_height() * config.line_height
|
|
||||||
end
|
|
||||||
return h
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function NagView:next()
|
|
||||||
local opts = table.remove(self.queue, 1) or {}
|
|
||||||
self.title = opts.title
|
|
||||||
self.message = opts.message and opts.message .. "\n"
|
|
||||||
self.options = opts.options
|
|
||||||
self.on_selected = opts.on_selected
|
|
||||||
if self.message and self.options then
|
|
||||||
local message_height = self:get_message_height()
|
|
||||||
-- self.target_height is the nagview height needed to display the message and
|
|
||||||
-- the buttons, excluding the top and bottom padding space.
|
|
||||||
self.target_height = math.max(message_height, self:get_buttons_height())
|
|
||||||
self:change_hovered(common.find_index(self.options, "default_yes"))
|
|
||||||
end
|
|
||||||
self.force_focus = self.message ~= nil
|
|
||||||
core.set_active_view(self.message ~= nil and self or
|
|
||||||
core.next_active_view or core.last_active_view)
|
|
||||||
end
|
|
||||||
|
|
||||||
function NagView:show(title, message, options, on_select)
|
|
||||||
local opts = {}
|
|
||||||
opts.title = assert(title, "No title")
|
|
||||||
opts.message = assert(message, "No message")
|
|
||||||
opts.options = assert(options, "No options")
|
|
||||||
opts.on_selected = on_select or noop
|
|
||||||
table.insert(self.queue, opts)
|
|
||||||
if #self.queue > 0 and not self.title then self:next() end
|
|
||||||
end
|
|
||||||
|
|
||||||
return NagView
|
|
|
@ -1,737 +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"
|
|
||||||
|
|
||||||
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: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")
|
|
||||||
self.active_view = view
|
|
||||||
core.set_active_view(view)
|
|
||||||
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.y
|
|
||||||
return x + w - pad - cw, 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))
|
|
||||||
self:move_towards("tab_width", tab_width)
|
|
||||||
else
|
|
||||||
self.a:update()
|
|
||||||
self.b:update()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
|
||||||
local ds = style.divider_size
|
|
||||||
local dots_width = style.font:get_width("…")
|
|
||||||
local color = style.dim
|
|
||||||
local padding_y = style.padding.y
|
|
||||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
|
||||||
if standalone then
|
|
||||||
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
|
|
||||||
end
|
|
||||||
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
|
|
||||||
local cx, cw, cspace = close_button_location(x, w)
|
|
||||||
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
|
|
||||||
if show_close_button then
|
|
||||||
local close_style = is_close_hovered and style.text or style.dim
|
|
||||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
|
||||||
end
|
|
||||||
if is_hovered then
|
|
||||||
color = style.text
|
|
||||||
end
|
|
||||||
local padx = style.padding.x
|
|
||||||
-- Normally we should substract "cspace" from text_avail_width and from the
|
|
||||||
-- clipping width. It is the padding space we give to the left and right of the
|
|
||||||
-- close button. However, since we are using dots to terminate filenames, we
|
|
||||||
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
|
||||||
-- close button.
|
|
||||||
local text_avail_width = cx - x - padx
|
|
||||||
core.push_clip_rect(x, y, cx - x, h)
|
|
||||||
x, w = x + padx, w - padx * 2
|
|
||||||
local align = "center"
|
|
||||||
if style.font:get_width(text) > text_avail_width then
|
|
||||||
align = "left"
|
|
||||||
for i = 1, #text do
|
|
||||||
local reduced_text = text:sub(1, #text - i)
|
|
||||||
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
|
||||||
text = reduced_text .. "…"
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
common.draw_text(style.font, color, text, align, x, y, w, h)
|
|
||||||
core.pop_clip_rect()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Node:draw_tabs()
|
|
||||||
local 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:get_name(), 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
|
|
|
@ -20,6 +20,17 @@ function Object:extend()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Object:implement(...)
|
||||||
|
for _, cls in pairs({...}) do
|
||||||
|
for k, v in pairs(cls) do
|
||||||
|
if self[k] == nil and type(v) == "function" then
|
||||||
|
self[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Object:is(T)
|
function Object:is(T)
|
||||||
local mt = getmetatable(self)
|
local mt = getmetatable(self)
|
||||||
while mt do
|
while mt do
|
||||||
|
|
|
@ -1,70 +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 s, e = regex.cmatch(pattern, string, offset or 1, options or 0)
|
|
||||||
return s, e and e - 1
|
|
||||||
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
|
|
||||||
|
|
|
@ -1,11 +1,423 @@
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local style = require "core.style"
|
local style = require "core.style"
|
||||||
local Node = require "core.node"
|
local keymap = require "core.keymap"
|
||||||
|
local Object = require "core.object"
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
|
local CommandView = require "core.commandview"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
|
|
||||||
|
|
||||||
|
local EmptyView = View:extend()
|
||||||
|
|
||||||
|
local function draw_text(x, y, color)
|
||||||
|
local th = style.big_font:get_height()
|
||||||
|
local dh = th + style.padding.y * 2
|
||||||
|
x = renderer.draw_text(style.big_font, "lite", x, y + (dh - th) / 2, 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:propagate(fn, ...)
|
||||||
|
self.a[fn](self.a, ...)
|
||||||
|
self.b[fn](self.b, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:on_mouse_moved(x, y, ...)
|
||||||
|
self.hovered_tab = self:get_tab_overlapping_point(x, y)
|
||||||
|
if self.type == "leaf" then
|
||||||
|
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: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" }
|
||||||
|
|
||||||
|
function Node:split(dir, view, locked)
|
||||||
|
assert(self.type == "leaf", "Tried to split non-leaf node")
|
||||||
|
local type = assert(type_map[dir], "Invalid direction")
|
||||||
|
local last_active = core.active_view
|
||||||
|
local child = Node()
|
||||||
|
child:consume(self)
|
||||||
|
self:consume(Node(type))
|
||||||
|
self.a = child
|
||||||
|
self.b = Node()
|
||||||
|
if view then self.b:add_view(view) end
|
||||||
|
if locked then
|
||||||
|
self.b.locked = locked
|
||||||
|
core.set_active_view(last_active)
|
||||||
|
end
|
||||||
|
if dir == "up" or dir == "left" then
|
||||||
|
self.a, self.b = self.b, self.a
|
||||||
|
end
|
||||||
|
return child
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:close_active_view(root)
|
||||||
|
local do_close = function()
|
||||||
|
if #self.views > 1 then
|
||||||
|
local idx = self:get_view_idx(self.active_view)
|
||||||
|
table.remove(self.views, idx)
|
||||||
|
self:set_active_view(self.views[idx] or self.views[#self.views])
|
||||||
|
else
|
||||||
|
local parent = self:get_parent_node(root)
|
||||||
|
local is_a = (parent.a == self)
|
||||||
|
local other = parent[is_a and "b" or "a"]
|
||||||
|
if other:get_locked_size() then
|
||||||
|
self.views = {}
|
||||||
|
self:add_view(EmptyView())
|
||||||
|
else
|
||||||
|
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
|
||||||
|
p.is_primary_node = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
core.last_active_view = nil
|
||||||
|
end
|
||||||
|
self.active_view:try_close(do_close)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:add_view(view)
|
||||||
|
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, 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")
|
||||||
|
self.active_view = view
|
||||||
|
core.set_active_view(view)
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_divider_overlapping_point(px, py)
|
||||||
|
if self.type ~= "leaf" then
|
||||||
|
local p = 6
|
||||||
|
local x, y, w, h = self:get_divider_rect()
|
||||||
|
x, y = x - p, y - p
|
||||||
|
w, h = w + p * 2, h + p * 2
|
||||||
|
if px > x and py > y and px < x + w and py < y + h then
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
return self.a:get_divider_overlapping_point(px, py)
|
||||||
|
or self.b:get_divider_overlapping_point(px, py)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_tab_overlapping_point(px, py)
|
||||||
|
if #self.views == 1 then return nil end
|
||||||
|
local x, y, w, h = self:get_tab_rect(1)
|
||||||
|
if px >= x and py >= y and px < x + w * #self.views and py < y + h then
|
||||||
|
return math.floor((px - x) / w) + 1
|
||||||
|
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_tab_rect(idx)
|
||||||
|
local tw = math.min(style.tab_width, math.ceil(self.size.x / #self.views))
|
||||||
|
local h = style.font:get_height() + style.padding.y * 2
|
||||||
|
return self.position.x + (idx-1) * tw, self.position.y, tw, 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
|
||||||
|
|
||||||
|
|
||||||
|
function Node:get_locked_size()
|
||||||
|
if self.type == "leaf" then
|
||||||
|
if self.locked then
|
||||||
|
local size = self.active_view.size
|
||||||
|
return size.x, size.y
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local x1, y1 = self.a:get_locked_size()
|
||||||
|
local x2, y2 = self.b:get_locked_size()
|
||||||
|
if x1 and x2 then
|
||||||
|
local dsx = (x1 < 1 or x2 < 1) and 0 or style.divider_size
|
||||||
|
local dsy = (y1 < 1 or y2 < 1) and 0 or style.divider_size
|
||||||
|
return x1 + x2 + dsx, y1 + y2 + dsy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function 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)
|
||||||
|
local n
|
||||||
|
local ds = (x1 and x1 < 1 or x2 and x2 < 1) and 0 or style.divider_size
|
||||||
|
if x1 then
|
||||||
|
n = x1 + ds
|
||||||
|
elseif x2 then
|
||||||
|
n = self.size[x] - x2
|
||||||
|
else
|
||||||
|
n = math.floor(self.size[x] * self.divider)
|
||||||
|
end
|
||||||
|
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.views > 1 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
|
||||||
|
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:update()
|
||||||
|
if self.type == "leaf" then
|
||||||
|
for _, view in ipairs(self.views) do
|
||||||
|
view:update()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.a:update()
|
||||||
|
self.b:update()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:draw_tabs()
|
||||||
|
local x, y, _, h = self:get_tab_rect(1)
|
||||||
|
local ds = style.divider_size
|
||||||
|
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)
|
||||||
|
|
||||||
|
for i, view in ipairs(self.views) do
|
||||||
|
local x, y, w, h = self:get_tab_rect(i)
|
||||||
|
local text = view:get_name()
|
||||||
|
local color = style.dim
|
||||||
|
if view == self.active_view 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
|
||||||
|
if i == self.hovered_tab then
|
||||||
|
color = style.text
|
||||||
|
end
|
||||||
|
core.push_clip_rect(x, y, w, h)
|
||||||
|
x, w = x + style.padding.x, w - style.padding.x * 2
|
||||||
|
local align = style.font:get_width(text) > w and "left" or "center"
|
||||||
|
common.draw_text(style.font, color, text, align, x, y, w, h)
|
||||||
|
core.pop_clip_rect()
|
||||||
|
end
|
||||||
|
|
||||||
|
core.pop_clip_rect()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:draw()
|
||||||
|
if self.type == "leaf" then
|
||||||
|
if #self.views > 1 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 + pos.x % 1, size.y + pos.y % 1)
|
||||||
|
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
|
||||||
|
else
|
||||||
|
return self.a:is_empty() and self.b:is_empty()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Node:close_all_docviews()
|
||||||
|
if self.type == "leaf" then
|
||||||
|
local i = 1
|
||||||
|
while i <= #self.views do
|
||||||
|
local view = self.views[i]
|
||||||
|
if view:is(DocView) and not view:is(CommandView) then
|
||||||
|
table.remove(self.views, i)
|
||||||
|
else
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #self.views == 0 and self.is_primary_node then
|
||||||
|
self:add_view(EmptyView())
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.a:close_all_docviews()
|
||||||
|
self.b:close_all_docviews()
|
||||||
|
if self.a:is_empty() then
|
||||||
|
self:consume(self.b)
|
||||||
|
elseif self.b:is_empty() then
|
||||||
|
self:consume(self.a)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local RootView = View:extend()
|
local RootView = View:extend()
|
||||||
|
|
||||||
function RootView:new()
|
function RootView:new()
|
||||||
|
@ -13,14 +425,6 @@ function RootView:new()
|
||||||
self.root_node = Node()
|
self.root_node = Node()
|
||||||
self.deferred_draws = {}
|
self.deferred_draws = {}
|
||||||
self.mouse = { x = 0, y = 0 }
|
self.mouse = { x = 0, y = 0 }
|
||||||
self.drag_overlay = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0,
|
|
||||||
base_color = style.drag_overlay,
|
|
||||||
color = { table.unpack(style.drag_overlay) } }
|
|
||||||
self.drag_overlay.to = { x = 0, y = 0, w = 0, h = 0 }
|
|
||||||
self.drag_overlay_tab = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0,
|
|
||||||
base_color = style.drag_overlay_tab,
|
|
||||||
color = { table.unpack(style.drag_overlay_tab) } }
|
|
||||||
self.drag_overlay_tab.to = { x = 0, y = 0, w = 0, h = 0 }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,7 +437,6 @@ function RootView:get_active_node()
|
||||||
return self.root_node:get_node_for_view(core.active_view)
|
return self.root_node:get_node_for_view(core.active_view)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function get_primary_node(node)
|
local function get_primary_node(node)
|
||||||
if node.is_primary_node then
|
if node.is_primary_node then
|
||||||
return node
|
return node
|
||||||
|
@ -43,44 +446,18 @@ local function get_primary_node(node)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function RootView:get_primary_node()
|
||||||
|
return get_primary_node(self.root_node)
|
||||||
|
end
|
||||||
|
|
||||||
function RootView:get_active_node_default()
|
function RootView:open_doc(doc)
|
||||||
local node = self.root_node:get_node_for_view(core.active_view)
|
local node = self:get_active_node()
|
||||||
if node.locked then
|
if node.locked then
|
||||||
local default_view = self:get_primary_node().views[1]
|
local default_view = self:get_primary_node().views[1]
|
||||||
assert(default_view, "internal error: cannot find original document node.")
|
assert(default_view, "internal error: cannot find original document node.")
|
||||||
core.set_active_view(default_view)
|
core.set_active_view(default_view)
|
||||||
node = self:get_active_node()
|
node = self:get_active_node()
|
||||||
end
|
end
|
||||||
return node
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function RootView:get_primary_node()
|
|
||||||
return get_primary_node(self.root_node)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function select_next_primary_node(node)
|
|
||||||
if node.is_primary_node then return end
|
|
||||||
if node.type ~= "leaf" then
|
|
||||||
return select_next_primary_node(node.a) or select_next_primary_node(node.b)
|
|
||||||
else
|
|
||||||
local lx, ly = node:get_locked_size()
|
|
||||||
if not lx and not ly then
|
|
||||||
return node
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function RootView:select_next_primary_node()
|
|
||||||
return select_next_primary_node(self.root_node)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function RootView:open_doc(doc)
|
|
||||||
local node = self:get_active_node_default()
|
|
||||||
for i, view in ipairs(node.views) do
|
for i, view in ipairs(node.views) do
|
||||||
if view.doc == doc then
|
if view.doc == doc then
|
||||||
node:set_active_view(node.views[i])
|
node:set_active_view(node.views[i])
|
||||||
|
@ -95,176 +472,62 @@ function RootView:open_doc(doc)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function RootView:close_all_docviews(keep_active)
|
function RootView:close_all_docviews()
|
||||||
self.root_node:close_all_docviews(keep_active)
|
self.root_node:close_all_docviews()
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- Function to intercept mouse pressed events on the active view.
|
|
||||||
-- Do nothing by default.
|
|
||||||
function RootView.on_view_mouse_pressed(button, x, y, clicks)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function RootView:on_mouse_pressed(button, x, y, clicks)
|
function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
if div then
|
||||||
if div and (node and not node.active_view:scrollbar_overlaps_point(x, y)) then
|
|
||||||
self.dragged_divider = div
|
self.dragged_divider = div
|
||||||
return true
|
return
|
||||||
end
|
|
||||||
if node.hovered_scroll_button > 0 then
|
|
||||||
node:scroll_tabs(node.hovered_scroll_button)
|
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
|
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||||
local idx = node:get_tab_overlapping_point(x, y)
|
local idx = node:get_tab_overlapping_point(x, y)
|
||||||
if idx then
|
if idx then
|
||||||
if button == "middle" or node.hovered_close == idx then
|
node:set_active_view(node.views[idx])
|
||||||
node:close_view(self.root_node, node.views[idx])
|
if button == "middle" then
|
||||||
return true
|
node:close_active_view(self.root_node)
|
||||||
else
|
|
||||||
if button == "left" then
|
|
||||||
self.dragged_node = { node = node, idx = idx, dragging = false, drag_start_x = x, drag_start_y = y}
|
|
||||||
end
|
|
||||||
node:set_active_view(node.views[idx])
|
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
elseif not self.dragged_node then -- avoid sending on_mouse_pressed events when dragging tabs
|
|
||||||
core.set_active_view(node.active_view)
|
|
||||||
return self.on_view_mouse_pressed(button, x, y, clicks) or node.active_view:on_mouse_pressed(button, x, y, clicks)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function RootView:get_overlay_base_color(overlay)
|
|
||||||
if overlay == self.drag_overlay then
|
|
||||||
return style.drag_overlay
|
|
||||||
else
|
else
|
||||||
return style.drag_overlay_tab
|
core.set_active_view(node.active_view)
|
||||||
|
node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function RootView:set_show_overlay(overlay, status)
|
function RootView:on_mouse_released(...)
|
||||||
overlay.visible = status
|
|
||||||
if status then -- reset colors
|
|
||||||
-- reload base_color
|
|
||||||
overlay.base_color = self:get_overlay_base_color(overlay)
|
|
||||||
overlay.color[1] = overlay.base_color[1]
|
|
||||||
overlay.color[2] = overlay.base_color[2]
|
|
||||||
overlay.color[3] = overlay.base_color[3]
|
|
||||||
overlay.color[4] = overlay.base_color[4]
|
|
||||||
overlay.opacity = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function RootView:on_mouse_released(button, x, y, ...)
|
|
||||||
if self.dragged_divider then
|
if self.dragged_divider then
|
||||||
self.dragged_divider = nil
|
self.dragged_divider = nil
|
||||||
end
|
end
|
||||||
if self.dragged_node then
|
self.root_node:on_mouse_released(...)
|
||||||
if button == "left" then
|
|
||||||
if self.dragged_node.dragging then
|
|
||||||
local node = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y)
|
|
||||||
local dragged_node = self.dragged_node.node
|
|
||||||
|
|
||||||
if node and not node.locked
|
|
||||||
-- don't do anything if dragging onto own node, with only one view
|
|
||||||
and (node ~= dragged_node or #node.views > 1) then
|
|
||||||
local split_type = node:get_split_type(self.mouse.x, self.mouse.y)
|
|
||||||
local view = dragged_node.views[self.dragged_node.idx]
|
|
||||||
|
|
||||||
if split_type ~= "middle" and split_type ~= "tab" then -- needs splitting
|
|
||||||
local new_node = node:split(split_type)
|
|
||||||
self.root_node:get_node_for_view(view):remove_view(self.root_node, view)
|
|
||||||
new_node:add_view(view)
|
|
||||||
elseif split_type == "middle" and node ~= dragged_node then -- move to other node
|
|
||||||
dragged_node:remove_view(self.root_node, view)
|
|
||||||
node:add_view(view)
|
|
||||||
self.root_node:get_node_for_view(view):set_active_view(view)
|
|
||||||
elseif split_type == "tab" then -- move besides other tabs
|
|
||||||
local tab_index = node:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y, dragged_node, self.dragged_node.idx)
|
|
||||||
dragged_node:remove_view(self.root_node, view)
|
|
||||||
node:add_view(view, tab_index)
|
|
||||||
self.root_node:get_node_for_view(view):set_active_view(view)
|
|
||||||
end
|
|
||||||
self.root_node:update_layout()
|
|
||||||
core.redraw = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:set_show_overlay(self.drag_overlay, false)
|
|
||||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
|
||||||
if self.dragged_node and self.dragged_node.dragging then
|
|
||||||
core.request_cursor("arrow")
|
|
||||||
end
|
|
||||||
self.dragged_node = nil
|
|
||||||
end
|
|
||||||
else -- avoid sending on_mouse_released events when dragging tabs
|
|
||||||
self.root_node:on_mouse_released(button, x, y, ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function resize_child_node(node, axis, value, delta)
|
|
||||||
local accept_resize = node.a:resize(axis, value)
|
|
||||||
if not accept_resize then
|
|
||||||
accept_resize = node.b:resize(axis, node.size[axis] - value)
|
|
||||||
end
|
|
||||||
if not accept_resize then
|
|
||||||
node.divider = node.divider + delta / node.size[axis]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function RootView:on_mouse_moved(x, y, dx, dy)
|
function RootView:on_mouse_moved(x, y, dx, dy)
|
||||||
if core.active_view == core.nag_view then
|
|
||||||
core.request_cursor("arrow")
|
|
||||||
core.active_view:on_mouse_moved(x, y, dx, dy)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.dragged_divider then
|
if self.dragged_divider then
|
||||||
local node = self.dragged_divider
|
local node = self.dragged_divider
|
||||||
if node.type == "hsplit" then
|
if node.type == "hsplit" then
|
||||||
x = common.clamp(x, 0, self.root_node.size.x * 0.95)
|
node.divider = node.divider + dx / node.size.x
|
||||||
resize_child_node(node, "x", x, dx)
|
else
|
||||||
elseif node.type == "vsplit" then
|
node.divider = node.divider + dy / node.size.y
|
||||||
y = common.clamp(y, 0, self.root_node.size.y * 0.95)
|
|
||||||
resize_child_node(node, "y", y, dy)
|
|
||||||
end
|
end
|
||||||
node.divider = common.clamp(node.divider, 0.01, 0.99)
|
node.divider = common.clamp(node.divider, 0.01, 0.99)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
self.mouse.x, self.mouse.y = x, y
|
self.mouse.x, self.mouse.y = x, y
|
||||||
|
|
||||||
local dn = self.dragged_node
|
|
||||||
if dn and not dn.dragging then
|
|
||||||
-- start dragging only after enough movement
|
|
||||||
dn.dragging = common.distance(x, y, dn.drag_start_x, dn.drag_start_y) > style.tab_width * .05
|
|
||||||
if dn.dragging then
|
|
||||||
core.request_cursor("hand")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- avoid sending on_mouse_moved events when dragging tabs
|
|
||||||
if dn then return end
|
|
||||||
|
|
||||||
self.root_node:on_mouse_moved(x, y, dx, dy)
|
self.root_node:on_mouse_moved(x, y, dx, dy)
|
||||||
|
|
||||||
self.overlapping_node = self.root_node:get_child_overlapping_point(x, y)
|
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||||
|
|
||||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||||
local tab_index = self.overlapping_node and self.overlapping_node:get_tab_overlapping_point(x, y)
|
if div then
|
||||||
if self.overlapping_node and self.overlapping_node:get_scroll_button_index(x, y) then
|
system.set_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||||
core.request_cursor("arrow")
|
elseif node:get_tab_overlapping_point(x, y) then
|
||||||
elseif div and (self.overlapping_node and not self.overlapping_node.active_view:scrollbar_overlaps_point(x, y)) then
|
system.set_cursor("arrow")
|
||||||
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
else
|
||||||
elseif tab_index then
|
system.set_cursor(node.active_view.cursor)
|
||||||
core.request_cursor("arrow")
|
|
||||||
elseif self.overlapping_node then
|
|
||||||
core.request_cursor(self.overlapping_node.active_view.cursor)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -272,7 +535,7 @@ end
|
||||||
function RootView:on_mouse_wheel(...)
|
function RootView:on_mouse_wheel(...)
|
||||||
local x, y = self.mouse.x, self.mouse.y
|
local x, y = self.mouse.x, self.mouse.y
|
||||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||||
return node.active_view:on_mouse_wheel(...)
|
node.active_view:on_mouse_wheel(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -286,110 +549,10 @@ function RootView:on_focus_lost(...)
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function RootView:interpolate_drag_overlay(overlay)
|
|
||||||
self:move_towards(overlay, "x", overlay.to.x)
|
|
||||||
self:move_towards(overlay, "y", overlay.to.y)
|
|
||||||
self:move_towards(overlay, "w", overlay.to.w)
|
|
||||||
self:move_towards(overlay, "h", overlay.to.h)
|
|
||||||
|
|
||||||
self:move_towards(overlay, "opacity", overlay.visible and 100 or 0)
|
|
||||||
overlay.color[4] = overlay.base_color[4] * overlay.opacity / 100
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function RootView:update()
|
function RootView:update()
|
||||||
Node.copy_position_and_size(self.root_node, self)
|
copy_position_and_size(self.root_node, self)
|
||||||
self.root_node:update()
|
self.root_node:update()
|
||||||
self.root_node:update_layout()
|
self.root_node:update_layout()
|
||||||
|
|
||||||
self:update_drag_overlay()
|
|
||||||
self:interpolate_drag_overlay(self.drag_overlay)
|
|
||||||
self:interpolate_drag_overlay(self.drag_overlay_tab)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function RootView:set_drag_overlay(overlay, x, y, w, h, immediate)
|
|
||||||
overlay.to.x = x
|
|
||||||
overlay.to.y = y
|
|
||||||
overlay.to.w = w
|
|
||||||
overlay.to.h = h
|
|
||||||
if immediate then
|
|
||||||
overlay.x = x
|
|
||||||
overlay.y = y
|
|
||||||
overlay.w = w
|
|
||||||
overlay.h = h
|
|
||||||
end
|
|
||||||
if not overlay.visible then
|
|
||||||
self:set_show_overlay(overlay, true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function get_split_sizes(split_type, x, y, w, h)
|
|
||||||
if split_type == "left" then
|
|
||||||
w = w * .5
|
|
||||||
elseif split_type == "right" then
|
|
||||||
x = x + w * .5
|
|
||||||
w = w * .5
|
|
||||||
elseif split_type == "up" then
|
|
||||||
h = h * .5
|
|
||||||
elseif split_type == "down" then
|
|
||||||
y = y + h * .5
|
|
||||||
h = h * .5
|
|
||||||
end
|
|
||||||
return x, y, w, h
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function RootView:update_drag_overlay()
|
|
||||||
if not (self.dragged_node and self.dragged_node.dragging) then return end
|
|
||||||
local over = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y)
|
|
||||||
if over and not over.locked then
|
|
||||||
local _, _, _, tab_h = over:get_scroll_button_rect(1)
|
|
||||||
local x, y = over.position.x, over.position.y
|
|
||||||
local w, h = over.size.x, over.size.y
|
|
||||||
local split_type = over:get_split_type(self.mouse.x, self.mouse.y)
|
|
||||||
|
|
||||||
if split_type == "tab" and (over ~= self.dragged_node.node or #over.views > 1) then
|
|
||||||
local tab_index, tab_x, tab_y, tab_w, tab_h = over:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y)
|
|
||||||
self:set_drag_overlay(self.drag_overlay_tab,
|
|
||||||
tab_x + (tab_index and 0 or tab_w), tab_y,
|
|
||||||
style.caret_width, tab_h,
|
|
||||||
-- avoid showing tab overlay moving between nodes
|
|
||||||
over ~= self.drag_overlay_tab.last_over)
|
|
||||||
self:set_show_overlay(self.drag_overlay, false)
|
|
||||||
self.drag_overlay_tab.last_over = over
|
|
||||||
else
|
|
||||||
if (over ~= self.dragged_node.node or #over.views > 1) then
|
|
||||||
y = y + tab_h
|
|
||||||
h = h - tab_h
|
|
||||||
x, y, w, h = get_split_sizes(split_type, x, y, w, h)
|
|
||||||
end
|
|
||||||
self:set_drag_overlay(self.drag_overlay, x, y, w, h)
|
|
||||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self:set_show_overlay(self.drag_overlay, false)
|
|
||||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function RootView:draw_grabbed_tab()
|
|
||||||
local dn = self.dragged_node
|
|
||||||
local _,_, w, h = dn.node:get_tab_rect(dn.idx)
|
|
||||||
local x = self.mouse.x - w / 2
|
|
||||||
local y = self.mouse.y - h / 2
|
|
||||||
local text = dn.node.views[dn.idx] and dn.node.views[dn.idx]:get_name() or ""
|
|
||||||
self.root_node:draw_tab(text, true, true, false, x, y, w, h, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function RootView:draw_drag_overlay(ov)
|
|
||||||
if ov.opacity > 0 then
|
|
||||||
renderer.draw_rect(ov.x, ov.y, ov.w, ov.h, ov.color)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -399,16 +562,6 @@ function RootView:draw()
|
||||||
local t = table.remove(self.deferred_draws)
|
local t = table.remove(self.deferred_draws)
|
||||||
t.fn(table.unpack(t))
|
t.fn(table.unpack(t))
|
||||||
end
|
end
|
||||||
|
|
||||||
self:draw_drag_overlay(self.drag_overlay)
|
|
||||||
self:draw_drag_overlay(self.drag_overlay_tab)
|
|
||||||
if self.dragged_node and self.dragged_node.dragging then
|
|
||||||
self:draw_grabbed_tab()
|
|
||||||
end
|
|
||||||
if core.cursor_change_req then
|
|
||||||
system.set_cursor(core.cursor_change_req)
|
|
||||||
core.cursor_change_req = nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
-- this file is used by lite-xl to setup the Lua environment when starting
|
|
||||||
VERSION = "2.0.3r1"
|
|
||||||
MOD_VERSION = "2"
|
|
||||||
|
|
||||||
SCALE = tonumber(os.getenv("LITE_SCALE") or os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or SCALE
|
|
||||||
PATHSEP = package.config:sub(1, 1)
|
|
||||||
|
|
||||||
EXEDIR = EXEFILE:match("^(.+)[/\\][^/\\]+$")
|
|
||||||
if MACOS_RESOURCES then
|
|
||||||
DATADIR = MACOS_RESOURCES
|
|
||||||
else
|
|
||||||
local prefix = EXEDIR:match("^(.+)[/\\]bin$")
|
|
||||||
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
|
|
||||||
end
|
|
||||||
USERDIR = (os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl")
|
|
||||||
or (HOME and (HOME .. '/.config/lite-xl') or (EXEDIR .. '/user'))
|
|
||||||
|
|
||||||
package.path = DATADIR .. '/?.lua;' .. package.path
|
|
||||||
package.path = DATADIR .. '/?/init.lua;' .. package.path
|
|
||||||
package.path = USERDIR .. '/?.lua;' .. package.path
|
|
||||||
package.path = USERDIR .. '/?/init.lua;' .. package.path
|
|
||||||
|
|
||||||
local dynamic_suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so')
|
|
||||||
package.cpath = DATADIR .. '/?.' .. dynamic_suffix .. ";" .. USERDIR .. '/?.' .. dynamic_suffix
|
|
||||||
package.native_plugins = {}
|
|
||||||
package.searchers = { package.searchers[1], package.searchers[2], function(modname)
|
|
||||||
local path = 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
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ local style = require "core.style"
|
||||||
local DocView = require "core.docview"
|
local DocView = require "core.docview"
|
||||||
local LogView = require "core.logview"
|
local LogView = require "core.logview"
|
||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
local Object = require "core.object"
|
|
||||||
|
|
||||||
|
|
||||||
local StatusView = View:extend()
|
local StatusView = View:extend()
|
||||||
|
@ -19,8 +18,6 @@ function StatusView:new()
|
||||||
StatusView.super.new(self)
|
StatusView.super.new(self)
|
||||||
self.message_timeout = 0
|
self.message_timeout = 0
|
||||||
self.message = {}
|
self.message = {}
|
||||||
self.tooltip_mode = false
|
|
||||||
self.tooltip = {}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,7 +27,6 @@ function StatusView:on_mouse_pressed()
|
||||||
and not core.active_view:is(LogView) then
|
and not core.active_view:is(LogView) then
|
||||||
command.perform "core:open-log"
|
command.perform "core:open-log"
|
||||||
end
|
end
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,17 +39,6 @@ function StatusView:show_message(icon, icon_color, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function StatusView:show_tooltip(text)
|
|
||||||
self.tooltip = { text }
|
|
||||||
self.tooltip_mode = true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function StatusView:remove_tooltip()
|
|
||||||
self.tooltip_mode = false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function StatusView:update()
|
function StatusView:update()
|
||||||
self.size.y = style.font:get_height() + style.padding.y * 2
|
self.size.y = style.font:get_height() + style.padding.y * 2
|
||||||
|
|
||||||
|
@ -72,7 +57,7 @@ local function draw_items(self, items, x, y, draw_fn)
|
||||||
local color = style.text
|
local color = style.text
|
||||||
|
|
||||||
for _, item in ipairs(items) do
|
for _, item in ipairs(items) do
|
||||||
if Object.is(item, renderer.font) then
|
if type(item) == "userdata" then
|
||||||
font = item
|
font = item
|
||||||
elseif type(item) == "table" then
|
elseif type(item) == "table" then
|
||||||
color = item
|
color = item
|
||||||
|
@ -109,9 +94,6 @@ function StatusView:get_items()
|
||||||
local dv = core.active_view
|
local dv = core.active_view
|
||||||
local line, col = dv.doc:get_selection()
|
local line, col = dv.doc:get_selection()
|
||||||
local dirty = dv.doc:is_dirty()
|
local dirty = dv.doc:is_dirty()
|
||||||
local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info()
|
|
||||||
local indent_label = (indent_type == "hard") and "tabs: " or "spaces: "
|
|
||||||
local indent_size_str = tostring(indent_size) .. (indent_confirmed and "" or "*") or "unknown"
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dirty and style.accent or style.text, style.icon_font, "f",
|
dirty and style.accent or style.text, style.icon_font, "f",
|
||||||
|
@ -126,8 +108,6 @@ function StatusView:get_items()
|
||||||
self.separator,
|
self.separator,
|
||||||
string.format("%d%%", line / #dv.doc.lines * 100),
|
string.format("%d%%", line / #dv.doc.lines * 100),
|
||||||
}, {
|
}, {
|
||||||
style.text, indent_label, indent_size,
|
|
||||||
style.dim, self.separator2, style.text,
|
|
||||||
style.icon_font, "g",
|
style.icon_font, "g",
|
||||||
style.font, style.dim, self.separator2, style.text,
|
style.font, style.dim, self.separator2, style.text,
|
||||||
#dv.doc.lines, " lines",
|
#dv.doc.lines, " lines",
|
||||||
|
@ -152,13 +132,9 @@ function StatusView:draw()
|
||||||
self:draw_items(self.message, false, self.size.y)
|
self:draw_items(self.message, false, self.size.y)
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.tooltip_mode then
|
local left, right = self:get_items()
|
||||||
self:draw_items(self.tooltip)
|
self:draw_items(left)
|
||||||
else
|
self:draw_items(right, true)
|
||||||
local left, right = self:get_items()
|
|
||||||
self:draw_items(left)
|
|
||||||
self:draw_items(right, true)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,55 +21,36 @@ 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/font.ttf", 14 * SCALE)
|
||||||
style.big_font = style.font:copy(46 * SCALE)
|
style.big_font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 34 * 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.code_font = renderer.font.load(DATADIR .. "/fonts/monospace.ttf", 13.5 * SCALE)
|
||||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 15 * SCALE)
|
|
||||||
|
|
||||||
style.background = { common.color "#2e2e32" } -- Docview
|
style.background = { common.color "#2e2e32" }
|
||||||
style.background2 = { common.color "#252529" } -- Treeview
|
style.background2 = { common.color "#252529" }
|
||||||
style.background3 = { common.color "#252529" } -- Command view
|
style.background3 = { common.color "#252529" }
|
||||||
style.text = { common.color "#97979c" }
|
style.text = { common.color "#97979c" }
|
||||||
style.caret = { common.color "#93DDFA" }
|
style.caret = { common.color "#93DDFA" }
|
||||||
style.accent = { common.color "#e1e1e6" }
|
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.dim = { common.color "#525257" }
|
||||||
style.divider = { common.color "#202024" } -- Line between nodes
|
style.divider = { common.color "#202024" }
|
||||||
style.selection = { common.color "#48484f" }
|
style.selection = { common.color "#48484f" }
|
||||||
style.line_number = { common.color "#525259" }
|
style.line_number = { common.color "#525259" }
|
||||||
style.line_number2 = { common.color "#83838f" } -- With cursor
|
style.line_number2 = { common.color "#83838f" }
|
||||||
style.line_highlight = { common.color "#343438" }
|
style.line_highlight = { common.color "#343438" }
|
||||||
style.scrollbar = { common.color "#414146" }
|
style.scrollbar = { common.color "#414146" }
|
||||||
style.scrollbar2 = { common.color "#4b4b52" } -- Hovered
|
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.drag_overlay = { common.color "rgba(255,255,255,0.1)" }
|
|
||||||
style.drag_overlay_tab = { common.color "#93DDFA" }
|
|
||||||
style.good = { common.color "#72b886" }
|
|
||||||
style.warn = { common.color "#FFA94D" }
|
|
||||||
style.error = { common.color "#FF3333" }
|
|
||||||
style.modified = { common.color "#1c7c9c" }
|
|
||||||
|
|
||||||
style.syntax = {}
|
style.syntax = {}
|
||||||
style.syntax["normal"] = { common.color "#e1e1e6" }
|
style.syntax["normal"] = { common.color "#e1e1e6" }
|
||||||
style.syntax["symbol"] = { common.color "#e1e1e6" }
|
style.syntax["symbol"] = { common.color "#e1e1e6" }
|
||||||
style.syntax["comment"] = { common.color "#676b6f" }
|
style.syntax["comment"] = { common.color "#676b6f" }
|
||||||
style.syntax["keyword"] = { common.color "#E58AC9" } -- local function end if case
|
style.syntax["keyword"] = { common.color "#E58AC9" }
|
||||||
style.syntax["keyword2"] = { common.color "#F77483" } -- self int float
|
style.syntax["keyword2"] = { common.color "#F77483" }
|
||||||
style.syntax["number"] = { common.color "#FFA94D" }
|
style.syntax["number"] = { common.color "#FFA94D" }
|
||||||
style.syntax["literal"] = { common.color "#FFA94D" } -- true false nil
|
style.syntax["literal"] = { common.color "#FFA94D" }
|
||||||
style.syntax["string"] = { common.color "#f7c95c" }
|
style.syntax["string"] = { common.color "#f7c95c" }
|
||||||
style.syntax["operator"] = { common.color "#93DDFA" } -- = + - / < >
|
style.syntax["operator"] = { common.color "#93DDFA" }
|
||||||
style.syntax["function"] = { common.color "#93DDFA" }
|
style.syntax["function"] = { common.color "#93DDFA" }
|
||||||
|
|
||||||
-- This can be used to override fonts per syntax group.
|
|
||||||
-- The syntax highlighter will take existing values from this table and
|
|
||||||
-- override style.code_font on a per-token basis, so you can choose to eg.
|
|
||||||
-- render comments in an italic font if you want to.
|
|
||||||
style.syntax_fonts = {}
|
|
||||||
-- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options)
|
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
|
|
@ -3,7 +3,7 @@ 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)
|
||||||
|
@ -22,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
|
||||||
|
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
local core = require "core"
|
|
||||||
local common = require "core.common"
|
|
||||||
local style = require "core.style"
|
|
||||||
local View = require "core.view"
|
|
||||||
|
|
||||||
local restore_command = {
|
|
||||||
symbol = "w", action = function() system.set_window_mode("normal") end
|
|
||||||
}
|
|
||||||
|
|
||||||
local maximize_command = {
|
|
||||||
symbol = "W", action = function() system.set_window_mode("maximized") end
|
|
||||||
}
|
|
||||||
|
|
||||||
local title_commands = {
|
|
||||||
{symbol = "_", action = function() system.set_window_mode("minimized") end},
|
|
||||||
maximize_command,
|
|
||||||
{symbol = "X", action = function() core.quit() end},
|
|
||||||
}
|
|
||||||
|
|
||||||
local TitleView = View:extend()
|
|
||||||
|
|
||||||
local function title_view_height()
|
|
||||||
return style.font:get_height() + style.padding.y * 2
|
|
||||||
end
|
|
||||||
|
|
||||||
function TitleView:new()
|
|
||||||
TitleView.super.new(self)
|
|
||||||
self.visible = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function TitleView:configure_hit_test(borderless)
|
|
||||||
if borderless then
|
|
||||||
local title_height = title_view_height()
|
|
||||||
local icon_w = style.icon_font:get_width("_")
|
|
||||||
local icon_spacing = icon_w
|
|
||||||
local controls_width = (icon_w + icon_spacing) * #title_commands + icon_spacing
|
|
||||||
system.set_window_hit_test(title_height, controls_width, icon_spacing)
|
|
||||||
-- core.hit_test_title_height = title_height
|
|
||||||
else
|
|
||||||
system.set_window_hit_test()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function TitleView:update()
|
|
||||||
self.size.y = self.visible and title_view_height() or 0
|
|
||||||
title_commands[2] = core.window_mode == "maximized" and restore_command or maximize_command
|
|
||||||
TitleView.super.update(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TitleView:draw_window_title()
|
|
||||||
local h = style.font:get_height()
|
|
||||||
local ox, oy = self:get_content_offset()
|
|
||||||
local color = style.text
|
|
||||||
local x, y = ox + style.padding.x, oy + style.padding.y
|
|
||||||
x = common.draw_text(style.icon_font, color, "M ", nil, x, y, 0, h)
|
|
||||||
local title = core.compose_window_title(core.window_title)
|
|
||||||
common.draw_text(style.font, color, title, nil, x, y, 0, h)
|
|
||||||
end
|
|
||||||
|
|
||||||
function TitleView:each_control_item()
|
|
||||||
local icon_h, icon_w = style.icon_font:get_height(), style.icon_font:get_width("_")
|
|
||||||
local icon_spacing = icon_w
|
|
||||||
local ox, oy = self:get_content_offset()
|
|
||||||
ox = ox + self.size.x
|
|
||||||
local i, n = 0, #title_commands
|
|
||||||
local iter = function()
|
|
||||||
i = i + 1
|
|
||||||
if i <= n then
|
|
||||||
local dx = - (icon_w + icon_spacing) * (n - i + 1)
|
|
||||||
local dy = style.padding.y
|
|
||||||
return title_commands[i], ox + dx, oy + dy, icon_w, icon_h
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return iter
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TitleView:draw_window_controls()
|
|
||||||
for item, x, y, w, h in self:each_control_item() do
|
|
||||||
local color = item == self.hovered_item and style.text or style.dim
|
|
||||||
common.draw_text(style.icon_font, color, item.symbol, nil, x, y, 0, h)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TitleView:on_mouse_pressed(button, x, y, clicks)
|
|
||||||
local caught = TitleView.super.on_mouse_pressed(self, button, x, y, clicks)
|
|
||||||
if caught then return end
|
|
||||||
core.set_active_view(core.last_active_view)
|
|
||||||
if self.hovered_item then
|
|
||||||
self.hovered_item.action()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TitleView:on_mouse_moved(px, py, ...)
|
|
||||||
if self.size.y == 0 then return end
|
|
||||||
TitleView.super.on_mouse_moved(self, px, py, ...)
|
|
||||||
self.hovered_item = nil
|
|
||||||
local x_min, x_max, y_min, y_max = self.size.x, 0, self.size.y, 0
|
|
||||||
for item, x, y, w, h in self:each_control_item() do
|
|
||||||
x_min, x_max = math.min(x, x_min), math.max(x + w, x_max)
|
|
||||||
y_min, y_max = y, y + h
|
|
||||||
if px > x and py > y and px <= x + w and py <= y + h then
|
|
||||||
self.hovered_item = item
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TitleView:draw()
|
|
||||||
self:draw_background(style.background2)
|
|
||||||
self:draw_window_title()
|
|
||||||
self:draw_window_controls()
|
|
||||||
end
|
|
||||||
|
|
||||||
return TitleView
|
|
|
@ -1,8 +1,6 @@
|
||||||
local syntax = require "core.syntax"
|
|
||||||
local common = require "core.common"
|
|
||||||
|
|
||||||
local tokenizer = {}
|
local tokenizer = {}
|
||||||
|
|
||||||
|
|
||||||
local function push_token(t, type, text)
|
local function push_token(t, type, text)
|
||||||
local prev_type = t[#t-1]
|
local prev_type = t[#t-1]
|
||||||
local prev_text = t[#t]
|
local prev_text = t[#t]
|
||||||
|
@ -16,220 +14,72 @@ local function push_token(t, type, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function push_tokens(t, syn, pattern, full_text, find_results)
|
local function is_escaped(text, idx, esc)
|
||||||
if #find_results > 2 then
|
local byte = esc:byte()
|
||||||
-- We do some manipulation with find_results so that it's arranged
|
local count = 0
|
||||||
-- like this:
|
for i = idx - 1, 1, -1 do
|
||||||
-- { start, end, i_1, i_2, i_3, …, i_last }
|
if text:byte(i) ~= byte then break end
|
||||||
-- Each position spans characters from i_n to ((i_n+1) - 1), to form
|
count = count + 1
|
||||||
-- consecutive spans of text.
|
end
|
||||||
--
|
return count % 2 == 1
|
||||||
-- If i_1 is not equal to start, start is automatically inserted at
|
end
|
||||||
-- that index.
|
|
||||||
if find_results[3] ~= find_results[1] then
|
|
||||||
table.insert(find_results, 3, find_results[1])
|
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
|
||||||
-- Copy the ending index to the end of the table, so that an ending index
|
|
||||||
-- always follows a starting index after position 3 in the table.
|
|
||||||
table.insert(find_results, find_results[2] + 1)
|
|
||||||
-- Then, we just iterate over our modified table.
|
|
||||||
for i = 3, #find_results - 1 do
|
|
||||||
local start = find_results[i]
|
|
||||||
local fin = find_results[i + 1] - 1
|
|
||||||
local type = pattern.type[i - 2]
|
|
||||||
-- ↑ (i - 2) to convert from [3; n] to [1; n]
|
|
||||||
local text = full_text:sub(start, fin)
|
|
||||||
push_token(t, syn.symbols[text] or type, text)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local start, fin = find_results[1], find_results[2]
|
|
||||||
local text = full_text:sub(start, fin)
|
|
||||||
push_token(t, syn.symbols[text] or pattern.type, text)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- State is a 32-bit number that is four separate bytes, illustrating how many
|
function tokenizer.tokenize(syntax, text, state)
|
||||||
-- differnet delimiters we have open, and which subsyntaxes we have active.
|
|
||||||
-- At most, there are 3 subsyntaxes active at the same time. Beyond that,
|
|
||||||
-- does not support further highlighting.
|
|
||||||
|
|
||||||
-- You can think of it as a maximum 4 integer (0-255) stack. It always has
|
|
||||||
-- 1 integer in it. Calling `push_subsyntax` increases the stack depth. Calling
|
|
||||||
-- `pop_subsyntax` decreases it. The integers represent the index of a pattern
|
|
||||||
-- that we're following in the syntax. The top of the stack can be any valid
|
|
||||||
-- pattern index, any integer lower in the stack must represent a pattern that
|
|
||||||
-- specifies a subsyntax.
|
|
||||||
|
|
||||||
-- If you do not have subsyntaxes in your syntax, the three most
|
|
||||||
-- singificant numbers will always be 0, the stack will only ever be length 1
|
|
||||||
-- and the state variable will only ever range from 0-255.
|
|
||||||
local function retrieve_syntax_state(incoming_syntax, state)
|
|
||||||
local current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
|
||||||
incoming_syntax, nil, state, 0
|
|
||||||
if state > 0 and (state > 255 or current_syntax.patterns[state].syntax) then
|
|
||||||
-- If we have higher bits, then decode them one at a time, and find which
|
|
||||||
-- syntax we're using. Rather than walking the bytes, and calling into
|
|
||||||
-- `syntax` each time, we could probably cache this in a single table.
|
|
||||||
for i = 0, 2 do
|
|
||||||
local target = bit32.extract(state, i*8, 8)
|
|
||||||
if target ~= 0 then
|
|
||||||
if current_syntax.patterns[target].syntax then
|
|
||||||
subsyntax_info = current_syntax.patterns[target]
|
|
||||||
current_syntax = type(subsyntax_info.syntax) == "table" and
|
|
||||||
subsyntax_info.syntax or syntax.get(subsyntax_info.syntax)
|
|
||||||
current_pattern_idx = 0
|
|
||||||
current_level = i+1
|
|
||||||
else
|
|
||||||
current_pattern_idx = target
|
|
||||||
break
|
|
||||||
end
|
|
||||||
else
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return current_syntax, subsyntax_info, current_pattern_idx, current_level
|
|
||||||
end
|
|
||||||
|
|
||||||
function tokenizer.tokenize(incoming_syntax, text, state)
|
|
||||||
local res = {}
|
local res = {}
|
||||||
local i = 1
|
local i = 1
|
||||||
|
|
||||||
if #incoming_syntax.patterns == 0 then
|
if #syntax.patterns == 0 then
|
||||||
return { "normal", text }
|
return { "normal", text }
|
||||||
end
|
end
|
||||||
|
|
||||||
state = state or 0
|
|
||||||
-- incoming_syntax : the parent syntax of the file.
|
|
||||||
-- state : a 32-bit number representing syntax state (see above)
|
|
||||||
|
|
||||||
-- current_syntax : the syntax we're currently in.
|
|
||||||
-- subsyntax_info : info about the delimiters of this subsyntax.
|
|
||||||
-- current_pattern_idx: the index of the pattern we're on for this syntax.
|
|
||||||
-- current_level : how many subsyntaxes deep we are.
|
|
||||||
local current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
|
||||||
retrieve_syntax_state(incoming_syntax, state)
|
|
||||||
|
|
||||||
-- Should be used to set the state variable. Don't modify it directly.
|
|
||||||
local function set_subsyntax_pattern_idx(pattern_idx)
|
|
||||||
current_pattern_idx = pattern_idx
|
|
||||||
state = bit32.replace(state, pattern_idx, current_level*8, 8)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function push_subsyntax(entering_syntax, pattern_idx)
|
|
||||||
set_subsyntax_pattern_idx(pattern_idx)
|
|
||||||
current_level = current_level + 1
|
|
||||||
subsyntax_info = entering_syntax
|
|
||||||
current_syntax = type(entering_syntax.syntax) == "table" and
|
|
||||||
entering_syntax.syntax or syntax.get(entering_syntax.syntax)
|
|
||||||
current_pattern_idx = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
local function pop_subsyntax()
|
|
||||||
set_subsyntax_pattern_idx(0)
|
|
||||||
current_level = current_level - 1
|
|
||||||
set_subsyntax_pattern_idx(0)
|
|
||||||
current_syntax, subsyntax_info, current_pattern_idx, current_level =
|
|
||||||
retrieve_syntax_state(incoming_syntax, state)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function find_text(text, p, offset, at_start, close)
|
|
||||||
local target, res = p.pattern or p.regex, { 1, offset - 1 }, p.regex
|
|
||||||
local code = type(target) == "table" and target[close and 2 or 1] or target
|
|
||||||
if p.regex and type(p.regex) ~= "table" then
|
|
||||||
p._regex = p._regex or regex.compile(p.regex)
|
|
||||||
code = p._regex
|
|
||||||
end
|
|
||||||
repeat
|
|
||||||
local next = res[2] + 1
|
|
||||||
-- go to the start of the next utf-8 character
|
|
||||||
while text:byte(next) and common.is_utf8_cont(text, next) do
|
|
||||||
next = next + 1
|
|
||||||
end
|
|
||||||
res = p.pattern and { text:find(at_start and "^" .. code or code, next) }
|
|
||||||
or { regex.match(code, text, next, at_start and regex.ANCHORED or 0) }
|
|
||||||
if res[1] and close and target[3] then
|
|
||||||
local count = 0
|
|
||||||
for i = res[1] - 1, 1, -1 do
|
|
||||||
if text:byte(i) ~= target[3]:byte() then break end
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
-- Check to see if the escaped character is there,
|
|
||||||
-- and if it is not itself escaped.
|
|
||||||
if count % 2 == 0 then break end
|
|
||||||
end
|
|
||||||
until not res[1] or not close or not target[3]
|
|
||||||
return table.unpack(res)
|
|
||||||
end
|
|
||||||
|
|
||||||
while i <= #text do
|
while i <= #text do
|
||||||
-- continue trying to match the end pattern of a pair if we have a state set
|
-- continue trying to match the end pattern of a pair if we have a state set
|
||||||
if current_pattern_idx > 0 then
|
if state then
|
||||||
local p = current_syntax.patterns[current_pattern_idx]
|
local p = syntax.patterns[state]
|
||||||
local s, e = find_text(text, p, i, false, true)
|
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_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: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_text(text, subsyntax_info, i, true, true)
|
|
||||||
if s then
|
if s then
|
||||||
push_token(res, subsyntax_info.type, text:sub(i, e))
|
push_token(res, p.type, text:sub(i, e))
|
||||||
-- On finding unescaped delimiter, pop it.
|
state = nil
|
||||||
pop_subsyntax()
|
|
||||||
i = e + 1
|
i = e + 1
|
||||||
|
else
|
||||||
|
push_token(res, p.type, text:sub(i))
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- find matching pattern
|
-- find matching pattern
|
||||||
local matched = false
|
local matched = false
|
||||||
for n, p in ipairs(current_syntax.patterns) do
|
for n, p in ipairs(syntax.patterns) do
|
||||||
local find_results = { find_text(text, p, i, true, false) }
|
local pattern = (type(p.pattern) == "table") and p.pattern[1] or p.pattern
|
||||||
if find_results[1] then
|
local s, e = text:find("^" .. pattern, i)
|
||||||
-- matched pattern; make and add tokens
|
|
||||||
push_tokens(res, current_syntax, p, text, find_results)
|
if s then
|
||||||
|
-- matched pattern; make and add token
|
||||||
|
local t = text:sub(s, e)
|
||||||
|
push_token(res, syntax.symbols[t] or p.type, t)
|
||||||
|
|
||||||
-- update state if this was a start|end pattern pair
|
-- update state if this was a start|end pattern pair
|
||||||
if type(p.pattern or p.regex) == "table" then
|
if type(p.pattern) == "table" then
|
||||||
-- If we have a subsyntax, push that onto the subsyntax stack.
|
state = n
|
||||||
if p.syntax then
|
|
||||||
push_subsyntax(p, n)
|
|
||||||
else
|
|
||||||
set_subsyntax_pattern_idx(n)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- move cursor past this token
|
-- move cursor past this token
|
||||||
i = find_results[2] + 1
|
i = e + 1
|
||||||
matched = true
|
matched = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
@ -237,13 +87,8 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
||||||
|
|
||||||
-- consume character if we didn't match
|
-- consume character if we didn't match
|
||||||
if not matched then
|
if not matched then
|
||||||
local n = 0
|
push_token(res, "normal", text:sub(i, i))
|
||||||
-- reach the next character
|
i = i + 1
|
||||||
while text:byte(i + n + 1) and common.is_utf8_cont(text, i + n + 1) do
|
|
||||||
n = n + 1
|
|
||||||
end
|
|
||||||
push_token(res, "normal", text:sub(i, i + n))
|
|
||||||
i = i + n + 1
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,6 @@ local Object = require "core.object"
|
||||||
|
|
||||||
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 }
|
||||||
|
@ -20,20 +16,16 @@ function View:new()
|
||||||
self.scrollable = false
|
self.scrollable = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function View:move_towards(t, k, dest, rate)
|
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)
|
return self:move_towards(self, t, k, dest, rate)
|
||||||
end
|
end
|
||||||
local val = t[k]
|
local val = t[k]
|
||||||
if not config.transitions or math.abs(val - dest) < 0.5 then
|
if math.abs(val - dest) < 0.5 then
|
||||||
t[k] = dest
|
t[k] = dest
|
||||||
else
|
else
|
||||||
rate = rate or 0.5
|
t[k] = common.lerp(val, dest, rate or 0.5)
|
||||||
if config.fps ~= 60 or config.animation_rate ~= 1 then
|
|
||||||
local dt = 60 / config.fps
|
|
||||||
rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt)
|
|
||||||
end
|
|
||||||
t[k] = common.lerp(val, dest, rate)
|
|
||||||
end
|
end
|
||||||
if val ~= dest then
|
if val ~= dest then
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
|
@ -102,10 +94,14 @@ function View:on_text_input(text)
|
||||||
-- no-op
|
-- no-op
|
||||||
end
|
end
|
||||||
|
|
||||||
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
|
||||||
|
@ -136,7 +132,7 @@ end
|
||||||
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
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,4 +1,3 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
|
@ -8,65 +7,25 @@ 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 = {
|
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
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, triggered_manually)
|
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
|
|
||||||
cb = info.cb, -- A callback called once when item is selected
|
|
||||||
data = info.data -- Optional data that can be used on cb
|
|
||||||
},
|
|
||||||
mt
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else
|
|
||||||
info = (type(info) == "string") and info
|
|
||||||
table.insert(items, setmetatable({ text = text, info = info }, mt))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not triggered_manually then
|
|
||||||
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
|
||||||
else
|
|
||||||
autocomplete.map_manually[t.name] = { files = t.files or ".*", items = items }
|
|
||||||
end
|
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.max_symbols
|
|
||||||
|
|
||||||
core.add_thread(function()
|
core.add_thread(function()
|
||||||
local cache = setmetatable({}, { __mode = "k" })
|
local cache = setmetatable({}, { __mode = "k" })
|
||||||
|
@ -149,39 +108,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 in_table(value, table_array)
|
|
||||||
for i, element in pairs(table_array) do
|
|
||||||
if element == value then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function update_suggestions()
|
local function update_suggestions()
|
||||||
local doc = core.active_view.doc
|
local doc = core.active_view.doc
|
||||||
local filename = doc and doc.filename or ""
|
local filename = doc and doc.filename or ""
|
||||||
|
|
||||||
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)
|
||||||
|
@ -192,7 +128,7 @@ 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
|
||||||
|
@ -201,6 +137,7 @@ local function update_suggestions()
|
||||||
end
|
end
|
||||||
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()
|
||||||
|
@ -208,12 +145,14 @@ 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 getmetatable(core.active_view) == 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
|
||||||
|
@ -235,67 +174,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
|
|
||||||
|
|
||||||
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 draw_description_box(text, av, sx, sy, sw, sh)
|
|
||||||
local width = 0
|
|
||||||
|
|
||||||
local lines = {}
|
|
||||||
for line in string.gmatch(text.."\n", "(.-)\n") do
|
|
||||||
width = math.max(width, style.font:get_width(line))
|
|
||||||
table.insert(lines, line)
|
|
||||||
end
|
|
||||||
|
|
||||||
local height = #lines * style.font:get_height()
|
|
||||||
|
|
||||||
-- draw background rect
|
|
||||||
renderer.draw_rect(
|
|
||||||
sx + sw + style.padding.x / 4,
|
|
||||||
sy,
|
|
||||||
width + style.padding.x * 2,
|
|
||||||
height + style.padding.y * 2,
|
|
||||||
style.background3
|
|
||||||
)
|
|
||||||
|
|
||||||
-- draw text
|
|
||||||
local lh = style.font:get_height()
|
|
||||||
local y = sy + style.padding.y
|
|
||||||
local x = sx + sw + style.padding.x / 4
|
|
||||||
|
|
||||||
for _, line in pairs(lines) do
|
|
||||||
common.draw_text(
|
|
||||||
style.font, style.text, line, "left", x + style.padding.x, y, width, lh
|
|
||||||
)
|
|
||||||
y = y + lh
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function draw_suggestions_box(av)
|
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)
|
||||||
|
@ -304,14 +191,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
|
||||||
|
@ -319,55 +199,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.cb then
|
|
||||||
s.cb(suggestions_idx, s)
|
|
||||||
s.cb = nil
|
|
||||||
s.data = nil
|
|
||||||
end
|
|
||||||
if s.desc and #s.desc > 0 then
|
|
||||||
draw_description_box(s.desc, av, rx, ry, rw, rh)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
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
|
||||||
|
@ -381,30 +232,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(...)
|
||||||
|
@ -413,19 +240,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(...)
|
||||||
|
|
||||||
|
@ -436,53 +257,12 @@ 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()
|
||||||
return get_active_view() and #suggestions > 0
|
return get_active_view() and #suggestions > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
command.add(predicate, {
|
command.add(predicate, {
|
||||||
["autocomplete:complete"] = function()
|
["autocomplete:complete"] = function()
|
||||||
local doc = core.active_view.doc
|
local doc = core.active_view.doc
|
||||||
|
@ -502,19 +282,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,8 +1,8 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local Doc = require "core.doc"
|
local Doc = require "core.doc"
|
||||||
|
|
||||||
|
|
||||||
local times = setmetatable({}, { __mode = "k" })
|
local times = setmetatable({}, { __mode = "k" })
|
||||||
|
|
||||||
local function update_time(doc)
|
local function update_time(doc)
|
||||||
|
@ -10,6 +10,7 @@ local function update_time(doc)
|
||||||
times[doc] = info.modified
|
times[doc] = info.modified
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function reload_doc(doc)
|
local function reload_doc(doc)
|
||||||
local fp = io.open(doc.filename, "r")
|
local fp = io.open(doc.filename, "r")
|
||||||
local text = fp:read("*a")
|
local text = fp:read("*a")
|
||||||
|
@ -25,19 +26,23 @@ local function reload_doc(doc)
|
||||||
core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename)
|
core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
local on_modify = core.on_dirmonitor_modify
|
|
||||||
|
|
||||||
core.on_dirmonitor_modify = function(dir, filepath)
|
core.add_thread(function()
|
||||||
local abs_filename = dir.name .. PATHSEP .. filepath
|
while true do
|
||||||
for _, doc in ipairs(core.docs) do
|
-- check all doc modified times
|
||||||
local info = system.get_file_info(doc.filename or "")
|
for _, doc in ipairs(core.docs) do
|
||||||
if doc.abs_filename == abs_filename and info and times[doc] ~= info.modified then
|
local info = system.get_file_info(doc.filename or "")
|
||||||
reload_doc(doc)
|
if info and times[doc] ~= info.modified then
|
||||||
break
|
reload_doc(doc)
|
||||||
|
end
|
||||||
|
coroutine.yield()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- wait for next scan
|
||||||
|
coroutine.yield(config.project_scan_rate)
|
||||||
end
|
end
|
||||||
on_modify(dir, filepath)
|
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
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
|
||||||
local command = require "core.command"
|
|
||||||
local keymap = require "core.keymap"
|
|
||||||
local ContextMenu = require "core.contextmenu"
|
|
||||||
local RootView = require "core.rootview"
|
|
||||||
|
|
||||||
local 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(nil, {
|
|
||||||
["context:show"] = function()
|
|
||||||
menu:show(core.active_view.position.x, core.active_view.position.y)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
keymap.add {
|
|
||||||
["menu"] = "context:show"
|
|
||||||
}
|
|
||||||
|
|
||||||
local function copy_log()
|
|
||||||
local item = core.active_view.hovered_item
|
|
||||||
if item then
|
|
||||||
system.set_clipboard(core.get_log(item))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function open_as_doc()
|
|
||||||
local doc = core.open_doc("logs.txt")
|
|
||||||
core.root_view:open_doc(doc)
|
|
||||||
doc:insert(1, 1, core.get_log())
|
|
||||||
end
|
|
||||||
|
|
||||||
menu:register("core.logview", {
|
|
||||||
{ text = "Copy entry", command = copy_log },
|
|
||||||
{ text = "Open as file", command = open_as_doc }
|
|
||||||
})
|
|
||||||
|
|
||||||
if require("plugins.scale") then
|
|
||||||
menu:register("core.docview", {
|
|
||||||
{ text = "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,11 +1,8 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local common = require "core.common"
|
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
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" })
|
||||||
|
|
||||||
|
@ -45,43 +42,12 @@ local function optimal_indent_from_stat(stat)
|
||||||
return bins[1][1], bins[1][2]
|
return bins[1][1], bins[1][2]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local auto_detect_max_lines = 400
|
||||||
-- return nil if it is a comment or blank line or the initial part of the
|
|
||||||
-- line otherwise.
|
|
||||||
-- we don't need to have the whole line to detect indentation.
|
|
||||||
local function get_first_line_part(tokens)
|
|
||||||
local i, n = 1, #tokens
|
|
||||||
while i + 1 <= n do
|
|
||||||
local ttype, ttext = tokens[i], tokens[i + 1]
|
|
||||||
if ttype ~= "comment" and ttext:gsub("%s+", "") ~= "" then
|
|
||||||
return ttext
|
|
||||||
end
|
|
||||||
i = i + 2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_non_empty_lines(syntax, lines)
|
|
||||||
return coroutine.wrap(function()
|
|
||||||
local tokens, state
|
|
||||||
local i = 0
|
|
||||||
for _, line in ipairs(lines) do
|
|
||||||
tokens, state = tokenizer.tokenize(syntax, line, state)
|
|
||||||
local line_start = get_first_line_part(tokens)
|
|
||||||
if line_start then
|
|
||||||
i = i + 1
|
|
||||||
coroutine.yield(i, line_start)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local auto_detect_max_lines = 100
|
|
||||||
|
|
||||||
local function detect_indent_stat(doc)
|
local function detect_indent_stat(doc)
|
||||||
local stat = {}
|
local stat = {}
|
||||||
local tab_count = 0
|
local tab_count = 0
|
||||||
for i, text in get_non_empty_lines(doc.syntax, doc.lines) do
|
for i, text in ipairs(doc.lines) do
|
||||||
local str = text:match("^ %s+%S")
|
local str = text:match("^ %s+%S")
|
||||||
if str then add_to_stat(stat, #str - 1) end
|
if str then add_to_stat(stat, #str - 1) end
|
||||||
local str = text:match("^\t+")
|
local str = text:match("^\t+")
|
||||||
|
@ -92,23 +58,20 @@ local function detect_indent_stat(doc)
|
||||||
table.sort(stat, function(a, b) return a[1] < b[1] 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
|
core.log_quiet("Detect-indent: using tabs indentation")
|
||||||
else
|
return "hard"
|
||||||
return "soft", indent or config.indent_size, score or 0
|
elseif indent then
|
||||||
|
core.log_quiet("Detect-indent: using indentation size of %d", indent)
|
||||||
|
return "soft", indent
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function update_cache(doc)
|
local function update_cache(doc)
|
||||||
local type, size, score = detect_indent_stat(doc)
|
local type, size = detect_indent_stat(doc)
|
||||||
local score_threshold = 4
|
if type then
|
||||||
if score < score_threshold then
|
cache[doc] = { type = type, size = size }
|
||||||
-- use default values
|
|
||||||
type = config.tab_type
|
|
||||||
size = config.indent_size
|
|
||||||
end
|
end
|
||||||
cache[doc] = { type = type, size = size, confirmed = (score >= score_threshold) }
|
|
||||||
doc.indent_info = cache[doc]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,86 +84,31 @@ 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] = {type = type,
|
return with_indent_override(core.active_view.doc, perform, ...)
|
||||||
size = indent_size,
|
|
||||||
confirmed = true}
|
|
||||||
doc.indent_info = cache[doc]
|
|
||||||
end
|
|
||||||
|
|
||||||
local function set_indent_type_command()
|
|
||||||
core.command_view:enter(
|
|
||||||
"Specify indent style for this file",
|
|
||||||
function(value) -- submit
|
|
||||||
local doc = core.active_view.doc
|
|
||||||
value = value:lower()
|
|
||||||
set_indent_type(doc, value == "tabs" and "hard" or "soft")
|
|
||||||
end,
|
|
||||||
function(text) -- suggest
|
|
||||||
return common.fuzzy_match({"tabs", "spaces"}, text)
|
|
||||||
end,
|
|
||||||
nil, -- cancel
|
|
||||||
function(text) -- validate
|
|
||||||
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] = {type = indent_type,
|
return with_indent_override(self.doc, draw, self, ...)
|
||||||
size = size,
|
|
||||||
confirmed = true}
|
|
||||||
doc.indent_info = cache[doc]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function set_indent_size_command()
|
|
||||||
core.command_view:enter(
|
|
||||||
"Specify indent size for current file",
|
|
||||||
function(value) -- submit
|
|
||||||
local value = math.floor(tonumber(value))
|
|
||||||
local doc = core.active_view.doc
|
|
||||||
set_indent_size(doc, value)
|
|
||||||
end,
|
|
||||||
nil, -- suggest
|
|
||||||
nil, -- cancel
|
|
||||||
function(value) -- validate
|
|
||||||
local 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,36 +0,0 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
|
|
||||||
local style = require "core.style"
|
|
||||||
local DocView = require "core.docview"
|
|
||||||
local common = require "core.common"
|
|
||||||
|
|
||||||
local draw_line_text = DocView.draw_line_text
|
|
||||||
|
|
||||||
function DocView:draw_line_text(idx, x, y)
|
|
||||||
local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"])
|
|
||||||
local color = style.syntax.whitespace or style.syntax.comment
|
|
||||||
local ty = y + self:get_line_text_y_offset()
|
|
||||||
local tx
|
|
||||||
local text, offset, s, e = self.doc.lines[idx], 1
|
|
||||||
local x1, _, x2, _ = self:get_content_bounds()
|
|
||||||
local _offset = self:get_x_offset_col(idx, x1)
|
|
||||||
offset = _offset
|
|
||||||
while true do
|
|
||||||
s, e = text:find(" +", offset)
|
|
||||||
if not s then break end
|
|
||||||
tx = self:get_col_x_offset(idx, s) + x
|
|
||||||
renderer.draw_text(font, string.rep("·", e - s + 1), tx, ty, color)
|
|
||||||
if tx > x + x2 then break end
|
|
||||||
offset = e + 1
|
|
||||||
end
|
|
||||||
offset = _offset
|
|
||||||
while true do
|
|
||||||
s, e = text:find("\t", offset)
|
|
||||||
if not s then break end
|
|
||||||
tx = self:get_col_x_offset(idx, s) + x
|
|
||||||
renderer.draw_text(font, "»", tx, ty, color)
|
|
||||||
if tx > x + x2 then break end
|
|
||||||
offset = e + 1
|
|
||||||
end
|
|
||||||
draw_line_text(self, idx, x, y)
|
|
||||||
end
|
|
|
@ -1,25 +1,20 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "C",
|
files = { "%.c$", "%.h$", "%.inl$", "%.cpp$", "%.hpp$" },
|
||||||
files = { "%.c$", "%.h$", "%.inl$" },
|
|
||||||
comment = "//",
|
comment = "//",
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = "//.-\n", type = "comment" },
|
{ pattern = "//.-\n", type = "comment" },
|
||||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
{ pattern = { "#", "[^\\]\n" }, type = "comment" },
|
||||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||||
{ pattern = "0x%x+", type = "number" },
|
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||||
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
|
{ pattern = "-?0x%x+", type = "number" },
|
||||||
{ pattern = "%.?%d+f?", type = "number" },
|
{ pattern = "-?%d+[%d%.eE]*f?", type = "number" },
|
||||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
{ pattern = "-?%.?%d+f?", type = "number" },
|
||||||
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||||
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
{ 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",
|
||||||
|
@ -33,6 +28,8 @@ 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",
|
||||||
|
@ -44,7 +41,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",
|
||||||
|
@ -56,17 +54,6 @@ syntax.add {
|
||||||
["true"] = "literal",
|
["true"] = "literal",
|
||||||
["false"] = "literal",
|
["false"] = "literal",
|
||||||
["NULL"] = "literal",
|
["NULL"] = "literal",
|
||||||
["#include"] = "keyword",
|
|
||||||
["#if"] = "keyword",
|
|
||||||
["#ifdef"] = "keyword",
|
|
||||||
["#ifndef"] = "keyword",
|
|
||||||
["#else"] = "keyword",
|
|
||||||
["#elseif"] = "keyword",
|
|
||||||
["#endif"] = "keyword",
|
|
||||||
["#define"] = "keyword",
|
|
||||||
["#warning"] = "keyword",
|
|
||||||
["#error"] = "keyword",
|
|
||||||
["#pragma"] = "keyword",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,123 +0,0 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
pcall(require, "plugins.language_c")
|
|
||||||
|
|
||||||
local syntax = require "core.syntax"
|
|
||||||
|
|
||||||
syntax.add {
|
|
||||||
name = "C++",
|
|
||||||
files = {
|
|
||||||
"%.h$", "%.inl$", "%.cpp$", "%.cc$", "%.C$", "%.cxx$",
|
|
||||||
"%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$"
|
|
||||||
},
|
|
||||||
comment = "//",
|
|
||||||
patterns = {
|
|
||||||
{ pattern = "//.-\n", type = "comment" },
|
|
||||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
|
||||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
|
||||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
|
||||||
{ pattern = "0x%x+", type = "number" },
|
|
||||||
{ pattern = "%d+[%d%.eE]*f?", type = "number" },
|
|
||||||
{ pattern = "%.?%d+f?", type = "number" },
|
|
||||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
|
||||||
{ pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
|
||||||
{ pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
|
||||||
{ pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
|
||||||
{ pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
|
|
||||||
{ pattern = "[%a_][%w_]*::", type = "symbol" },
|
|
||||||
{ pattern = "::", type = "symbol" },
|
|
||||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
|
||||||
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
|
|
||||||
{ pattern = "#[%a_][%w_]*", type = "keyword" },
|
|
||||||
},
|
|
||||||
symbols = {
|
|
||||||
["alignof"] = "keyword",
|
|
||||||
["alignas"] = "keyword",
|
|
||||||
["private"] = "keyword",
|
|
||||||
["protected"] = "keyword",
|
|
||||||
["public"] = "keyword",
|
|
||||||
["register"] = "keyword",
|
|
||||||
["nullptr"] = "keyword",
|
|
||||||
["operator"] = "keyword",
|
|
||||||
["asm"] = "keyword",
|
|
||||||
["catch"] = "keyword",
|
|
||||||
["throw"] = "keyword",
|
|
||||||
["try"] = "keyword",
|
|
||||||
["compl"] = "keyword",
|
|
||||||
["explicit"] = "keyword",
|
|
||||||
["export"] = "keyword",
|
|
||||||
["concept"] = "keyword",
|
|
||||||
["consteval"] = "keyword",
|
|
||||||
["constexpr"] = "keyword",
|
|
||||||
["constinit"] = "keyword",
|
|
||||||
["const_cast"] = "keyword",
|
|
||||||
["dynamic_cast"] = "keyword",
|
|
||||||
["reinterpret_cast"] = "keyword",
|
|
||||||
["static_cast"] = "keyword",
|
|
||||||
["static_assert"] = "keyword",
|
|
||||||
["template"] = "keyword",
|
|
||||||
["this"] = "keyword",
|
|
||||||
["thread_local"] = "keyword",
|
|
||||||
["requires"] = "keyword",
|
|
||||||
["co_wait"] = "keyword",
|
|
||||||
["co_return"] = "keyword",
|
|
||||||
["co_yield"] = "keyword",
|
|
||||||
["decltype"] = "keyword",
|
|
||||||
["delete"] = "keyword",
|
|
||||||
["export"] = "keyword",
|
|
||||||
["friend"] = "keyword",
|
|
||||||
["typeid"] = "keyword",
|
|
||||||
["typename"] = "keyword",
|
|
||||||
["mutable"] = "keyword",
|
|
||||||
["override"] = "keyword",
|
|
||||||
["virtual"] = "keyword",
|
|
||||||
["using"] = "keyword",
|
|
||||||
["new"] = "keyword",
|
|
||||||
["noexcept"] = "keyword",
|
|
||||||
["if"] = "keyword",
|
|
||||||
["then"] = "keyword",
|
|
||||||
["else"] = "keyword",
|
|
||||||
["elseif"] = "keyword",
|
|
||||||
["do"] = "keyword",
|
|
||||||
["while"] = "keyword",
|
|
||||||
["for"] = "keyword",
|
|
||||||
["break"] = "keyword",
|
|
||||||
["continue"] = "keyword",
|
|
||||||
["return"] = "keyword",
|
|
||||||
["goto"] = "keyword",
|
|
||||||
["typedef"] = "keyword",
|
|
||||||
["enum"] = "keyword",
|
|
||||||
["extern"] = "keyword",
|
|
||||||
["static"] = "keyword",
|
|
||||||
["volatile"] = "keyword",
|
|
||||||
["const"] = "keyword",
|
|
||||||
["inline"] = "keyword",
|
|
||||||
["switch"] = "keyword",
|
|
||||||
["case"] = "keyword",
|
|
||||||
["default"] = "keyword",
|
|
||||||
["auto"] = "keyword",
|
|
||||||
["const"] = "keyword",
|
|
||||||
["void"] = "keyword2",
|
|
||||||
["int"] = "keyword2",
|
|
||||||
["short"] = "keyword2",
|
|
||||||
["long"] = "keyword2",
|
|
||||||
["float"] = "keyword2",
|
|
||||||
["double"] = "keyword2",
|
|
||||||
["char"] = "keyword2",
|
|
||||||
["unsigned"] = "keyword2",
|
|
||||||
["bool"] = "keyword2",
|
|
||||||
["true"] = "keyword2",
|
|
||||||
["false"] = "keyword2",
|
|
||||||
["#include"] = "keyword",
|
|
||||||
["#if"] = "keyword",
|
|
||||||
["#ifdef"] = "keyword",
|
|
||||||
["#ifndef"] = "keyword",
|
|
||||||
["#else"] = "keyword",
|
|
||||||
["#elseif"] = "keyword",
|
|
||||||
["#endif"] = "keyword",
|
|
||||||
["#define"] = "keyword",
|
|
||||||
["#warning"] = "keyword",
|
|
||||||
["#error"] = "keyword",
|
|
||||||
["#pragma"] = "keyword",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "CSS",
|
|
||||||
files = { "%.css$" },
|
files = { "%.css$" },
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = "\\.", type = "normal" },
|
{ pattern = "\\.", type = "normal" },
|
||||||
|
@ -11,8 +9,7 @@ syntax.add {
|
||||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||||
{ pattern = "[%a][%w-]*%s*%f[:]", type = "keyword" },
|
{ pattern = "[%a][%w-]*%s*%f[:]", type = "keyword" },
|
||||||
{ pattern = "#%x%x%x%x%x%x%f[%W]",type = "string" },
|
{ pattern = "#%x+", type = "string" },
|
||||||
{ pattern = "#%x%x%x%f[%W]", type = "string" },
|
|
||||||
{ pattern = "-?%d+[%d%.]*p[xt]", type = "number" },
|
{ pattern = "-?%d+[%d%.]*p[xt]", type = "number" },
|
||||||
{ pattern = "-?%d+[%d%.]*deg", type = "number" },
|
{ pattern = "-?%d+[%d%.]*deg", type = "number" },
|
||||||
{ pattern = "-?%d+[%d%.]*", type = "number" },
|
{ pattern = "-?%d+[%d%.]*", type = "number" },
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local syntax = require "core.syntax"
|
|
||||||
|
|
||||||
syntax.add {
|
|
||||||
name = "HTML",
|
|
||||||
files = { "%.html?$" },
|
|
||||||
patterns = {
|
|
||||||
{
|
|
||||||
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"
|
|
||||||
},
|
|
||||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
|
||||||
{ pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },
|
|
||||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
|
||||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
|
||||||
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
|
||||||
{ pattern = "-?%d+[%d%.]*f?", type = "number" },
|
|
||||||
{ pattern = "-?%.?%d+f?", type = "number" },
|
|
||||||
{ pattern = "%f[^<]![%a_][%w_]*", type = "keyword2" },
|
|
||||||
{ pattern = "%f[^<][%a_][%w_]*", type = "function" },
|
|
||||||
{ pattern = "%f[^<]/[%a_][%w_]*", type = "function" },
|
|
||||||
{ pattern = "[%a_][%w_]*", type = "keyword" },
|
|
||||||
{ pattern = "[/<>=]", type = "operator" },
|
|
||||||
},
|
|
||||||
symbols = {},
|
|
||||||
}
|
|
|
@ -1,19 +1,16 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "JavaScript",
|
|
||||||
files = { "%.js$", "%.json$", "%.cson$" },
|
files = { "%.js$", "%.json$", "%.cson$" },
|
||||||
comment = "//",
|
comment = "//",
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = "//.-\n", 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 = { "'", "'", '\\' }, 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" },
|
||||||
|
@ -42,7 +39,6 @@ syntax.add {
|
||||||
["if"] = "keyword",
|
["if"] = "keyword",
|
||||||
["import"] = "keyword",
|
["import"] = "keyword",
|
||||||
["in"] = "keyword",
|
["in"] = "keyword",
|
||||||
["of"] = "keyword",
|
|
||||||
["instanceof"] = "keyword",
|
["instanceof"] = "keyword",
|
||||||
["let"] = "keyword",
|
["let"] = "keyword",
|
||||||
["new"] = "keyword",
|
["new"] = "keyword",
|
||||||
|
|
|
@ -1,34 +1,25 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
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 = "--",
|
||||||
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 = "%-%-.-\n", type = "comment" },
|
{ pattern = "%-%-.-\n", type = "comment" },
|
||||||
{ pattern = "0x%x+%.%x*[pP][-+]?%d+", type = "number" },
|
{ pattern = "-?0x%x+", type = "number" },
|
||||||
{ pattern = "0x%x+%.%x*", type = "number" },
|
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
||||||
{ pattern = "0x%.%x+[pP][-+]?%d+", type = "number" },
|
{ pattern = "-?%.?%d+", type = "number" },
|
||||||
{ pattern = "0x%.%x+", type = "number" },
|
{ pattern = "<%a+>", type = "keyword2" },
|
||||||
{ pattern = "0x%x+[pP][-+]?%d+", type = "number" },
|
{ pattern = "%.%.%.?", type = "operator" },
|
||||||
{ pattern = "0x%x+", type = "number" },
|
{ pattern = "[<>~=]=", type = "operator" },
|
||||||
{ pattern = "%d%.%d*[eE][-+]?%d+", type = "number" },
|
{ pattern = "[%+%-=/%*%^%%#<>]", type = "operator" },
|
||||||
{ pattern = "%d%.%d*", type = "number" },
|
{ pattern = "[%a_][%w_]*%s*%f[(\"{]", type = "function" },
|
||||||
{ pattern = "%.?%d*[eE][-+]?%d+", type = "number" },
|
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||||
{ pattern = "%.?%d+", type = "number" },
|
{ pattern = "::[%a_][%w_]*::", type = "function" },
|
||||||
{ pattern = "<%a+>", type = "keyword2" },
|
|
||||||
{ pattern = "%.%.%.?", type = "operator" },
|
|
||||||
{ pattern = "[<>~=]=", type = "operator" },
|
|
||||||
{ pattern = "[%+%-=/%*%^%%#<>]", type = "operator" },
|
|
||||||
{ pattern = "[%a_][%w_]*()%s*%f[(\"'{]", type = {"function", "normal"} },
|
|
||||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
|
||||||
{ pattern = "::[%a_][%w_]*::", type = "function" },
|
|
||||||
},
|
},
|
||||||
symbols = {
|
symbols = {
|
||||||
["if"] = "keyword",
|
["if"] = "keyword",
|
||||||
|
|
|
@ -1,56 +1,21 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "Markdown",
|
|
||||||
files = { "%.md$", "%.markdown$" },
|
files = { "%.md$", "%.markdown$" },
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = "\\.", type = "normal" },
|
{ pattern = "\\.", type = "normal" },
|
||||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||||
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
|
{ pattern = { "```", "```" }, type = "string" },
|
||||||
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" },
|
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||||
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
|
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||||
{ pattern = { "```perl", "```" }, type = "string", syntax = ".pl" },
|
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||||
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
|
{ pattern = "%-%-%-+", type = "comment" },
|
||||||
{ pattern = { "```javascript", "```" }, type = "string", syntax = ".js" },
|
{ pattern = "%*%s+", type = "operator" },
|
||||||
{ pattern = { "```html", "```" }, type = "string", syntax = ".html" },
|
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||||
{ pattern = { "```xml", "```" }, type = "string", syntax = ".xml" },
|
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||||
{ pattern = { "```css", "```" }, type = "string", syntax = ".css" },
|
{ pattern = "#.-\n", type = "keyword" },
|
||||||
{ pattern = { "```lua", "```" }, type = "string", syntax = ".lua" },
|
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||||
{ pattern = { "```bash", "```" }, type = "string", syntax = ".sh" },
|
{ pattern = "https?://%S+", type = "function" },
|
||||||
{ 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 = { "```php", "```" }, type = "string", syntax = ".php" },
|
|
||||||
{ pattern = { "```nim", "```" }, type = "string", syntax = ".nim" },
|
|
||||||
{ pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" },
|
|
||||||
{ pattern = { "```rescript", "```" }, type = "string", syntax = ".res" },
|
|
||||||
{ pattern = { "```moon", "```" }, type = "string", syntax = ".moon" },
|
|
||||||
{ pattern = { "```go", "```" }, type = "string", syntax = ".go" },
|
|
||||||
{ pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" },
|
|
||||||
{ pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" },
|
|
||||||
{ pattern = { "```", "```" }, type = "string" },
|
|
||||||
{ pattern = { "``", "``", "\\" }, type = "string" },
|
|
||||||
{ pattern = { "`", "`", "\\" }, type = "string" },
|
|
||||||
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
|
||||||
{ pattern = "%-%-%-+", type = "comment" },
|
|
||||||
{ pattern = "%*%s+", type = "operator" },
|
|
||||||
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
|
||||||
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
|
||||||
{ pattern = "#.-\n", type = "keyword" },
|
|
||||||
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
|
||||||
{ pattern = "https?://%S+", type = "function" },
|
|
||||||
},
|
},
|
||||||
symbols = { },
|
symbols = { },
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "Python",
|
files = "%.py$",
|
||||||
files = { "%.py$", "%.pyw$", "%.rpy$" },
|
|
||||||
headers = "^#!.*[ /]python",
|
headers = "^#!.*[ /]python",
|
||||||
comment = "#",
|
comment = "#",
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = { "#", "\n" }, type = "comment" },
|
{ pattern = { "#", "\n" }, type = "comment" },
|
||||||
{ pattern = { '[ruU]?"""', '"""'; '\\' }, type = "string" },
|
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
|
||||||
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
|
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
|
||||||
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
|
{ pattern = { '"""', '"""' }, type = "string" },
|
||||||
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
||||||
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
||||||
{ pattern = "-?%.?%d+", type = "number" },
|
{ pattern = "-?%.?%d+", type = "number" },
|
||||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||||
},
|
},
|
||||||
symbols = {
|
symbols = {
|
||||||
["class"] = "keyword",
|
["class"] = "keyword",
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local syntax = require "core.syntax"
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
syntax.add {
|
syntax.add {
|
||||||
name = "XML",
|
files = { "%.xml$", "%.html?$" },
|
||||||
files = { "%.xml$" },
|
|
||||||
headers = "<%?xml",
|
headers = "<%?xml",
|
||||||
patterns = {
|
patterns = {
|
||||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local config = require "core.config"
|
|
||||||
local style = require "core.style"
|
|
||||||
local DocView = require "core.docview"
|
|
||||||
local CommandView = require "core.commandview"
|
|
||||||
|
|
||||||
local draw_overlay = DocView.draw_overlay
|
|
||||||
|
|
||||||
function DocView:draw_overlay(...)
|
|
||||||
if not self:is(CommandView) then
|
|
||||||
local offset = self:get_font():get_width("n") * config.line_limit
|
|
||||||
local x = self:get_line_screen_position(1) + offset
|
|
||||||
local y = self.position.y
|
|
||||||
local w = math.ceil(SCALE * 1)
|
|
||||||
local h = self.size.y
|
|
||||||
|
|
||||||
local color = style.guide or style.selection
|
|
||||||
renderer.draw_rect(x, y, w, h, color)
|
|
||||||
end
|
|
||||||
draw_overlay(self, ...)
|
|
||||||
end
|
|
|
@ -1,4 +1,3 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
|
@ -9,7 +8,6 @@ local View = require "core.view"
|
||||||
|
|
||||||
local ResultsView = View:extend()
|
local ResultsView = View:extend()
|
||||||
|
|
||||||
ResultsView.context = "session"
|
|
||||||
|
|
||||||
function ResultsView:new(text, fn)
|
function ResultsView:new(text, fn)
|
||||||
ResultsView.super.new(self)
|
ResultsView.super.new(self)
|
||||||
|
@ -31,10 +29,7 @@ local function find_all_matches_in_file(t, filename, fn)
|
||||||
for line in fp:lines() do
|
for line in fp:lines() do
|
||||||
local s = fn(line)
|
local s = fn(line)
|
||||||
if s then
|
if s then
|
||||||
-- Insert maximum 256 characters. If we insert more, for compiled files, which can have very long lines
|
table.insert(t, { file = filename, text = line, line = n, col = s })
|
||||||
-- things tend to get sluggish. If our line is longer than 80 characters, begin to truncate the thing.
|
|
||||||
local start_index = math.max(s - 80, 1)
|
|
||||||
table.insert(t, { file = filename, text = (start_index > 1 and "..." or "") .. line:sub(start_index, 256 + start_index), line = n, col = s })
|
|
||||||
core.redraw = true
|
core.redraw = true
|
||||||
end
|
end
|
||||||
if n % 100 == 0 then coroutine.yield() end
|
if n % 100 == 0 then coroutine.yield() end
|
||||||
|
@ -57,8 +52,7 @@ function ResultsView:begin_search(text, fn)
|
||||||
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" then
|
if file.type == "file" then
|
||||||
local path = (dir_name == core.project_dir and "" or (dir_name .. PATHSEP))
|
find_all_matches_in_file(self.results, dir_name .. PATHSEP .. 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 +86,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 +102,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,17 +165,12 @@ 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 = self.last_file_idx / files_number
|
||||||
local text
|
local text
|
||||||
if self.searching then
|
if self.searching then
|
||||||
if files_number then
|
text = string.format("Searching %d%% (%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
|
|
||||||
text = string.format("Searching (%d files, %d matches) for %q...",
|
|
||||||
self.last_file_idx, #self.results, self.query)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
text = string.format("Found %d matches for %q",
|
text = string.format("Found %d matches for %q",
|
||||||
#self.results, self.query)
|
#self.results, self.query)
|
||||||
|
@ -225,7 +213,7 @@ local function begin_search(text, fn)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local rv = ResultsView(text, fn)
|
local rv = ResultsView(text, fn)
|
||||||
core.root_view:get_active_node_default():add_view(rv)
|
core.root_view:get_active_node():add_view(rv)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -239,12 +227,9 @@ command.add(nil, {
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
["project-search:find-regex"] = function()
|
["project-search:find-pattern"] = function()
|
||||||
core.command_view:enter("Find Regex In Project", function(text)
|
core.command_view:enter("Find Pattern In Project", function(text)
|
||||||
local re = regex.compile(text, "i")
|
begin_search(text, function(line_text) return line_text:find(text) end)
|
||||||
begin_search(text, function(line_text)
|
|
||||||
return regex.cmatch(re, line_text)
|
|
||||||
end)
|
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
@ -278,38 +263,12 @@ 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()
|
|
||||||
local view = core.active_view
|
|
||||||
view.scroll.to.y = view.scroll.to.y - view.size.y
|
|
||||||
end,
|
|
||||||
|
|
||||||
["project-search:move-to-next-page"] = function()
|
|
||||||
local view = core.active_view
|
|
||||||
view.scroll.to.y = view.scroll.to.y + view.size.y
|
|
||||||
end,
|
|
||||||
|
|
||||||
["project-search:move-to-start-of-doc"] = function()
|
|
||||||
local view = core.active_view
|
|
||||||
view.scroll.to.y = 0
|
|
||||||
end,
|
|
||||||
|
|
||||||
["project-search:move-to-end-of-doc"] = function()
|
|
||||||
local view = core.active_view
|
|
||||||
view.scroll.to.y = view:get_scrollable_size()
|
|
||||||
end
|
|
||||||
})
|
})
|
||||||
|
|
||||||
keymap.add {
|
keymap.add {
|
||||||
["f5"] = "project-search:refresh",
|
["f5"] = "project-search:refresh",
|
||||||
["ctrl+shift+f"] = "project-search:find",
|
["ctrl+shift+f"] = "project-search:find",
|
||||||
["up"] = "project-search:select-previous",
|
["up"] = "project-search:select-previous",
|
||||||
["down"] = "project-search:select-next",
|
["down"] = "project-search:select-next",
|
||||||
["return"] = "project-search:open-selected",
|
["return"] = "project-search:open-selected",
|
||||||
["pageup"] = "project-search:move-to-previous-page",
|
|
||||||
["pagedown"] = "project-search:move-to-next-page",
|
|
||||||
["ctrl+home"] = "project-search:move-to-start-of-doc",
|
|
||||||
["ctrl+end"] = "project-search:move-to-end-of-doc",
|
|
||||||
["home"] = "project-search:move-to-start-of-doc",
|
|
||||||
["end"] = "project-search:move-to-end-of-doc"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local keymap = require "core.keymap"
|
local keymap = require "core.keymap"
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local config = require "core.config"
|
local config = require "core.config"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
@ -59,6 +58,6 @@ command.add("core.docview", {
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
keymap.add_direct {
|
keymap.add {
|
||||||
["ctrl+shift+q"] = "reflow:reflow"
|
["ctrl+shift+q"] = "reflow:reflow"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
|
||||||
local common = require "core.common"
|
|
||||||
local command = require "core.command"
|
|
||||||
local config = require "core.config"
|
|
||||||
local keymap = require "core.keymap"
|
|
||||||
local style = require "core.style"
|
|
||||||
local RootView = require "core.rootview"
|
|
||||||
local CommandView = require "core.commandview"
|
|
||||||
|
|
||||||
config.plugins.scale = {
|
|
||||||
mode = "code",
|
|
||||||
use_mousewheel = true
|
|
||||||
}
|
|
||||||
|
|
||||||
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] = renderer.font.copy(style[name], s * style[name]:get_size())
|
|
||||||
end
|
|
||||||
else
|
|
||||||
style.code_font = renderer.font.copy(style.code_font, s * style.code_font:get_size())
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, font in pairs(style.syntax_fonts) do
|
|
||||||
renderer.font.set_size(font, s * font:get_size())
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, font in pairs(style.syntax_fonts) do
|
|
||||||
renderer.font.set_size(font, s * font:get_size())
|
|
||||||
end
|
|
||||||
|
|
||||||
-- restore scroll positions
|
|
||||||
for view, n in pairs(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
|
|
||||||
|
|
||||||
|
|
||||||
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",
|
|
||||||
["ctrl+wheelup"] = "scale:increase",
|
|
||||||
["ctrl+wheeldown"] = "scale:decrease"
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
["set"] = set_scale,
|
|
||||||
["get"] = get_scale,
|
|
||||||
["increase"] = inc_scale,
|
|
||||||
["decrease"] = dec_scale,
|
|
||||||
["reset"] = res_scale
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local translate = require "core.doc.translate"
|
local translate = require "core.doc.translate"
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
|
||||||
local common = require "core.common"
|
|
||||||
local command = require "core.command"
|
|
||||||
local style = require "core.style"
|
|
||||||
local View = require "core.view"
|
|
||||||
|
|
||||||
local ToolbarView = View:extend()
|
|
||||||
|
|
||||||
local toolbar_commands = {
|
|
||||||
{symbol = "f", command = "core:new-doc"},
|
|
||||||
{symbol = "D", command = "core:open-file"},
|
|
||||||
{symbol = "S", command = "doc:save"},
|
|
||||||
{symbol = "L", command = "core:find-file"},
|
|
||||||
{symbol = "B", command = "core:find-command"},
|
|
||||||
{symbol = "P", command = "core:open-user-module"},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
local function toolbar_height()
|
|
||||||
return style.icon_big_font:get_height() + style.padding.y * 2
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:new()
|
|
||||||
ToolbarView.super.new(self)
|
|
||||||
self.visible = true
|
|
||||||
self.init_size = true
|
|
||||||
self.tooltip = false
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:update()
|
|
||||||
local dest_size = self.visible and toolbar_height() or 0
|
|
||||||
if self.init_size then
|
|
||||||
self.size.y = dest_size
|
|
||||||
self.init_size = nil
|
|
||||||
else
|
|
||||||
self:move_towards(self.size, "y", dest_size)
|
|
||||||
end
|
|
||||||
ToolbarView.super.update(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:toggle_visible()
|
|
||||||
self.visible = not self.visible
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:each_item()
|
|
||||||
local icon_h, icon_w = style.icon_big_font:get_height(), style.icon_big_font:get_width("D")
|
|
||||||
local toolbar_spacing = icon_w / 2
|
|
||||||
local ox, oy = self:get_content_offset()
|
|
||||||
local index = 0
|
|
||||||
local iter = function()
|
|
||||||
index = index + 1
|
|
||||||
if index <= #toolbar_commands then
|
|
||||||
local dx = style.padding.x + (icon_w + toolbar_spacing) * (index - 1)
|
|
||||||
local dy = style.padding.y
|
|
||||||
if dx + icon_w > self.size.x then return end
|
|
||||||
return toolbar_commands[index], ox + dx, oy + dy, icon_w, icon_h
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return iter
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:get_min_width()
|
|
||||||
local icon_w = style.icon_big_font:get_width("D")
|
|
||||||
local space = icon_w / 2
|
|
||||||
return 2 * style.padding.x + (icon_w + space) * #toolbar_commands - space
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:draw()
|
|
||||||
self:draw_background(style.background2)
|
|
||||||
|
|
||||||
for item, x, y, w, h in self:each_item() do
|
|
||||||
local color = item == self.hovered_item and style.text or style.dim
|
|
||||||
common.draw_text(style.icon_big_font, color, item.symbol, nil, x, y, 0, h)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:on_mouse_pressed(button, x, y, clicks)
|
|
||||||
local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks)
|
|
||||||
if caught then return end
|
|
||||||
core.set_active_view(core.last_active_view)
|
|
||||||
if self.hovered_item then
|
|
||||||
command.perform(self.hovered_item.command)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function ToolbarView:on_mouse_moved(px, py, ...)
|
|
||||||
ToolbarView.super.on_mouse_moved(self, px, py, ...)
|
|
||||||
self.hovered_item = nil
|
|
||||||
local x_min, x_max, y_min, y_max = self.size.x, 0, self.size.y, 0
|
|
||||||
for item, x, y, w, h in self:each_item() do
|
|
||||||
x_min, x_max = math.min(x, x_min), math.max(x + w, x_max)
|
|
||||||
y_min, y_max = y, y + h
|
|
||||||
if px > x and py > y and px <= x + w and py <= y + h then
|
|
||||||
self.hovered_item = item
|
|
||||||
core.status_view:show_tooltip(command.prettify_name(item.command))
|
|
||||||
self.tooltip = true
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if self.tooltip and not (px > x_min and px <= x_max and py > y_min and py <= y_max) then
|
|
||||||
core.status_view:remove_tooltip()
|
|
||||||
self.tooltip = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- The toolbarview pane is not plugged here but it is added in the
|
|
||||||
-- treeview plugin.
|
|
||||||
|
|
||||||
return ToolbarView
|
|
|
@ -1,4 +1,3 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local common = require "core.common"
|
local common = require "core.common"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
|
@ -6,17 +5,8 @@ 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 default_treeview_size = 200 * SCALE
|
|
||||||
local tooltip_offset = style.font:get_height()
|
|
||||||
local tooltip_border = 1
|
|
||||||
local tooltip_delay = 0.5
|
|
||||||
local tooltip_alpha = 255
|
|
||||||
local tooltip_alpha_rate = 1
|
|
||||||
|
|
||||||
|
config.treeview_size = 200 * SCALE
|
||||||
|
|
||||||
local function get_depth(filename)
|
local function get_depth(filename)
|
||||||
local n = 1
|
local n = 1
|
||||||
|
@ -26,11 +16,6 @@ local function get_depth(filename)
|
||||||
return n
|
return n
|
||||||
end
|
end
|
||||||
|
|
||||||
local function replace_alpha(color, alpha)
|
|
||||||
local r, g, b = table.unpack(color)
|
|
||||||
return { r, g, b, alpha }
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local TreeView = View:extend()
|
local TreeView = View:extend()
|
||||||
|
|
||||||
|
@ -39,45 +24,21 @@ 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 = default_treeview_size
|
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 }
|
self.last = {}
|
||||||
|
|
||||||
self.item_icon_width = 0
|
|
||||||
self.item_text_spacing = 0
|
|
||||||
|
|
||||||
local on_dirmonitor_modify = core.on_dirmonitor_modify
|
|
||||||
function core.on_dirmonitor_modify(dir, filepath)
|
|
||||||
if self.cache[dir.name] then
|
|
||||||
self.cache[dir.name][filepath] = nil
|
|
||||||
end
|
|
||||||
on_dirmonitor_modify(dir, filepath)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:set_target_size(axis, value)
|
function TreeView:get_cached(item, dirname)
|
||||||
if axis == "x" then
|
|
||||||
self.target_size = value
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:get_cached(dir, 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 = {}
|
||||||
self.cache[dirname] = dir_cache
|
self.cache[dirname] = dir_cache
|
||||||
end
|
end
|
||||||
-- to discriminate top directories from regular files or subdirectories
|
local t = dir_cache[item.filename]
|
||||||
-- we add ':' at the end of the top directories' filename. it will be
|
|
||||||
-- used only to identify the entry into the cache.
|
|
||||||
local cache_name = item.filename .. (item.topdir and ":" or "")
|
|
||||||
local t = dir_cache[cache_name]
|
|
||||||
if not t then
|
if not t then
|
||||||
t = {}
|
t = {}
|
||||||
local basename = common.basename(item.filename)
|
local basename = item.filename:match("[^\\/]+$")
|
||||||
if item.topdir then
|
if item.topdir then
|
||||||
t.filename = basename
|
t.filename = basename
|
||||||
t.expanded = true
|
t.expanded = true
|
||||||
|
@ -90,8 +51,7 @@ function TreeView:get_cached(dir, item, dirname)
|
||||||
end
|
end
|
||||||
t.name = basename
|
t.name = basename
|
||||||
t.type = item.type
|
t.type = item.type
|
||||||
t.dir = dir -- points to top level "dir" item
|
dir_cache[item.filename] = t
|
||||||
dir_cache[cache_name] = t
|
|
||||||
end
|
end
|
||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
@ -107,21 +67,21 @@ function TreeView:get_item_height()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:invalidate_cache(dirname)
|
|
||||||
for _, v in pairs(self.cache[dirname]) do
|
|
||||||
v.skip = nil
|
|
||||||
end
|
|
||||||
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
|
||||||
|
for _, v in pairs(self.cache[dir.name]) do
|
||||||
|
v.skip = nil
|
||||||
|
end
|
||||||
|
self.last[dir.name] = dir.files
|
||||||
|
end
|
||||||
end
|
end
|
||||||
dir.is_dirty = false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -129,7 +89,6 @@ end
|
||||||
function TreeView:each_item()
|
function TreeView:each_item()
|
||||||
return coroutine.wrap(function()
|
return coroutine.wrap(function()
|
||||||
self:check_cache()
|
self:check_cache()
|
||||||
local count_lines = 0
|
|
||||||
local ox, oy = self:get_content_offset()
|
local ox, oy = self:get_content_offset()
|
||||||
local y = oy + style.padding.y
|
local y = oy + style.padding.y
|
||||||
local w = self.size.x
|
local w = self.size.x
|
||||||
|
@ -137,17 +96,15 @@ 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
|
|
||||||
y = y + h
|
y = y + h
|
||||||
local i = 1
|
local i = 1
|
||||||
while i <= #dir.files and dir_cached.expanded do
|
while i <= #dir.files and dir_cached.expanded do
|
||||||
local item = dir.files[i]
|
local item = dir.files[i]
|
||||||
local cached = self:get_cached(dir, item, dir.name)
|
local cached = self:get_cached(item, dir.name)
|
||||||
|
|
||||||
coroutine.yield(cached, ox, y, w, h)
|
coroutine.yield(cached, ox, y, w, h)
|
||||||
count_lines = count_lines + 1
|
|
||||||
y = y + h
|
y = y + h
|
||||||
i = i + 1
|
i = i + 1
|
||||||
|
|
||||||
|
@ -165,91 +122,58 @@ function TreeView:each_item()
|
||||||
end
|
end
|
||||||
end -- while files
|
end -- while files
|
||||||
end -- for directories
|
end -- for directories
|
||||||
self.count_lines = count_lines
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:get_text_bounding_box(item, x, y, w, h)
|
function TreeView:on_mouse_moved(px, py)
|
||||||
local icon_width = style.icon_font:get_width("D")
|
self.hovered_item = nil
|
||||||
local xoffset = item.depth * style.padding.x + style.padding.x + icon_width
|
|
||||||
x = x + xoffset
|
|
||||||
w = style.font:get_width(item.name) + 2 * style.padding.x
|
|
||||||
return x, y, w, h
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:on_mouse_moved(px, py, ...)
|
|
||||||
TreeView.super.on_mouse_moved(self, px, py, ...)
|
|
||||||
if self.dragging_scrollbar then return end
|
|
||||||
|
|
||||||
local item_changed, tooltip_changed
|
|
||||||
for item, x,y,w,h in self:each_item() do
|
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
|
|
||||||
self.hovered_item = item
|
self.hovered_item = item
|
||||||
|
|
||||||
x,y,w,h = self:get_text_bounding_box(item, x,y,w,h)
|
|
||||||
if px > x and py > y and px <= x + w and py <= y + h then
|
|
||||||
tooltip_changed = true
|
|
||||||
self.tooltip.x, self.tooltip.y = px, py
|
|
||||||
self.tooltip.begin = system.get_time()
|
|
||||||
end
|
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if not item_changed then self.hovered_item = nil end
|
|
||||||
if not tooltip_changed then self.tooltip.x, self.tooltip.y = nil, nil end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function create_directory_in(item)
|
local function create_directory_in(item)
|
||||||
local path = item.abs_filename
|
local path = item.abs_filename
|
||||||
core.command_view:enter("Create directory in " .. path, function(text)
|
local basename = path:match("[^\\/]+$")
|
||||||
|
core.command_view:enter("Create directory in " .. basename, function(text)
|
||||||
local dirname = path .. PATHSEP .. text
|
local dirname = path .. PATHSEP .. text
|
||||||
local success, err = system.mkdir(dirname)
|
local success, err = system.mkdir(dirname)
|
||||||
if not success then
|
if not success then
|
||||||
core.error("cannot create directory %q: %s", dirname, err)
|
core.error("cannot create directory %q: %s", dirname, err)
|
||||||
end
|
end
|
||||||
item.expanded = true
|
item.expanded = true
|
||||||
|
core.request_project_scan()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:on_mouse_pressed(button, x, y, clicks)
|
function TreeView:on_mouse_pressed(button, x, y)
|
||||||
local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks)
|
if not self.hovered_item then
|
||||||
if caught or button ~= "left" then
|
return
|
||||||
return true
|
elseif self.hovered_item.type == "dir" then
|
||||||
end
|
if keymap.modkeys["shift"] and button == "middle" and self.hovered_item.depth == 0 then
|
||||||
local hovered_item = self.hovered_item
|
core.remove_project_directory(self.hovered_item.abs_filename)
|
||||||
if not hovered_item then
|
elseif keymap.modkeys["alt"] and button == "left" then
|
||||||
return false
|
create_directory_in(self.hovered_item)
|
||||||
elseif hovered_item.type == "dir" then
|
|
||||||
if keymap.modkeys["ctrl"] and button == "left" then
|
|
||||||
create_directory_in(hovered_item)
|
|
||||||
else
|
else
|
||||||
hovered_item.expanded = not hovered_item.expanded
|
self.hovered_item.expanded = not self.hovered_item.expanded
|
||||||
if hovered_item.dir.files_limit then
|
|
||||||
core.update_project_subdir(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
|
|
||||||
core.project_subdir_set_show(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
core.try(function()
|
core.try(function()
|
||||||
if core.last_active_view and core.active_view == self then
|
core.root_view:open_doc(core.open_doc(self.hovered_item.abs_filename))
|
||||||
core.set_active_view(core.last_active_view)
|
|
||||||
end
|
|
||||||
local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename)
|
|
||||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
return true
|
|
||||||
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 config.treeview_size or 0
|
||||||
if self.init_size then
|
if self.init_size then
|
||||||
self.size.x = dest
|
self.size.x = dest
|
||||||
self.init_size = false
|
self.init_size = false
|
||||||
|
@ -257,133 +181,51 @@ function TreeView:update()
|
||||||
self:move_towards(self.size, "x", dest)
|
self:move_towards(self.size, "x", dest)
|
||||||
end
|
end
|
||||||
|
|
||||||
local duration = system.get_time() - self.tooltip.begin
|
|
||||||
if self.hovered_item and self.tooltip.x and duration > tooltip_delay then
|
|
||||||
self:move_towards(self.tooltip, "alpha", tooltip_alpha, tooltip_alpha_rate)
|
|
||||||
else
|
|
||||||
self.tooltip.alpha = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
self.item_icon_width = style.icon_font:get_width("D")
|
|
||||||
self.item_text_spacing = style.icon_font:get_width("f") / 2
|
|
||||||
|
|
||||||
TreeView.super.update(self)
|
TreeView.super.update(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function TreeView:get_scrollable_size()
|
|
||||||
return self.count_lines and self:get_item_height() * (self.count_lines + 1) or math.huge
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function TreeView:draw_tooltip()
|
|
||||||
local text = common.home_encode(self.hovered_item.abs_filename)
|
|
||||||
local w, h = style.font:get_width(text), style.font:get_height(text)
|
|
||||||
|
|
||||||
local x, y = self.tooltip.x + tooltip_offset, self.tooltip.y + tooltip_offset
|
|
||||||
w, h = w + style.padding.x, h + style.padding.y
|
|
||||||
|
|
||||||
if x + w > core.root_view.root_node.size.x then -- check if we can span right
|
|
||||||
x = x - w -- span left instead
|
|
||||||
end
|
|
||||||
|
|
||||||
local bx, by = x - tooltip_border, y - tooltip_border
|
|
||||||
local bw, bh = w + 2 * tooltip_border, h + 2 * tooltip_border
|
|
||||||
renderer.draw_rect(bx, by, bw, bh, replace_alpha(style.text, self.tooltip.alpha))
|
|
||||||
renderer.draw_rect(x, y, w, h, replace_alpha(style.background2, self.tooltip.alpha))
|
|
||||||
common.draw_text(style.font, replace_alpha(style.text, self.tooltip.alpha), text, "center", x, y, w, h)
|
|
||||||
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
|
|
||||||
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()
|
||||||
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.font:get_width(" ") * 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.abs_filename == active_filename,
|
|
||||||
item == self.hovered_item,
|
|
||||||
x, y, w, h)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:draw_scrollbar()
|
-- highlight active_view doc
|
||||||
if self.hovered_item and self.tooltip.alpha > 0 then
|
if item.abs_filename == active_filename then
|
||||||
core.root_view:defer_draw(self.draw_tooltip, self)
|
color = style.accent
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -391,203 +233,13 @@ 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()
|
||||||
local treeview_node = node:split("left", view, {x = true}, true)
|
node:split("left", view, true)
|
||||||
|
|
||||||
-- The toolbarview plugin is special because it is plugged inside
|
-- register commands and keymap
|
||||||
-- a treeview pane which is itelf provided in a plugin.
|
|
||||||
-- We therefore break the usual plugin's logic that would require each
|
|
||||||
-- plugin to be independent of each other. In addition it is not the
|
|
||||||
-- plugin module that plug itself in the active node but it is plugged here
|
|
||||||
-- in the treeview node.
|
|
||||||
local toolbar_view = nil
|
|
||||||
local toolbar_plugin, ToolbarView = core.try(require, "plugins.toolbarview")
|
|
||||||
if config.plugins.toolbarview ~= false and toolbar_plugin then
|
|
||||||
toolbar_view = ToolbarView()
|
|
||||||
treeview_node:split("down", toolbar_view, {y = true})
|
|
||||||
local min_toolbar_width = toolbar_view:get_min_width()
|
|
||||||
view:set_target_size("x", math.max(default_treeview_size, min_toolbar_width))
|
|
||||||
command.add(nil, {
|
|
||||||
["toolbar:toggle"] = function()
|
|
||||||
toolbar_view:toggle_visible()
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add a context menu to the treeview
|
|
||||||
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.
|
|
||||||
if button == "right" then
|
|
||||||
view.tooltip.alpha = 0
|
|
||||||
view.tooltip.x, view.tooltip.y = nil, nil
|
|
||||||
end
|
|
||||||
local handled = menu:on_mouse_pressed(button, x, y, clicks)
|
|
||||||
return handled or on_view_mouse_pressed(button, x, y, clicks)
|
|
||||||
end
|
|
||||||
|
|
||||||
function RootView:update(...)
|
|
||||||
root_view_update(self, ...)
|
|
||||||
menu:update()
|
|
||||||
end
|
|
||||||
|
|
||||||
function RootView:draw(...)
|
|
||||||
root_view_draw(self, ...)
|
|
||||||
menu:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_project_folder(path)
|
|
||||||
return 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" },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
-- 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})
|
|
||||||
|
|
||||||
|
|
||||||
command.add(function() return view.hovered_item ~= nil end, {
|
|
||||||
["treeview:rename"] = function()
|
|
||||||
local old_filename = view.hovered_item.filename
|
|
||||||
local old_abs_filename = view.hovered_item.abs_filename
|
|
||||||
core.command_view:set_text(old_filename)
|
|
||||||
core.command_view:enter("Rename", function(filename)
|
|
||||||
filename = core.normalize_to_project_dir(filename)
|
|
||||||
local abs_filename = core.project_absolute_path(filename)
|
|
||||||
local res, err = os.rename(old_abs_filename, abs_filename)
|
|
||||||
if res then -- successfully renamed
|
|
||||||
for _, doc in ipairs(core.docs) do
|
|
||||||
if doc.abs_filename and old_abs_filename == doc.abs_filename then
|
|
||||||
doc:set_filename(filename, abs_filename) -- make doc point to the new filename
|
|
||||||
doc:reset_syntax()
|
|
||||||
break -- only first needed
|
|
||||||
end
|
|
||||||
end
|
|
||||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
|
||||||
else
|
|
||||||
core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
|
|
||||||
end
|
|
||||||
end, common.path_suggest)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:new-file"] = function()
|
|
||||||
if not is_project_folder(view.hovered_item.abs_filename) then
|
|
||||||
core.command_view:set_text(view.hovered_item.filename .. "/")
|
|
||||||
end
|
|
||||||
core.command_view:enter("Filename", function(filename)
|
|
||||||
local doc_filename = core.project_dir .. PATHSEP .. filename
|
|
||||||
local file = io.open(doc_filename, "a+")
|
|
||||||
file:write("")
|
|
||||||
file:close()
|
|
||||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
|
||||||
core.log("Created %s", doc_filename)
|
|
||||||
end, common.path_suggest)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:new-folder"] = function()
|
|
||||||
if not is_project_folder(view.hovered_item.abs_filename) then
|
|
||||||
core.command_view:set_text(view.hovered_item.filename .. "/")
|
|
||||||
end
|
|
||||||
core.command_view:enter("Folder Name", function(filename)
|
|
||||||
local dir_path = core.project_dir .. PATHSEP .. filename
|
|
||||||
common.mkdirp(dir_path)
|
|
||||||
core.log("Created %s", dir_path)
|
|
||||||
end, common.path_suggest)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:delete"] = function()
|
|
||||||
local filename = view.hovered_item.abs_filename
|
|
||||||
local relfilename = view.hovered_item.filename
|
|
||||||
local file_info = system.get_file_info(filename)
|
|
||||||
local file_type = file_info.type == "dir" and "Directory" or "File"
|
|
||||||
-- Ask before deleting
|
|
||||||
local opt = {
|
|
||||||
{ font = style.font, text = "Yes", default_yes = true },
|
|
||||||
{ font = style.font, text = "No" , default_no = true }
|
|
||||||
}
|
|
||||||
core.nag_view:show(
|
|
||||||
string.format("Delete %s", file_type),
|
|
||||||
string.format(
|
|
||||||
"Are you sure you want to delete the %s?\n%s: %s",
|
|
||||||
file_type:lower(), file_type, relfilename
|
|
||||||
),
|
|
||||||
opt,
|
|
||||||
function(item)
|
|
||||||
if item.text == "Yes" then
|
|
||||||
if file_info.type == "dir" then
|
|
||||||
local deleted, error, path = common.rm(filename, true)
|
|
||||||
if not deleted then
|
|
||||||
core.error("Error: %s - \"%s\" ", error, path)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local removed, error = os.remove(filename)
|
|
||||||
if not removed then
|
|
||||||
core.error("Error: %s - \"%s\"", error, filename)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
core.log("Deleted \"%s\"", filename)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
|
|
||||||
["treeview:open-in-system"] = function()
|
|
||||||
local hovered_item = view.hovered_item
|
|
||||||
|
|
||||||
if PLATFORM == "Windows" then
|
|
||||||
system.exec(string.format("start \"\" %q", hovered_item.abs_filename))
|
|
||||||
elseif string.find(PLATFORM, "Mac") then
|
|
||||||
system.exec(string.format("open %q", hovered_item.abs_filename))
|
|
||||||
elseif PLATFORM == "Linux" then
|
|
||||||
system.exec(string.format("xdg-open %q", hovered_item.abs_filename))
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
keymap.add { ["ctrl+\\"] = "treeview:toggle" }
|
keymap.add { ["ctrl+\\"] = "treeview:toggle" }
|
||||||
|
|
||||||
-- Return the treeview with toolbar and contextmenu to allow
|
|
||||||
-- user or plugin modifications
|
|
||||||
view.toolbar = toolbar_view
|
|
||||||
view.contextmenu = menu
|
|
||||||
|
|
||||||
return view
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
local core = require "core"
|
||||||
local command = require "core.command"
|
local command = require "core.command"
|
||||||
local Doc = require "core.doc"
|
local Doc = require "core.doc"
|
||||||
|
|
|
@ -1,240 +0,0 @@
|
||||||
-- mod-version:2 -- lite-xl 2.0
|
|
||||||
local core = require "core"
|
|
||||||
local common = require "core.common"
|
|
||||||
local DocView = require "core.docview"
|
|
||||||
local LogView = require "core.logview"
|
|
||||||
|
|
||||||
|
|
||||||
local function workspace_files_for(project_dir)
|
|
||||||
local basename = common.basename(project_dir)
|
|
||||||
local workspace_dir = USERDIR .. PATHSEP .. "ws"
|
|
||||||
local info_wsdir = system.get_file_info(workspace_dir)
|
|
||||||
if not info_wsdir then
|
|
||||||
local ok, err = system.mkdir(workspace_dir)
|
|
||||||
if not ok then
|
|
||||||
error("cannot create workspace directory: \"" .. err .. "\"")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return coroutine.wrap(function()
|
|
||||||
local files = system.list_dir(workspace_dir) or {}
|
|
||||||
local n = #basename
|
|
||||||
for _, file in ipairs(files) do
|
|
||||||
if file:sub(1, n) == basename then
|
|
||||||
local id = tonumber(file:sub(n + 1):match("^-(%d+)$"))
|
|
||||||
if id then
|
|
||||||
coroutine.yield(workspace_dir .. PATHSEP .. file, id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function consume_workspace_file(project_dir)
|
|
||||||
for filename, id in workspace_files_for(project_dir) do
|
|
||||||
local load_f = loadfile(filename)
|
|
||||||
local workspace = load_f and load_f()
|
|
||||||
if workspace and workspace.path == project_dir then
|
|
||||||
os.remove(filename)
|
|
||||||
return workspace
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function get_workspace_filename(project_dir)
|
|
||||||
local id_list = {}
|
|
||||||
for filename, id in workspace_files_for(project_dir) do
|
|
||||||
id_list[id] = true
|
|
||||||
end
|
|
||||||
local id = 1
|
|
||||||
while id_list[id] do
|
|
||||||
id = id + 1
|
|
||||||
end
|
|
||||||
local basename = common.basename(project_dir)
|
|
||||||
return USERDIR .. PATHSEP .. "ws" .. PATHSEP .. basename .. "-" .. tostring(id)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function has_no_locked_children(node)
|
|
||||||
if node.locked then return false end
|
|
||||||
if node.type == "leaf" then return true end
|
|
||||||
return has_no_locked_children(node.a) and has_no_locked_children(node.b)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function get_unlocked_root(node)
|
|
||||||
if node.type == "leaf" then
|
|
||||||
return not node.locked and node
|
|
||||||
end
|
|
||||||
if has_no_locked_children(node) then
|
|
||||||
return node
|
|
||||||
end
|
|
||||||
return get_unlocked_root(node.a) or get_unlocked_root(node.b)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function save_view(view)
|
|
||||||
local mt = getmetatable(view)
|
|
||||||
if mt == DocView then
|
|
||||||
return {
|
|
||||||
type = "doc",
|
|
||||||
active = (core.active_view == view),
|
|
||||||
filename = view.doc.filename,
|
|
||||||
selection = { view.doc:get_selection() },
|
|
||||||
scroll = { x = view.scroll.to.x, y = view.scroll.to.y },
|
|
||||||
text = not view.doc.filename and view.doc:get_text(1, 1, math.huge, math.huge)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
if mt == LogView then return end
|
|
||||||
for name, mod in pairs(package.loaded) do
|
|
||||||
if mod == mt then
|
|
||||||
return {
|
|
||||||
type = "view",
|
|
||||||
active = (core.active_view == view),
|
|
||||||
module = name
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function load_view(t)
|
|
||||||
if t.type == "doc" then
|
|
||||||
local dv
|
|
||||||
if not t.filename then
|
|
||||||
-- document not associated to a file
|
|
||||||
dv = DocView(core.open_doc())
|
|
||||||
if t.text then dv.doc:insert(1, 1, t.text) end
|
|
||||||
else
|
|
||||||
-- we have a filename, try to read the file
|
|
||||||
local ok, doc = pcall(core.open_doc, t.filename)
|
|
||||||
if ok then
|
|
||||||
dv = DocView(doc)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- doc view "dv" can be nil here if the filename associated to the document
|
|
||||||
-- cannot be read.
|
|
||||||
if dv and dv.doc then
|
|
||||||
dv.doc:set_selection(table.unpack(t.selection))
|
|
||||||
dv.last_line, dv.last_col = dv.doc:get_selection()
|
|
||||||
dv.scroll.x, dv.scroll.to.x = t.scroll.x, t.scroll.x
|
|
||||||
dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y
|
|
||||||
end
|
|
||||||
return dv
|
|
||||||
end
|
|
||||||
return require(t.module)()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function save_node(node)
|
|
||||||
local res = {}
|
|
||||||
res.type = node.type
|
|
||||||
if node.type == "leaf" then
|
|
||||||
res.views = {}
|
|
||||||
for _, view in ipairs(node.views) do
|
|
||||||
local t = save_view(view)
|
|
||||||
if t then
|
|
||||||
table.insert(res.views, t)
|
|
||||||
if node.active_view == view then
|
|
||||||
res.active_view = #res.views
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
res.divider = node.divider
|
|
||||||
res.a = save_node(node.a)
|
|
||||||
res.b = save_node(node.b)
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function load_node(node, t)
|
|
||||||
if t.type == "leaf" then
|
|
||||||
local res
|
|
||||||
local active_view
|
|
||||||
for i, v in ipairs(t.views) do
|
|
||||||
local view = load_view(v)
|
|
||||||
if view then
|
|
||||||
if v.active then res = view end
|
|
||||||
node:add_view(view)
|
|
||||||
if t.active_view == i then
|
|
||||||
active_view = view
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if active_view then
|
|
||||||
node:set_active_view(active_view)
|
|
||||||
end
|
|
||||||
return res
|
|
||||||
else
|
|
||||||
node:split(t.type == "hsplit" and "right" or "down")
|
|
||||||
node.divider = t.divider
|
|
||||||
local res1 = load_node(node.a, t.a)
|
|
||||||
local res2 = load_node(node.b, t.b)
|
|
||||||
return res1 or res2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function save_directories()
|
|
||||||
local project_dir = core.project_dir
|
|
||||||
local dir_list = {}
|
|
||||||
for i = 2, #core.project_directories do
|
|
||||||
dir_list[#dir_list + 1] = common.relative_path(project_dir, core.project_directories[i].name)
|
|
||||||
end
|
|
||||||
return dir_list
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function save_workspace()
|
|
||||||
local root = get_unlocked_root(core.root_view.root_node)
|
|
||||||
local workspace_filename = get_workspace_filename(core.project_dir)
|
|
||||||
local fp = io.open(workspace_filename, "w")
|
|
||||||
if fp then
|
|
||||||
local node_text = common.serialize(save_node(root))
|
|
||||||
local dir_text = common.serialize(save_directories())
|
|
||||||
fp:write(string.format("return { path = %q, documents = %s, directories = %s }\n", core.project_dir, node_text, dir_text))
|
|
||||||
fp:close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function load_workspace()
|
|
||||||
local workspace = consume_workspace_file(core.project_dir)
|
|
||||||
if workspace then
|
|
||||||
local root = get_unlocked_root(core.root_view.root_node)
|
|
||||||
local active_view = load_node(root, workspace.documents)
|
|
||||||
if active_view then
|
|
||||||
core.set_active_view(active_view)
|
|
||||||
end
|
|
||||||
for i, dir_name in ipairs(workspace.directories) do
|
|
||||||
core.add_project_directory(system.absolute_path(dir_name))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local run = core.run
|
|
||||||
|
|
||||||
function core.run(...)
|
|
||||||
if #core.docs == 0 then
|
|
||||||
core.try(load_workspace)
|
|
||||||
|
|
||||||
local on_quit_project = core.on_quit_project
|
|
||||||
function core.on_quit_project()
|
|
||||||
core.try(save_workspace)
|
|
||||||
on_quit_project()
|
|
||||||
end
|
|
||||||
|
|
||||||
local on_enter_project = core.on_enter_project
|
|
||||||
function core.on_enter_project(new_dir)
|
|
||||||
on_enter_project(new_dir)
|
|
||||||
core.try(load_workspace)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
core.run = run
|
|
||||||
return core.run(...)
|
|
||||||
end
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
-- put user settings here
|
||||||
|
-- this module will be loaded after everything else when the application starts
|
||||||
|
-- it will be automatically reloaded when saved
|
||||||
|
|
||||||
|
local core = require "core"
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
local config = require "core.config"
|
||||||
|
local style = require "core.style"
|
||||||
|
|
||||||
|
-- light theme:
|
||||||
|
-- core.reload_module("colors.summer")
|
||||||
|
|
||||||
|
-- key binding:
|
||||||
|
-- keymap.add { ["ctrl+escape"] = "core:quit" }
|
||||||
|
|
||||||
|
-- customize fonts:
|
||||||
|
-- style.font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 14 * SCALE)
|
||||||
|
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/monospace.ttf", 13.5 * SCALE)
|
||||||
|
--
|
||||||
|
-- fonts used by the editor:
|
||||||
|
-- style.font : user interface
|
||||||
|
-- style.big_font : big text in welcome screen
|
||||||
|
-- style.icon_font : icons
|
||||||
|
-- style.code_font : code
|
||||||
|
--
|
||||||
|
-- the function to load the font accept a 3rd optional argument like:
|
||||||
|
--
|
||||||
|
-- {antialiasing="grayscale", hinting="full"}
|
||||||
|
--
|
||||||
|
-- possible values are:
|
||||||
|
-- antialiasing: grayscale, subpixel
|
||||||
|
-- hinting: none, slight, full
|
|
@ -10,63 +10,28 @@ copy_directory_from_repo () {
|
||||||
fi
|
fi
|
||||||
local dirname="$1"
|
local dirname="$1"
|
||||||
local destdir="$2"
|
local destdir="$2"
|
||||||
git archive "$lite_branch" "$dirname" --format=tar | tar xf - -C "$destdir" "${tar_options[@]}"
|
git archive master "$dirname" --format=tar | tar xf - -C "$destdir" "${tar_options[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
lite_copy_third_party_modules () {
|
lite_copy_third_party_modules () {
|
||||||
local build="$1"
|
local build="$1"
|
||||||
curl --retry 5 --retry-delay 3 --insecure -L "https://github.com/lite-xl/lite-xl-colors/archive/master.zip" -o "$build/lite-xl-colors.zip" || exit 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"
|
mkdir -p "$build/third/data/colors" "$build/third/data/plugins"
|
||||||
unzip -qq "$build/lite-xl-colors.zip" -d "$build"
|
unzip -qq "$build/rxi-lite-colors.zip" -d "$build"
|
||||||
mv "$build/lite-xl-colors-master/colors" "$build/third/data"
|
mv "$build/lite-colors-master/colors" "$build/third/data"
|
||||||
rm -fr "$build/lite-xl-colors-master"
|
rm -fr "$build/lite-colors-master"
|
||||||
rm "$build/lite-xl-colors.zip"
|
rm "$build/rxi-lite-colors.zip"
|
||||||
}
|
}
|
||||||
|
|
||||||
lite_branch=master
|
assets=($(wget --no-check-certificate -q -nv -O- https://api.github.com/repos/franko/lite-xl/releases/latest | grep "browser_download_url" | cut -d '"' -f 4))
|
||||||
while [ ! -z ${1+x} ]; do
|
|
||||||
case "$1" in
|
|
||||||
-dir)
|
|
||||||
use_dir="$(realpath $2)"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-branch)
|
|
||||||
lite_branch="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "unknown option: $1"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
wget="wget --retry-connrefused --waitretry=1 --read-timeout=20 --no-check-certificate"
|
|
||||||
|
|
||||||
workdir=".repackage"
|
workdir=".repackage"
|
||||||
rm -fr "$workdir" && mkdir "$workdir" && pushd "$workdir"
|
rm -fr "$workdir" && mkdir "$workdir" && pushd "$workdir"
|
||||||
|
|
||||||
fetch_packages_from_github () {
|
for url in "${assets[@]}"; do
|
||||||
assets=($($wget -q -nv -O- https://api.github.com/repos/lite-xl/lite-xl/releases/latest | grep "browser_download_url" | cut -d '"' -f 4))
|
|
||||||
|
|
||||||
for url in "${assets[@]}"; do
|
|
||||||
echo "getting: $url"
|
echo "getting: $url"
|
||||||
$wget -q "$url" || exit 1
|
wget -q --no-check-certificate "$url"
|
||||||
done
|
done
|
||||||
}
|
|
||||||
|
|
||||||
fetch_packages_from_dir () {
|
|
||||||
for file in "$1"/*.zip "$1"/*.tar.* ; do
|
|
||||||
echo "copying file $file"
|
|
||||||
cp "$file" .
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ -z ${use_dir+x} ]; then
|
|
||||||
fetch_packages_from_github
|
|
||||||
else
|
|
||||||
fetch_packages_from_dir "$use_dir"
|
|
||||||
fi
|
|
||||||
|
|
||||||
lite_copy_third_party_modules "."
|
lite_copy_third_party_modules "."
|
||||||
|
|
||||||
|
@ -77,9 +42,11 @@ for filename in $(ls -1 *.zip *.tar.*); do
|
||||||
tar xf "$filename"
|
tar xf "$filename"
|
||||||
fi
|
fi
|
||||||
rm "$filename"
|
rm "$filename"
|
||||||
find lite-xl -name lite -exec chmod a+x '{}' \;
|
if [ -f lite-xl/bin/lite ]; then
|
||||||
start_file=$(find lite-xl -name start.lua)
|
chmod a+x lite-xl/bin/lite
|
||||||
lite_version=$(cat "$start_file" | awk 'match($0, /^\s*VERSION\s*=\s*"(.+)"/, a) { print(a[1]) }')
|
elif [ -f lite-xl/lite ]; then
|
||||||
|
chmod a+x lite-xl/lite
|
||||||
|
fi
|
||||||
xcoredir="$(find lite-xl -type d -name 'core')"
|
xcoredir="$(find lite-xl -type d -name 'core')"
|
||||||
coredir="$(dirname $xcoredir)"
|
coredir="$(dirname $xcoredir)"
|
||||||
echo "coredir: $coredir"
|
echo "coredir: $coredir"
|
||||||
|
@ -88,7 +55,6 @@ for filename in $(ls -1 *.zip *.tar.*); do
|
||||||
rm -fr "$coredir/$module_name"
|
rm -fr "$coredir/$module_name"
|
||||||
(cd .. && copy_directory_from_repo --strip-components=1 "data/$module_name" "$workdir/$coredir")
|
(cd .. && copy_directory_from_repo --strip-components=1 "data/$module_name" "$workdir/$coredir")
|
||||||
done
|
done
|
||||||
sed -i "s/@PROJECT_VERSION@/$lite_version/g" "$start_file"
|
|
||||||
for module_name in plugins colors; do
|
for module_name in plugins colors; do
|
||||||
cp -r "third/data/$module_name" "$coredir"
|
cp -r "third/data/$module_name" "$coredir"
|
||||||
done
|
done
|
|
@ -0,0 +1,88 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
|
||||||
|
option_portable=off
|
||||||
|
option_copy=on
|
||||||
|
pargs=()
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-portable)
|
||||||
|
option_portable=on
|
||||||
|
;;
|
||||||
|
-keep)
|
||||||
|
option_copy=off
|
||||||
|
;;
|
||||||
|
-global)
|
||||||
|
option_global=on
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "error: unknown option \"$1\""
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
pargs+=("$1")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${#pargs[@]}" -lt 1 ]; then
|
||||||
|
echo "usage: $0 [options] <build-dir>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "mingw"* ]]; then
|
||||||
|
run_windows=yes
|
||||||
|
fi
|
||||||
|
|
||||||
|
rundir=".run"
|
||||||
|
if [ "$option_portable" == on ]; then
|
||||||
|
bindir="$rundir"
|
||||||
|
datadir="$rundir/data"
|
||||||
|
else
|
||||||
|
bindir="$rundir/bin"
|
||||||
|
datadir="$rundir/share/lite-xl"
|
||||||
|
fi
|
||||||
|
|
||||||
|
userdir="$(realpath "$rundir")"
|
||||||
|
builddir="${pargs[0]}"
|
||||||
|
|
||||||
|
build_lite () {
|
||||||
|
echo "running ninja"
|
||||||
|
ninja -C "$builddir"
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_lite_build () {
|
||||||
|
echo "copying lite executable and data"
|
||||||
|
rm -fr "$rundir"
|
||||||
|
mkdir -p "$bindir" "$datadir"
|
||||||
|
if [ ! -z ${run_windows+x} ]; then
|
||||||
|
cp "$builddir/src/lite.exe" "$bindir"
|
||||||
|
else
|
||||||
|
cp "$builddir/src/lite" "$bindir"
|
||||||
|
fi
|
||||||
|
for module_name in core plugins colors fonts; do
|
||||||
|
cp -r "data/$module_name" "$datadir"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
run_lite () {
|
||||||
|
if [ ! -z ${option_global+x} ]; then
|
||||||
|
echo "running \"lite ${pargs[@]:1}\""
|
||||||
|
exec "$bindir/lite" "${pargs[@]:1}"
|
||||||
|
else
|
||||||
|
echo "running \"lite ${pargs[@]:1}\" with local HOME"
|
||||||
|
if [ ! -z ${run_windows+x} ]; then
|
||||||
|
USERPROFILE="$userdir" exec "$bindir/lite" "${pargs[@]:1}"
|
||||||
|
else
|
||||||
|
HOME="$userdir" exec "$bindir/lite" "${pargs[@]:1}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $option_copy == on ]; then
|
||||||
|
build_lite
|
||||||
|
copy_lite_build
|
||||||
|
fi
|
||||||
|
run_lite
|
|
@ -0,0 +1,178 @@
|
||||||
|
# lite
|
||||||
|
|
||||||
|
![screenshot](https://user-images.githubusercontent.com/3920290/81471642-6c165880-91ea-11ea-8cd1-fae7ae8f0bc4.png)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Lite is a lightweight text editor written mostly in Lua — it aims to provide
|
||||||
|
something practical, pretty, *small* and fast, implemented as simply as
|
||||||
|
possible; easy to modify and extend, or to use without doing either.
|
||||||
|
|
||||||
|
Lite XL is based on the Lite editor itself and provide some enhancements
|
||||||
|
while remaining perfectly compatible with Lite.
|
||||||
|
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
Lite works using a *project directory* — this is the directory where your
|
||||||
|
project's code and other data resides.
|
||||||
|
|
||||||
|
To open lite with a specific project directory the directory name can be passed
|
||||||
|
as a command-line argument *(`.` can be passed to use the current directory)* or
|
||||||
|
the directory can be dragged onto either the lite executable or a running
|
||||||
|
instance of lite.
|
||||||
|
|
||||||
|
Once started the project directory can be changed using the command
|
||||||
|
`core:change-project-folder`. The command will close all the documents
|
||||||
|
currently opened and switch to the new project directory.
|
||||||
|
|
||||||
|
If you want to open a project directory in a new window the command
|
||||||
|
`core:open-project-folder` will open a new editor window with the selected
|
||||||
|
project directory.
|
||||||
|
|
||||||
|
The main way of opening files in lite is through the `core:find-file` command
|
||||||
|
— this provides a fuzzy finder over all of the project's files and can be
|
||||||
|
opened using the **`ctrl+p`** shortcut by default.
|
||||||
|
|
||||||
|
Commands can be run using keyboard shortcuts, or by using the `core:find-command`
|
||||||
|
command bound to **`ctrl+shift+p`** by default. For example, pressing
|
||||||
|
`ctrl+shift+p` and typing `newdoc` then pressing `return` would open a new
|
||||||
|
document. The current keyboard shortcut for a command can be seen to the right
|
||||||
|
of the command name on the command finder, thus to find the shortcut for a command
|
||||||
|
`ctrl+shift+p` can be pressed and the command name typed.
|
||||||
|
|
||||||
|
|
||||||
|
## User Module
|
||||||
|
lite can be configured through use of the user module. The user module can be
|
||||||
|
used for changing options in the config module, adding additional key bindings,
|
||||||
|
loading custom color themes, modifying the style or changing any other part of
|
||||||
|
lite to your personal preference.
|
||||||
|
|
||||||
|
The user module is loaded by lite when the application starts, after the plugins
|
||||||
|
have been loaded.
|
||||||
|
|
||||||
|
The user module can be modified by running the `core:open-user-module` command
|
||||||
|
or otherwise directly opening the `$HOME/.config/lite-xl/init.lua` file.
|
||||||
|
|
||||||
|
On Windows, the variable `$USERPROFILE` will be used instead of
|
||||||
|
`$HOME`.
|
||||||
|
|
||||||
|
Please note that Lite XL differs from the standard Lite editor for the location
|
||||||
|
of the user's module.
|
||||||
|
|
||||||
|
## Project Module
|
||||||
|
The project module is an optional module which is loaded from the current
|
||||||
|
project's directory when lite is started. Project modules can be useful for
|
||||||
|
things like adding custom commands for project-specific build systems, or
|
||||||
|
loading project-specific plugins.
|
||||||
|
|
||||||
|
The project module is loaded by lite when the application starts, after both the
|
||||||
|
plugins and user module have been loaded.
|
||||||
|
|
||||||
|
The project module can be edited by running the `core:open-project-module`
|
||||||
|
command — if the module does not exist for the current project when the
|
||||||
|
command is run it will be created.
|
||||||
|
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
Commands in lite are used both through the command finder (`ctrl+shift+p`) and
|
||||||
|
by lite's keyboard shortcut system. Commands consist of 3 components:
|
||||||
|
* **Name** — The command name in the form of `namespace:action-name`, for
|
||||||
|
example: `doc:select-all`
|
||||||
|
* **Predicate** — A function that returns true if the command can be ran, for
|
||||||
|
example, for any document commands the predicate checks whether the active
|
||||||
|
view is a document
|
||||||
|
* **Function** — The function which performs the command itself
|
||||||
|
|
||||||
|
Commands can be added using the `command.add` function provided by the
|
||||||
|
`core.command` module:
|
||||||
|
```lua
|
||||||
|
local core = require "core"
|
||||||
|
local command = require "core.command"
|
||||||
|
|
||||||
|
command.add("core.docview", {
|
||||||
|
["doc:save"] = function()
|
||||||
|
core.active_view.doc:save()
|
||||||
|
core.log("Saved '%s', core.active_view.doc.filename)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Commands can be performed programatically (eg. from another command or by your
|
||||||
|
user module) by calling the `command.perform` function after requiring the
|
||||||
|
`command` module:
|
||||||
|
```lua
|
||||||
|
local command = require "core.command"
|
||||||
|
command.perform "core:quit"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Keymap
|
||||||
|
All keyboard shortcuts in lite are handled by the `core.keymap` module. A key
|
||||||
|
binding in lite maps a "stroke" (eg. `ctrl+q`) to one or more commands (eg.
|
||||||
|
`core:quit`). When the shortcut is pressed lite will iterate each command
|
||||||
|
assigned to that key and run the *predicate function* for that command — if the
|
||||||
|
predicate passes it stops iterating and runs the command.
|
||||||
|
|
||||||
|
An example of where this used is the default binding of the `tab` key:
|
||||||
|
``` lua
|
||||||
|
["tab"] = { "command:complete", "doc:indent" },
|
||||||
|
```
|
||||||
|
When tab is pressed the `command:complete` command is attempted which will only
|
||||||
|
succeed if the command-input at the bottom of the window is active. Otherwise
|
||||||
|
the `doc:indent` command is attempted which will only succeed if we have a
|
||||||
|
document as our active view.
|
||||||
|
|
||||||
|
A new mapping can be added by your user module as follows:
|
||||||
|
```lua
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
keymap.add { ["ctrl+q"] = "core:quit" }
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
Plugins in lite are normal lua modules and are treated as such — no
|
||||||
|
complicated plugin manager is provided, and, once a plugin is loaded, it is never
|
||||||
|
expected be to have to unload itself.
|
||||||
|
|
||||||
|
To install a plugin simply drop it in the `plugins` directory in the user
|
||||||
|
module directory.
|
||||||
|
When Lite XL starts it will first load the plugins included in the data directory
|
||||||
|
and will then loads the plugins located in the user module directory.
|
||||||
|
|
||||||
|
To uninstall a plugin the
|
||||||
|
plugin file can be deleted — any plugin (including those included with lite's
|
||||||
|
default installation) can be deleted to remove its functionality.
|
||||||
|
|
||||||
|
If you want to load a plugin only under a certain circumstance (for example,
|
||||||
|
only on a given project) the plugin can be placed somewhere other than the
|
||||||
|
`plugins` directory so that it is not automatically loaded. The plugin can
|
||||||
|
then be loaded manually as needed by using the `require` function.
|
||||||
|
|
||||||
|
Plugins can be downloaded from the [plugins repository](https://github.com/rxi/lite-plugins).
|
||||||
|
|
||||||
|
|
||||||
|
## Restarting the editor
|
||||||
|
|
||||||
|
If you modifies the user configuration file or some of the Lua implementation files you may
|
||||||
|
restart the editor using the command `core:restart`.
|
||||||
|
All the application will be restarting by keeping the window that is already in use.
|
||||||
|
|
||||||
|
|
||||||
|
## Color Themes
|
||||||
|
Colors themes in lite are lua modules which overwrite the color fields of lite's
|
||||||
|
`core.style` module.
|
||||||
|
Pre-defined color methods are located in the `colors` folder in the data directory.
|
||||||
|
Additional color themes can be installed in the user's directory in a folder named
|
||||||
|
`colors`.
|
||||||
|
|
||||||
|
A color theme can be set by requiring it in your user module:
|
||||||
|
```lua
|
||||||
|
core.reload_module "colors.winter"
|
||||||
|
```
|
||||||
|
|
||||||
|
In the Lite editor the function `require` is used instead of `core.reload_module`.
|
||||||
|
In Lite XL `core.reload_module` should be used to ensure that the color module
|
||||||
|
is actually reloaded when saving the user's configuration file.
|
||||||
|
|
||||||
|
Color themes can be downloaded from the [color themes repository](https://github.com/rxi/lite-colors).
|
||||||
|
They are included with Lite XL release packages.
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
# Interface Files
|
|
||||||
|
|
||||||
This directory holds the documentation for the Lua C API that
|
|
||||||
is hidden in the C source files of Lite. The idea of these files
|
|
||||||
is to serve you as a quick reference about the functionality
|
|
||||||
that is not written in Lua it self. Please note that they
|
|
||||||
don't have any real code, just metadata or annotations.
|
|
||||||
|
|
||||||
Also, these interfaces are using
|
|
||||||
[EmmyLua annotation syntax](https://emmylua.github.io/annotation.html)
|
|
||||||
which is supported by LSP servers like the
|
|
||||||
[Sumneko Lua LSP](https://github.com/sumneko/lua-language-server).
|
|
||||||
This means that you can get nice code autocompletion and descriptions
|
|
||||||
of Lite core libraries and symbols when developing plugins or adding
|
|
||||||
any options to your **User Module File** (init.lua).
|
|
||||||
|
|
||||||
## The Base Core
|
|
||||||
|
|
||||||
Most of the code that is written in Lua for Lite is powered by the exposed
|
|
||||||
C API in the four namespaces that follow:
|
|
||||||
|
|
||||||
* [system](api/system.lua)
|
|
||||||
* [renderer](api/renderer.lua)
|
|
||||||
* [regex](api/regex.lua)
|
|
||||||
* [process](api/process.lua)
|
|
||||||
|
|
||||||
Finally, all global variables are documented in the file named
|
|
||||||
[globals.lua](api/globals.lua).
|
|
|
@ -1,21 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---The command line arguments given to lite.
|
|
||||||
---@type table<integer, string>
|
|
||||||
ARGS = {}
|
|
||||||
|
|
||||||
---The current operating system.
|
|
||||||
---@type string | "'Windows'" | "'Mac OS X'" | "'Linux'" | "'iOS'" | "'Android'"
|
|
||||||
PLATFORM = "Operating System"
|
|
||||||
|
|
||||||
---The current text or ui scale.
|
|
||||||
---@type number
|
|
||||||
SCALE = 1.0
|
|
||||||
|
|
||||||
---Full path of lite executable.
|
|
||||||
---@type string
|
|
||||||
EXEFILE = "/path/to/lite"
|
|
||||||
|
|
||||||
---Path to the users home directory.
|
|
||||||
---@type string
|
|
||||||
HOME = "/path/to/user/dir"
|
|
|
@ -1,235 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---
|
|
||||||
---Functionality that allows you to launch subprocesses and read
|
|
||||||
---or write to them in a non-blocking fashion.
|
|
||||||
---@class process
|
|
||||||
process = {}
|
|
||||||
|
|
||||||
---Error triggered when the stdout, stderr or stdin fails while reading
|
|
||||||
---or writing, its value is platform dependent, so the value declared on this
|
|
||||||
---interface does not represents the real one.
|
|
||||||
---@type integer
|
|
||||||
process.ERROR_PIPE = -1
|
|
||||||
|
|
||||||
---Error triggered when a read or write action is blocking,
|
|
||||||
---its value is platform dependent, so the value declared on this
|
|
||||||
---interface does not represents the real one.
|
|
||||||
---@type integer
|
|
||||||
process.ERROR_WOULDBLOCK = -2
|
|
||||||
|
|
||||||
---Error triggered when a process takes more time than that specified
|
|
||||||
---by the deadline parameter given on process:start(),
|
|
||||||
---its value is platform dependent, so the value declared on this
|
|
||||||
---interface does not represents the real one.
|
|
||||||
---@type integer
|
|
||||||
process.ERROR_TIMEDOUT = -3
|
|
||||||
|
|
||||||
---Error triggered when trying to terminate or kill a non running process,
|
|
||||||
---its value is platform dependent, so the value declared on this
|
|
||||||
---interface does not represents the real one.
|
|
||||||
---@type integer
|
|
||||||
process.ERROR_INVAL = -4
|
|
||||||
|
|
||||||
---Error triggered when no memory is available to allocate the process,
|
|
||||||
---its value is platform dependent, so the value declared on this
|
|
||||||
---interface does not represents the real one.
|
|
||||||
---@type integer
|
|
||||||
process.ERROR_NOMEM = -5
|
|
||||||
|
|
||||||
---Used for the process:close_stream() method to close stdin.
|
|
||||||
---@type integer
|
|
||||||
process.STREAM_STDIN = 0
|
|
||||||
|
|
||||||
---Used for the process:close_stream() method to close stdout.
|
|
||||||
---@type integer
|
|
||||||
process.STREAM_STDOUT = 1
|
|
||||||
|
|
||||||
---Used for the process:close_stream() method to close stderr.
|
|
||||||
---@type integer
|
|
||||||
process.STREAM_STDERR = 2
|
|
||||||
|
|
||||||
---Instruct process:wait() to wait until the process ends.
|
|
||||||
---@type integer
|
|
||||||
process.WAIT_INFINITE = -1
|
|
||||||
|
|
||||||
---Instruct process:wait() to wait until the deadline given on process:start()
|
|
||||||
---@type integer
|
|
||||||
process.WAIT_DEADLINE = -2
|
|
||||||
|
|
||||||
---Default behavior for redirecting streams.
|
|
||||||
---This flag is deprecated and for backwards compatibility with reproc only.
|
|
||||||
---The behavior of this flag may change in future versions of Lite XL.
|
|
||||||
---@type integer
|
|
||||||
process.REDIRECT_DEFAULT = 0
|
|
||||||
|
|
||||||
---Allow Process API to read this stream via process:read functions.
|
|
||||||
---@type integer
|
|
||||||
process.REDIRECT_PIPE = 1
|
|
||||||
|
|
||||||
---Redirect this stream to the parent.
|
|
||||||
---@type integer
|
|
||||||
process.REDIRECT_PARENT = 2
|
|
||||||
|
|
||||||
---Discard this stream (piping it to /dev/null)
|
|
||||||
---@type integer
|
|
||||||
process.REDIRECT_DISCARD = 3
|
|
||||||
|
|
||||||
---Redirect this stream to stdout.
|
|
||||||
---This flag can only be used on process.options.stderr.
|
|
||||||
---@type integer
|
|
||||||
process.REDIRECT_STDOUT = 4
|
|
||||||
|
|
||||||
---@alias process.errortype
|
|
||||||
---|>'process.ERROR_PIPE'
|
|
||||||
---| 'process.ERROR_WOULDBLOCK'
|
|
||||||
---| 'process.ERROR_TIMEDOUT'
|
|
||||||
---| 'process.ERROR_INVAL'
|
|
||||||
---| 'process.ERROR_NOMEM'
|
|
||||||
|
|
||||||
---@alias process.streamtype
|
|
||||||
---|>'process.STREAM_STDIN'
|
|
||||||
---| 'process.STREAM_STDOUT'
|
|
||||||
---| 'process.STREAM_STDERR'
|
|
||||||
|
|
||||||
---@alias process.waittype
|
|
||||||
---|>'process.WAIT_INFINITE'
|
|
||||||
---| 'process.WAIT_DEADLINE'
|
|
||||||
|
|
||||||
---@alias process.redirecttype
|
|
||||||
---|>'process.REDIRECT_DEFAULT'
|
|
||||||
---| 'process.REDIRECT_PIPE'
|
|
||||||
---| 'process.REDIRECT_PARENT'
|
|
||||||
---| 'process.REDIRECT_DISCARD'
|
|
||||||
---| 'process.REDIRECT_STDOUT'
|
|
||||||
|
|
||||||
---
|
|
||||||
--- Options that can be passed to process.start()
|
|
||||||
---@class process.options
|
|
||||||
---@field public timeout number
|
|
||||||
---@field public cwd string
|
|
||||||
---@field public stdin process.redirecttype
|
|
||||||
---@field public stdout process.redirecttype
|
|
||||||
---@field public stderr process.redirecttype
|
|
||||||
---@field public env table<string, string>
|
|
||||||
process.options = {}
|
|
||||||
|
|
||||||
---
|
|
||||||
---Create and start a new process
|
|
||||||
---
|
|
||||||
---@param command_and_params table First index is the command to execute
|
|
||||||
---and subsequente elements are parameters for the command.
|
|
||||||
---@param options process.options
|
|
||||||
---
|
|
||||||
---@return process | nil
|
|
||||||
---@return string errmsg
|
|
||||||
---@return process.errortype | integer errcode
|
|
||||||
function process:start(command_and_params, options) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Translates an error code into a useful text message
|
|
||||||
---
|
|
||||||
---@param code integer
|
|
||||||
---
|
|
||||||
---@return string | nil
|
|
||||||
function process.strerror(code) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Get the process id.
|
|
||||||
---
|
|
||||||
---@return integer id Process id or 0 if not running.
|
|
||||||
function process:pid() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Read from the given stream type, if the process fails with a ERROR_PIPE it is
|
|
||||||
---automatically destroyed returning nil along error message and code.
|
|
||||||
---
|
|
||||||
---@param stream process.streamtype
|
|
||||||
---@param len? integer Amount of bytes to read, defaults to 2048.
|
|
||||||
---
|
|
||||||
---@return string | nil
|
|
||||||
---@return string errmsg
|
|
||||||
---@return process.errortype | integer errcode
|
|
||||||
function process:read(stream, len) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Read from stdout, if the process fails with a ERROR_PIPE it is
|
|
||||||
---automatically destroyed returning nil along error message and code.
|
|
||||||
---
|
|
||||||
---@param len? integer Amount of bytes to read, defaults to 2048.
|
|
||||||
---
|
|
||||||
---@return string | nil
|
|
||||||
---@return string errmsg
|
|
||||||
---@return process.errortype | integer errcode
|
|
||||||
function process:read_stdout(len) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Read from stderr, if the process fails with a ERROR_PIPE it is
|
|
||||||
---automatically destroyed returning nil along error message and code.
|
|
||||||
---
|
|
||||||
---@param len? integer Amount of bytes to read, defaults to 2048.
|
|
||||||
---
|
|
||||||
---@return string | nil
|
|
||||||
---@return string errmsg
|
|
||||||
---@return process.errortype | integer errcode
|
|
||||||
function process:read_stderr(len) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Write to the stdin, if the process fails with a ERROR_PIPE it is
|
|
||||||
---automatically destroyed returning nil along error message and code.
|
|
||||||
---
|
|
||||||
---@param data string
|
|
||||||
---
|
|
||||||
---@return integer | nil bytes The amount of bytes written or nil if error
|
|
||||||
---@return string errmsg
|
|
||||||
---@return process.errortype | integer errcode
|
|
||||||
function process:write(data) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Allows you to close a stream pipe that you will not be using.
|
|
||||||
---
|
|
||||||
---@param stream process.streamtype
|
|
||||||
---
|
|
||||||
---@return integer | nil
|
|
||||||
---@return string errmsg
|
|
||||||
---@return process.errortype | integer errcode
|
|
||||||
function process:close_stream(stream) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Wait the specified amount of time for the process to exit.
|
|
||||||
---
|
|
||||||
---@param timeout integer | process.waittype Time to wait in milliseconds,
|
|
||||||
---if 0, the function will only check if process is running without waiting.
|
|
||||||
---
|
|
||||||
---@return integer | nil exit_status The process exit status or nil on error
|
|
||||||
---@return string errmsg
|
|
||||||
---@return process.errortype | integer errcode
|
|
||||||
function process:wait(timeout) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Sends SIGTERM to the process
|
|
||||||
---
|
|
||||||
---@return boolean | nil
|
|
||||||
---@return string errmsg
|
|
||||||
---@return process.errortype | integer errcode
|
|
||||||
function process:terminate() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Sends SIGKILL to the process
|
|
||||||
---
|
|
||||||
---@return boolean | nil
|
|
||||||
---@return string errmsg
|
|
||||||
---@return process.errortype | integer errcode
|
|
||||||
function process:kill() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Get the exit code of the process or nil if still running.
|
|
||||||
---
|
|
||||||
---@return number | nil
|
|
||||||
function process:returncode() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Check if the process is running
|
|
||||||
---
|
|
||||||
---@return boolean
|
|
||||||
function process:running() end
|
|
|
@ -1,57 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---
|
|
||||||
---Provides the base functionality for regular expressions matching.
|
|
||||||
---@class regex
|
|
||||||
regex = {}
|
|
||||||
|
|
||||||
---Instruct regex:cmatch() to match only at the first position.
|
|
||||||
---@type integer
|
|
||||||
regex.ANCHORED = 0x80000000
|
|
||||||
|
|
||||||
---Tell regex:cmatch() that the pattern can match only at end of subject.
|
|
||||||
---@type integer
|
|
||||||
regex.ENDANCHORED = 0x20000000
|
|
||||||
|
|
||||||
---Tell regex:cmatch() that subject string is not the beginning of a line.
|
|
||||||
---@type integer
|
|
||||||
regex.NOTBOL = 0x00000001
|
|
||||||
|
|
||||||
---Tell regex:cmatch() that subject string is not the end of a line.
|
|
||||||
---@type integer
|
|
||||||
regex.NOTEOL = 0x00000002
|
|
||||||
|
|
||||||
---Tell regex:cmatch() that an empty string is not a valid match.
|
|
||||||
---@type integer
|
|
||||||
regex.NOTEMPTY = 0x00000004
|
|
||||||
|
|
||||||
---Tell regex:cmatch() that an empty string at the start of the
|
|
||||||
---subject is not a valid match.
|
|
||||||
---@type integer
|
|
||||||
regex.NOTEMPTY_ATSTART = 0x00000008
|
|
||||||
|
|
||||||
---@alias regex.modifiers
|
|
||||||
---|>'"i"' # Case insesitive matching
|
|
||||||
---| '"m"' # Multiline matching
|
|
||||||
---| '"s"' # Match all characters with dot (.) metacharacter even new lines
|
|
||||||
|
|
||||||
---
|
|
||||||
---Compiles a regular expression pattern that can be used to search in strings.
|
|
||||||
---
|
|
||||||
---@param pattern string
|
|
||||||
---@param options? regex.modifiers A string of one or more pattern modifiers.
|
|
||||||
---
|
|
||||||
---@return regex|string regex Ready to use regular expression object or error
|
|
||||||
---message if compiling the pattern failed.
|
|
||||||
function regex.compile(pattern, options) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Search a string for valid matches and returns a list of matching offsets.
|
|
||||||
---
|
|
||||||
---@param subject string The string to search for valid matches.
|
|
||||||
---@param offset? integer The position on the subject to start searching.
|
|
||||||
---@param options? integer A bit field of matching options, eg:
|
|
||||||
---regex.NOTBOL | regex.NOTEMPTY
|
|
||||||
---
|
|
||||||
---@return table<integer, integer> list List of offsets where a match was found.
|
|
||||||
function regex:cmatch(subject, offset, options) end
|
|
|
@ -1,169 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---
|
|
||||||
---Core functionality to render or draw elements into the screen.
|
|
||||||
---@class renderer
|
|
||||||
renderer = {}
|
|
||||||
|
|
||||||
---
|
|
||||||
---Represents a color used by the rendering functions.
|
|
||||||
---@class renderer.color
|
|
||||||
---@field public r number Red
|
|
||||||
---@field public g number Green
|
|
||||||
---@field public b number Blue
|
|
||||||
---@field public a number Alpha
|
|
||||||
renderer.color = {}
|
|
||||||
|
|
||||||
---
|
|
||||||
---Represent options that affect a font's rendering.
|
|
||||||
---@class renderer.fontoptions
|
|
||||||
---@field public antialiasing "'grayscale'" | "'subpixel'"
|
|
||||||
---@field public hinting "'slight'" | "'none'" | '"full"'
|
|
||||||
-- @field public bold boolean
|
|
||||||
-- @field public italic boolean
|
|
||||||
-- @field public underline boolean
|
|
||||||
renderer.fontoptions = {}
|
|
||||||
|
|
||||||
---
|
|
||||||
---@class renderer.font
|
|
||||||
renderer.font = {}
|
|
||||||
|
|
||||||
---
|
|
||||||
---Create a new font object.
|
|
||||||
---
|
|
||||||
---@param path string
|
|
||||||
---@param size number
|
|
||||||
---@param options renderer.fontoptions
|
|
||||||
---
|
|
||||||
---@return renderer.font
|
|
||||||
function renderer.font.load(path, size, options) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Clones a font object into a new one.
|
|
||||||
---
|
|
||||||
---@param size? number Optional new size for cloned font.
|
|
||||||
---
|
|
||||||
---@return renderer.font
|
|
||||||
function renderer.font:copy(size) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Set the amount of characters that represent a tab.
|
|
||||||
---
|
|
||||||
---@param chars integer Also known as tab width.
|
|
||||||
function renderer.font:set_tab_size(chars) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Get the width in pixels of the given text when
|
|
||||||
---rendered with this font.
|
|
||||||
---
|
|
||||||
---@param text string
|
|
||||||
---
|
|
||||||
---@return number
|
|
||||||
function renderer.font:get_width(text) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Get the height in pixels that occupies a single character
|
|
||||||
---when rendered with this font.
|
|
||||||
---
|
|
||||||
---@return number
|
|
||||||
function renderer.font:get_height() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Get the current size of the font.
|
|
||||||
---
|
|
||||||
---@return number
|
|
||||||
function renderer.font:get_size() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Set a new size for the font.
|
|
||||||
---
|
|
||||||
---@param size number
|
|
||||||
function renderer.font:set_size(size) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Assistive functionality to replace characters in a
|
|
||||||
---rendered text with other characters.
|
|
||||||
---@class renderer.replacements
|
|
||||||
renderer.replacements = {}
|
|
||||||
|
|
||||||
---
|
|
||||||
---Create a new character replacements object.
|
|
||||||
---
|
|
||||||
---@return renderer.replacements
|
|
||||||
function renderer.replacements.new() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Add to internal map a character to character replacement.
|
|
||||||
---
|
|
||||||
---@param original_char string Should be a single character like '\t'
|
|
||||||
---@param replacement_char string Should be a single character like '»'
|
|
||||||
function renderer.replacements:add(original_char, replacement_char) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Toggles drawing debugging rectangles on the currently rendered sections
|
|
||||||
---of the window to help troubleshoot the renderer.
|
|
||||||
---
|
|
||||||
---@param enable boolean
|
|
||||||
function renderer.show_debug(enable) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Get the size of the screen area been rendered.
|
|
||||||
---
|
|
||||||
---@return number width
|
|
||||||
---@return number height
|
|
||||||
function renderer.get_size() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Tell the rendering system that we want to build a new frame to render.
|
|
||||||
function renderer.begin_frame() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Tell the rendering system that we finished building the frame.
|
|
||||||
function renderer.end_frame() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Set the region of the screen where draw operations will take effect.
|
|
||||||
---
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param width number
|
|
||||||
---@param height number
|
|
||||||
function renderer.set_clip_rect(x, y, width, height) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Draw a rectangle.
|
|
||||||
---
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param width number
|
|
||||||
---@param height number
|
|
||||||
---@param color renderer.color
|
|
||||||
function renderer.draw_rect(x, y, width, height, color) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Draw text.
|
|
||||||
---
|
|
||||||
---@param font renderer.font
|
|
||||||
---@param text string
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param color renderer.color
|
|
||||||
---@param replace renderer.replacements
|
|
||||||
---@param color_replace renderer.color
|
|
||||||
---
|
|
||||||
---@return number x_subpixel
|
|
||||||
function renderer.draw_text(font, text, x, y, color, replace, color_replace) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Draw text at subpixel level.
|
|
||||||
---
|
|
||||||
---@param font renderer.font
|
|
||||||
---@param text string
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param color renderer.color
|
|
||||||
---@param replace renderer.replacements
|
|
||||||
---@param color_replace renderer.color
|
|
||||||
---
|
|
||||||
---@return number x_subpixel
|
|
||||||
function renderer.draw_text_subpixel(font, text, x, y, color, replace, color_replace) end
|
|
|
@ -1,234 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---
|
|
||||||
---Utilites for managing current window, files and more.
|
|
||||||
---@class system
|
|
||||||
system = {}
|
|
||||||
|
|
||||||
---@alias system.fileinfotype
|
|
||||||
---|>'"file"' # It is a file.
|
|
||||||
---| '"dir"' # It is a directory.
|
|
||||||
|
|
||||||
---
|
|
||||||
---@class system.fileinfo
|
|
||||||
---@field public modified number A timestamp in seconds.
|
|
||||||
---@field public size number Size in bytes.
|
|
||||||
---@field public type system.fileinfotype Type of file
|
|
||||||
system.fileinfo = {}
|
|
||||||
|
|
||||||
---
|
|
||||||
---Core function used to retrieve the current event been triggered by SDL.
|
|
||||||
---
|
|
||||||
---The following is a list of event types emitted by this function and
|
|
||||||
---the arguments for each of them if applicable.
|
|
||||||
---
|
|
||||||
---Window events:
|
|
||||||
--- * "quit"
|
|
||||||
--- * "resized" -> width, height
|
|
||||||
--- * "exposed"
|
|
||||||
--- * "minimized"
|
|
||||||
--- * "maximized"
|
|
||||||
--- * "restored"
|
|
||||||
--- * "focuslost"
|
|
||||||
---
|
|
||||||
---File events:
|
|
||||||
--- * "filedropped" -> filename, x, y
|
|
||||||
---
|
|
||||||
---Keyboard events:
|
|
||||||
--- * "keypressed" -> key_name
|
|
||||||
--- * "keyreleased" -> key_name
|
|
||||||
--- * "textinput" -> text
|
|
||||||
---
|
|
||||||
---Mouse events:
|
|
||||||
--- * "mousepressed" -> button_name, x, y, amount_of_clicks
|
|
||||||
--- * "mousereleased" -> button_name, x, y
|
|
||||||
--- * "mousemoved" -> x, y, relative_x, relative_y
|
|
||||||
--- * "mousewheel" -> y
|
|
||||||
---
|
|
||||||
---@return string type
|
|
||||||
---@return any? arg1
|
|
||||||
---@return any? arg2
|
|
||||||
---@return any? arg3
|
|
||||||
---@return any? arg4
|
|
||||||
function system.poll_event() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Wait until an event is triggered.
|
|
||||||
---
|
|
||||||
---@param timeout number Amount of seconds, also supports fractions
|
|
||||||
---of a second, eg: 0.01
|
|
||||||
---
|
|
||||||
---@return boolean status True on success or false if there was an error.
|
|
||||||
function system.wait_event(timeout) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Change the cursor type displayed on screen.
|
|
||||||
---
|
|
||||||
---@param type string | "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'"
|
|
||||||
function system.set_cursor(type) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Change the window title.
|
|
||||||
---
|
|
||||||
---@param title string
|
|
||||||
function system.set_window_title(title) end
|
|
||||||
|
|
||||||
---@alias system.windowmode
|
|
||||||
---|>'"normal"'
|
|
||||||
---| '"minimized"'
|
|
||||||
---| '"maximized"'
|
|
||||||
---| '"fullscreen"'
|
|
||||||
|
|
||||||
---
|
|
||||||
---Change the window mode.
|
|
||||||
---
|
|
||||||
---@param mode system.windowmode
|
|
||||||
function system.set_window_mode(mode) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Retrieve the current window mode.
|
|
||||||
---
|
|
||||||
---@return system.windowmode mode
|
|
||||||
function system.get_window_mode() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Toggle between bordered and borderless.
|
|
||||||
---
|
|
||||||
---@param bordered boolean
|
|
||||||
function system.set_window_bordered(bordered) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---When then window is run borderless (without system decorations), this
|
|
||||||
---function allows to set the size of the different regions that allow
|
|
||||||
---for custom window management.
|
|
||||||
---
|
|
||||||
---@param title_height number
|
|
||||||
---@param controls_width number This is for minimize, maximize, close, etc...
|
|
||||||
---@param resize_border number The amount of pixels reserved for resizing
|
|
||||||
function system.set_window_hit_test(title_height, controls_width, resize_border) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Get the size and coordinates of the window.
|
|
||||||
---
|
|
||||||
---@return number width
|
|
||||||
---@return number height
|
|
||||||
---@return number x
|
|
||||||
---@return number y
|
|
||||||
function system.get_window_size() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Sets the size and coordinates of the window.
|
|
||||||
---
|
|
||||||
---@param width number
|
|
||||||
---@param height number
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
function system.set_window_size(width, height, x, y) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Check if the window currently has focus.
|
|
||||||
---
|
|
||||||
---@return boolean
|
|
||||||
function system.window_has_focus() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Opens a message box to display an error message.
|
|
||||||
---
|
|
||||||
---@param title string
|
|
||||||
---@param message string
|
|
||||||
function system.show_fatal_error(title, message) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Change the current directory path which affects relative file operations.
|
|
||||||
---This function raises an error if the path doesn't exists.
|
|
||||||
---
|
|
||||||
---@param path string
|
|
||||||
function system.chdir(path) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Create a new directory, note that this function doesn't recursively
|
|
||||||
---creates the directories on the given path.
|
|
||||||
---
|
|
||||||
---@param directory_path string
|
|
||||||
---
|
|
||||||
---@return boolean created True on success or false on failure.
|
|
||||||
function system.mkdir(directory_path) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Gets a list of files and directories for a given path.
|
|
||||||
---
|
|
||||||
---@param path string
|
|
||||||
---
|
|
||||||
---@return table|nil list List of directories or nil if empty or error.
|
|
||||||
---@return string? message Error message in case of error.
|
|
||||||
function system.list_dir(path) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Converts a relative path from current directory to the absolute one.
|
|
||||||
---
|
|
||||||
---@param path string
|
|
||||||
---
|
|
||||||
---@return string
|
|
||||||
function system.absolute_path(path) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Get details about a given file or path.
|
|
||||||
---
|
|
||||||
---@param path string Can be a file or a directory path
|
|
||||||
---
|
|
||||||
---@return system.fileinfo|nil info Path details or nil if empty or error.
|
|
||||||
---@return string? message Error message in case of error.
|
|
||||||
function system.get_file_info(path) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Retrieve the text currently stored on the clipboard.
|
|
||||||
---
|
|
||||||
---@return string
|
|
||||||
function system.get_clipboard() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Set the content of the clipboard.
|
|
||||||
---
|
|
||||||
---@param text string
|
|
||||||
function system.set_clipboard(text) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Get amount of iterations since the application was launched
|
|
||||||
---also known as SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency()
|
|
||||||
---
|
|
||||||
---@return number
|
|
||||||
function system.get_time() end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Sleep for the given amount of seconds.
|
|
||||||
---
|
|
||||||
---@param seconds number Also supports fractions of a second, eg: 0.01
|
|
||||||
function system.sleep(seconds) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Similar to os.execute() but does not return the exit status of the
|
|
||||||
---executed command and executes the process in a non blocking way by
|
|
||||||
---forking it to the background.
|
|
||||||
---
|
|
||||||
---@param command string The command to execute.
|
|
||||||
function system.exec(command) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Generates a matching score depending on how well the value of the
|
|
||||||
---given needle compares to that of the value in the haystack.
|
|
||||||
---
|
|
||||||
---@param haystack string
|
|
||||||
---@param needle string
|
|
||||||
---@param file boolean Reverse the algorithm to prioritize the end
|
|
||||||
---of the haystack, eg: with a haystack "/my/path/to/file" and a needle
|
|
||||||
---"file", will get better score than with this option not set to true.
|
|
||||||
---
|
|
||||||
---@return integer score
|
|
||||||
function system.fuzzy_match(haystack, needle, file) end
|
|
||||||
|
|
||||||
---
|
|
||||||
---Change the opacity (also known as transparency) of the window.
|
|
||||||
---
|
|
||||||
---@param opacity number A value from 0.0 to 1.0, the lower the value
|
|
||||||
---the less visible the window will be.
|
|
||||||
function system.set_window_opacity(opacity) end
|
|
File diff suppressed because it is too large
Load Diff
1595
lib/dmon/dmon.h
1595
lib/dmon/dmon.h
File diff suppressed because it is too large
Load Diff
|
@ -1,162 +0,0 @@
|
||||||
#ifndef __DMON_EXTRA_H__
|
|
||||||
#define __DMON_EXTRA_H__
|
|
||||||
|
|
||||||
//
|
|
||||||
// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved.
|
|
||||||
// License: https://github.com/septag/dmon#license-bsd-2-clause
|
|
||||||
//
|
|
||||||
// Extra header functionality for dmon.h for the backend based on inotify
|
|
||||||
//
|
|
||||||
// Add/Remove directory functions:
|
|
||||||
// dmon_watch_add: Adds a sub-directory to already valid watch_id. sub-directories are assumed to be relative to watch root_dir
|
|
||||||
// dmon_watch_add: Removes a sub-directory from already valid watch_id. sub-directories are assumed to be relative to watch root_dir
|
|
||||||
// Reason: The inotify backend does not work well when watching in recursive mode a root directory of a large tree, it may take
|
|
||||||
// quite a while until all inotify watches are established, and events will not be received in this time. Also, since one
|
|
||||||
// inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user
|
|
||||||
// will be reached. The default maximum is 8192.
|
|
||||||
// When using inotify backend users may turn off the DMON_WATCHFLAGS_RECURSIVE flag and add/remove selectively the
|
|
||||||
// sub-directories to be watched based on application-specific logic about which sub-directory actually needs to be watched.
|
|
||||||
// The function dmon_watch_add and dmon_watch_rm are used to this purpose.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef __DMON_H__
|
|
||||||
#error "Include 'dmon.h' before including this file"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir);
|
|
||||||
DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef DMON_IMPL
|
|
||||||
#if DMON_OS_LINUX
|
|
||||||
DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
|
|
||||||
{
|
|
||||||
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
|
|
||||||
|
|
||||||
bool skip_lock = pthread_self() == _dmon.thread_handle;
|
|
||||||
|
|
||||||
if (!skip_lock)
|
|
||||||
pthread_mutex_lock(&_dmon.mutex);
|
|
||||||
|
|
||||||
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
|
||||||
|
|
||||||
// check if the directory exists
|
|
||||||
// if watchdir contains absolute/root-included path, try to strip the rootdir from it
|
|
||||||
// else, we assume that watchdir is correct, so save it as it is
|
|
||||||
struct stat st;
|
|
||||||
dmon__watch_subdir subdir;
|
|
||||||
if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) {
|
|
||||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
|
|
||||||
if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) {
|
|
||||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
char fullpath[DMON_MAX_PATH];
|
|
||||||
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
|
|
||||||
dmon__strcat(fullpath, sizeof(fullpath), watchdir);
|
|
||||||
if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) {
|
|
||||||
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
|
|
||||||
if (!skip_lock)
|
|
||||||
pthread_mutex_unlock(&_dmon.mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
|
|
||||||
}
|
|
||||||
|
|
||||||
int dirlen = (int)strlen(subdir.rootdir);
|
|
||||||
if (subdir.rootdir[dirlen - 1] != '/') {
|
|
||||||
subdir.rootdir[dirlen] = '/';
|
|
||||||
subdir.rootdir[dirlen + 1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that the directory is not already added
|
|
||||||
for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) {
|
|
||||||
if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) {
|
|
||||||
_DMON_LOG_ERRORF("Error watching directory '%s', because it is already added.", watchdir);
|
|
||||||
if (!skip_lock)
|
|
||||||
pthread_mutex_unlock(&_dmon.mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY;
|
|
||||||
char fullpath[DMON_MAX_PATH];
|
|
||||||
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
|
|
||||||
dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir);
|
|
||||||
int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask);
|
|
||||||
if (wd == -1) {
|
|
||||||
_DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watchdir, errno);
|
|
||||||
if (!skip_lock)
|
|
||||||
pthread_mutex_unlock(&_dmon.mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
stb_sb_push(watch->subdirs, subdir);
|
|
||||||
stb_sb_push(watch->wds, wd);
|
|
||||||
|
|
||||||
if (!skip_lock)
|
|
||||||
pthread_mutex_unlock(&_dmon.mutex);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir)
|
|
||||||
{
|
|
||||||
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
|
|
||||||
|
|
||||||
bool skip_lock = pthread_self() == _dmon.thread_handle;
|
|
||||||
|
|
||||||
if (!skip_lock)
|
|
||||||
pthread_mutex_lock(&_dmon.mutex);
|
|
||||||
|
|
||||||
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
|
||||||
|
|
||||||
char subdir[DMON_MAX_PATH];
|
|
||||||
dmon__strcpy(subdir, sizeof(subdir), watchdir);
|
|
||||||
if (strstr(subdir, watch->rootdir) == subdir) {
|
|
||||||
dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir));
|
|
||||||
}
|
|
||||||
|
|
||||||
int dirlen = (int)strlen(subdir);
|
|
||||||
if (subdir[dirlen - 1] != '/') {
|
|
||||||
subdir[dirlen] = '/';
|
|
||||||
subdir[dirlen + 1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
int i, c = stb_sb_count(watch->subdirs);
|
|
||||||
for (i = 0; i < c; i++) {
|
|
||||||
if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i >= c) {
|
|
||||||
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
|
|
||||||
if (!skip_lock)
|
|
||||||
pthread_mutex_unlock(&_dmon.mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
inotify_rm_watch(watch->fd, watch->wds[i]);
|
|
||||||
|
|
||||||
/* Remove entry from subdirs and wds by swapping position with the last entry */
|
|
||||||
watch->subdirs[i] = stb_sb_last(watch->subdirs);
|
|
||||||
stb_sb_pop(watch->subdirs);
|
|
||||||
|
|
||||||
watch->wds[i] = stb_sb_last(watch->wds);
|
|
||||||
stb_sb_pop(watch->wds);
|
|
||||||
|
|
||||||
if (!skip_lock)
|
|
||||||
pthread_mutex_unlock(&_dmon.mutex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif // DMON_OS_LINUX
|
|
||||||
#endif // DMON_IMPL
|
|
||||||
|
|
||||||
#endif // __DMON_EXTRA_H__
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
lite_includes += include_directories('.')
|
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue