Compare commits

..

1 Commits

Author SHA1 Message Date
Francesco Abbate 17b90c25ca Improved font atlas debug images
Produce debug images of font's atlas with highlighted bounding box,
offset and advance. It creates a specific image for subpixel rendering
that shows each subpixel separately.
2021-03-09 14:08:02 +01:00
161 changed files with 6403 additions and 15220 deletions

View File

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

6
.gitattributes vendored
View File

@ -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
src/lib/* linguist-vendored
resources/icons/icon.inl linguist-vendored
icon.inl linguist-vendored

35
.github/labeler.yml vendored
View File

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

View File

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

View File

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

30
.gitignore vendored
View File

@ -1,26 +1,8 @@
build*/
.build*/
lhelper/
submodules/
subprojects/lua/
subprojects/reproc/
/appimage*
.ccls-cache
.lite-debug.log
build*
.build*
.run*
*.diff
*.exe
*.tar.gz
*.zip
*.DS_Store
*App*
compile_commands.json
error.txt
lite-xl*
LiteXL*
lite
.config/
*.lha
release_files
*.o
.lite-debug.log
subprojects/lua
subprojects/libagg
sybprojects/lua

View File

@ -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
this software and associated documentation files (the "Software"), to deal in

View File

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

194
README.md
View File

@ -1,159 +1,109 @@
# Lite XL
[![CI]](https://github.com/lite-xl/lite-xl/actions/workflows/build.yml)
[![Discord Badge Image]](https://discord.gg/RWzqC3nx7K)
![screenshot-dark](https://user-images.githubusercontent.com/433545/85227778-b42abc80-b3df-11ea-9dd3-e788f6c71882.png)
![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.
* **[Get plugins]** — Add additional functionality, adapted for Lite XL.
* **[Get color themes]** — Add additional colors themes.
Please note that Lite XL is compatible with Lite for all the plugins and color themes.
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.
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,
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].
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).
## 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.
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.
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.
## 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
the [plugins repository] or in the [Lite XL plugins repository].
## Building
Additional color themes can be found in the [colors repository].
These color themes are bundled with all releases of Lite XL by default.
You can build the project yourself using the Meson build.
## 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
`build-packages.sh`:
The following libraries are required:
- 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
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,
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:
To build Lite XL with Meson the commands below can be used:
```sh
meson setup --buildtype=release --prefix <prefix> build
meson setup 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 `/`.
To build a bundle application on macOS:
When performing the "meson setup" command you may enable the "portable" option to choose the
kind of install.
If the portable option 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 compile lite-xl yourself it is recommended to use the script `build-packages.sh`:
```sh
meson setup --buildtype=release --Dbundle=true --prefix / build
meson compile -C build
DESTDIR="$(pwd)/Lite XL.app" meson install --skip-subprojects -C build
bash build-packages.sh <arch>
```
Please note that the package is relocatable to any prefix and the option prefix
affects only the place where the application is actually installed.
It will run meson and create two zip files, one for the portable version and
one for the unix-like version. Lite XL can be easily installed by unpacking one
of the zip archive in a directory of your choice.
## Installing Prebuilt
Please note that no directory is not hard-coded in the exectuable so that the
package can be extracted and used in any directory.
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
```
Note also that the unix-like package can work on windows as well.
Please note that while compiling Lite XL on Mac OS X should work Mac OS X is not
officially supported.
## Contributing
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.
## Licenses
## License
This project is free software; you can redistribute it and/or modify it under
the terms of the MIT license. See [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
the terms of the MIT license. See [LICENSE](LICENSE) for details.

View File

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

View File

@ -1,164 +1,219 @@
#!/bin/bash
set -e
if [ ! -e "src/api/api.h" ]; then
echo "Please run this script from the root directory of Lite XL."; exit 1
fi
source scripts/common.sh
show_help() {
echo
echo "Usage: $0 <OPTIONS>"
echo
echo "Common options:"
echo
echo "-h --help Show this help and exit."
echo "-b --builddir DIRNAME Set the name of the build directory (not path)."
echo " Default: '$(get_default_build_dir)'."
echo "-p --prefix PREFIX Install directory prefix."
echo " Default: '/'."
echo " --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
# strip-components is normally set to 1 to strip the initial "data" from the
# directory path.
copy_directory_from_repo () {
local tar_options=()
if [[ $1 == --strip-components=* ]]; then
tar_options+=($1)
shift
fi
local dirname="$1"
local destdir="$2"
git archive "$use_branch" "$dirname" --format=tar | tar xf - -C "$destdir" "${tar_options[@]}"
}
main() {
local build_dir
local build_dir_option=()
local dest_dir
local dest_dir_option=()
local prefix
local prefix_option=()
local version
local version_option=()
local debug
local force_fallback
local appimage
local bundle
local innosetup
local portable
local pgo
# Check if build directory is ok to be used to build.
build_dir_is_usable () {
local build="$1"
if [[ $build == */* || -z "$build" ]]; then
echo "invalid build directory, no path allowed: \"$build\""
return 1
fi
git ls-files --error-unmatch "$build" &> /dev/null
if [ $? == 0 ]; then
echo "invalid path, \"$build\" is under revision control"
return 1
fi
}
for i in "$@"; do
case $i in
-h|--help)
show_help
exit 0
;;
-b|--builddir)
build_dir="$2"
shift
shift
;;
-d|--destdir)
dest_dir="$2"
shift
shift
;;
-f|--forcefallback)
force_fallback="--forcefallback"
shift
;;
-p|--prefix)
prefix="$2"
shift
shift
;;
-v|--version)
version="$2"
shift
shift
;;
-A|--appimage)
appimage="--appimage"
shift
;;
-B|--bundle)
bundle="--bundle"
shift
;;
-D|--dmg)
dmg="--dmg"
shift
;;
-I|--innosetup)
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
# Ordinary release build
lite_build () {
local build="$1"
build_dir_is_usable "$build" || exit 1
rm -fr "$build"
meson setup --buildtype=release "$build" || exit 1
ninja -C "$build" || exit 1
}
# Build using Profile Guided Optimizations (PGO)
lite_build_pgo () {
local build="$1"
build_dir_is_usable "$build" || exit 1
rm -fr "$build"
meson setup --buildtype=release -Db_pgo=generate "$build" || exit 1
ninja -C "$build" || exit 1
copy_directory_from_repo data "$build/src"
"$build/src/lite"
meson configure -Db_pgo=use "$build"
ninja -C "$build" || exit 1
}
lite_build_package_windows () {
local portable=""
if [ "$1" == "-portable" ]; then
portable="-portable"
shift
fi
local build="$1"
local arch="$2"
local os="win"
local pdir=".package-build/lite-xl"
if [ "$portable" == "-portable" ]; then
local bindir="$pdir"
local datadir="$pdir/data"
else
local bindir="$pdir/bin"
local datadir="$pdir/share/lite-xl"
fi
mkdir -p "$bindir"
mkdir -p "$datadir"
for module_name in core plugins colors fonts; do
copy_directory_from_repo --strip-components=1 "data/$module_name" "$datadir"
done
for module_name in plugins colors; do
cp -r "$build/third/data/$module_name" "$datadir"
done
cp "$build/src/lite.exe" "$bindir"
strip --strip-all "$bindir/lite.exe"
pushd ".package-build"
local package_name="lite-xl-$os-$arch$portable.zip"
zip "$package_name" -r "lite-xl"
mv "$package_name" ..
popd
rm -fr ".package-build"
echo "created package $package_name"
}
if [[ -n $1 ]]; then
show_help
lite_build_package_macosx () {
local portable=""
if [ "$1" == "-portable" ]; then
portable="-portable"
shift
fi
local build="$1"
local arch="$2"
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$portable.zip"
zip "$package_name" -r "lite-xl.app"
mv "$package_name" ..
popd
rm -fr ".package-build"
echo "created package $package_name"
}
lite_build_package_linux () {
local portable=""
if [ "$1" == "-portable" ]; then
portable="-portable"
shift
fi
local build="$1"
local arch="$2"
local os="linux"
local pdir=".package-build/lite-xl"
if [ $portable == "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$portable.tar.gz"
tar czf "$package_name" "lite-xl"
mv "$package_name" ..
popd
rm -fr ".package-build"
echo "created package $package_name"
}
lite_build_package () {
if [[ "$OSTYPE" == msys || "$OSTYPE" == win32 ]]; then
lite_build_package_windows "$@"
elif [[ "$OSTYPE" == "darwin"* ]]; then
lite_build_package_macosx "$@"
elif [[ "$OSTYPE" == "linux"* || "$OSTYPE" == "freebsd"* ]]; then
lite_build_package_linux "$@"
else
echo "Unknown OS type \"$OSTYPE\""
exit 1
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"
}
unset arch
while [ ! -z {$1+x} ]; do
case $1 in
-pgo)
pgo=true
shift
;;
-branch=*)
use_branch="${1#-branch=}"
shift
;;
*)
arch="$1"
break
esac
done
if [ -z ${arch+set} ]; then
echo "usage: $0 [options] <arch>"
exit 1
fi
if [ -z ${use_branch+set} ]; then
use_branch="$(git rev-parse --abbrev-ref HEAD)"
fi
build_dir=".build-$arch"
if [ -z ${pgo+set} ]; then
lite_build "$build_dir"
else
lite_build_pgo "$build_dir"
fi
lite_copy_third_party_modules "$build_dir"
lite_build_package "$build_dir" "$arch"
lite_build_package -portable "$build_dir" "$arch"

39
build.sh Executable file
View File

@ -0,0 +1,39 @@
#!/bin/bash
cflags+="-Wall -O3 -g -std=gnu11 -fno-strict-aliasing -Isrc -Ilib/font_renderer"
cflags+=" $(pkg-config --cflags lua5.2) $(sdl2-config --cflags)"
lflags="-static-libgcc -static-libstdc++"
for package in libagg freetype2 lua5.2; 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"

View File

@ -1,240 +1,7 @@
Lite XL is following closely [rxi/lite](https://github.com/rxi/lite) but with some enhancements.
This files document the changes done in Lite XL for each release.
### 2.0.3
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.

View File

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

View File

@ -41,14 +41,11 @@ function command.get_all_valid()
return res
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]
if cmd and cmd.predicate(...) then
cmd.perform(...)
if cmd and cmd.predicate() then
cmd.perform()
return true
end
return false
@ -62,10 +59,7 @@ end
function command.add_defaults()
local reg = {
"core", "root", "command", "doc", "findreplace",
"files", "drawwhitespace", "dialog"
}
local reg = { "core", "root", "command", "doc", "findreplace", "files" }
for _, name in ipairs(reg) do
require("core.commands." .. name)
end

View File

@ -1,7 +1,13 @@
local core = require "core"
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()
core.active_view:submit()
end,

View File

@ -6,12 +6,10 @@ local LogView = require "core.logview"
local fullscreen = false
local restore_title_view = false
local function suggest_directory(text)
text = common.home_expand(text)
return common.home_encode_list((text == "" or text == common.home_expand(common.dirname(core.project_dir)))
and core.recent_projects or common.dir_path_suggest(text))
return common.home_encode_list(text == "" and core.recent_projects or common.dir_path_suggest(text))
end
command.add(nil, {
@ -29,12 +27,7 @@ command.add(nil, {
["core:toggle-fullscreen"] = function()
fullscreen = not fullscreen
if fullscreen then
restore_title_view = core.title_view.visible
end
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,
["core:reload-module"] = function()
@ -71,9 +64,6 @@ command.add(nil, {
end,
["core:find-file"] = function()
if not core.project_files_number() then
return command.perform "core:open-file"
end
local files = {}
for dir, item in core.get_project_files() do
if item.type == "file" then
@ -94,38 +84,10 @@ command.add(nil, {
end,
["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)
local filename = system.absolute_path(common.home_expand(text))
core.root_view:open_doc(core.open_doc(filename))
core.root_view:open_doc(core.open_doc(common.home_expand(text)))
end, function (text)
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
end, nil, function(text)
local filename = common.home_expand(text)
local path_stat, err = system.get_file_info(filename)
if err then
if err:find("No such file", 1, true) then
-- check if the containing directory exists
local dirname = common.dirname(filename)
local dir_stat = dirname and system.get_file_info(dirname)
if not dirname or (dir_stat and dir_stat.type == 'dir') then
return true
end
end
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,
@ -152,10 +114,6 @@ command.add(nil, {
end,
["core:change-project-folder"] = function()
local dirname = common.dirname(core.project_dir)
if dirname then
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
@ -164,15 +122,13 @@ command.add(nil, {
core.error("Cannot open folder %q", text)
return
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,
["core:open-project-folder"] = function()
local dirname = common.dirname(core.project_dir)
if dirname then
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)
@ -196,6 +152,8 @@ command.add(nil, {
return
end
core.add_project_directory(system.absolute_path(text))
-- TODO: add the name of directory to prioritize
core.reschedule_project_scan()
end, suggest_directory)
end,

View File

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

View File

@ -16,88 +16,63 @@ local function doc()
end
local function doc_multiline_selections(sort)
local iter, state, idx, line1, col1, line2, col2 = doc():get_selections(sort)
return function()
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
local function get_indent_string()
if config.tab_type == "hard" then
return "\t"
end
return string.rep(" ", config.indent_size)
end
local function doc_multiline_selection(sort)
local line1, col1, line2, col2, swap = doc():get_selection(sort)
if line2 > line1 and col2 == 1 then
line2 = line2 - 1
col2 = #doc().lines[line2]
end
return line1, col1, line2, col2, swap
end
local function insert_at_start_of_selected_lines(text, skip_empty)
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
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_multiline_selection(true)
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)
if line >= #doc().lines then
doc():insert(line, math.huge, "\n")
end
end
local function save(filename)
local abs_filename
if filename then
filename = core.normalize_to_project_dir(filename)
abs_filename = core.project_absolute_path(filename)
end
doc():save(filename, abs_filename)
local saved_filename = doc().filename
core.log("Saved \"%s\"", saved_filename)
doc():save(filename)
core.on_doc_save(filename)
core.log("Saved \"%s\"", doc().filename)
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 = {
["doc:undo"] = function()
@ -108,191 +83,173 @@ local commands = {
doc():redo()
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()
local clipboard = system.get_clipboard()
-- 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
doc():text_input(system.get_clipboard():gsub("\r", ""))
end,
["doc:newline"] = function()
for idx, line, col in doc():get_selections(false, true) do
local indent = doc().lines[line]:match("^[\t ]*")
if col <= #indent then
indent = indent:sub(#indent + 2 - col)
end
doc():text_input("\n" .. indent, idx)
local line, col = doc():get_selection()
local indent = doc().lines[line]:match("^[\t ]*")
if col <= #indent then
indent = indent:sub(#indent + 2 - col)
end
doc():text_input("\n" .. indent)
end,
["doc:newline-below"] = function()
for idx, line in doc():get_selections(false, true) do
local indent = doc().lines[line]:match("^[\t ]*")
doc():insert(line, math.huge, "\n" .. indent)
doc():set_selections(idx, line + 1, math.huge)
end
local line = doc():get_selection()
local indent = doc().lines[line]:match("^[\t ]*")
doc():insert(line, math.huge, "\n" .. indent)
doc():set_selection(line + 1, math.huge)
end,
["doc:newline-above"] = function()
for idx, line in doc():get_selections(false, true) do
local indent = doc().lines[line]:match("^[\t ]*")
doc():insert(line, 1, indent .. "\n")
doc():set_selections(idx, line, math.huge)
end
local line = doc():get_selection()
local indent = doc().lines[line]:match("^[\t ]*")
doc():insert(line, 1, indent .. "\n")
doc():set_selection(line, math.huge)
end,
["doc:delete"] = function()
for idx, line1, col1, line2, col2 in doc():get_selections() do
if line1 == line2 and col1 == col2 and doc().lines[line1]:find("^%s*$", col1) then
doc():remove(line1, col1, line1, math.huge)
end
doc():delete_to_cursor(idx, translate.next_char)
local line, col = doc():get_selection()
if not doc():has_selection() and doc().lines[line]:find("^%s*$", col) then
doc():remove(line, col, line, math.huge)
end
doc():delete_to(translate.next_char)
end,
["doc:backspace"] = function()
local _, indent_size = doc():get_indent_info()
for idx, line1, col1, line2, col2 in doc():get_selections() do
if line1 == line2 and col1 == col2 then
local text = doc():get_text(line1, 1, line1, col1)
if #text >= indent_size and text:find("^ *$") then
doc():delete_to_cursor(idx, 0, -indent_size)
return
end
local line, col = doc():get_selection()
if not doc():has_selection() then
local text = doc():get_text(line, 1, line, col)
if #text >= config.indent_size and text:find("^ *$") then
doc():delete_to(0, -config.indent_size)
return
end
doc():delete_to_cursor(idx, translate.previous_char)
end
doc():delete_to(translate.previous_char)
end,
["doc:select-all"] = function()
doc():set_selection(1, 1, math.huge, math.huge)
end,
["doc:select-none"] = function()
local line, col = doc():get_selection()
doc():set_selection(line, col)
end,
["doc:select-lines"] = function()
for idx, line1, _, line2 in doc():get_selections(true) do
append_line_if_last_line(line2)
doc():set_selections(idx, line1, 1, line2 + 1, 1)
end
local line1, _, line2, _, swap = doc():get_selection(true)
append_line_if_last_line(line2)
doc():set_selection(line1, 1, line2 + 1, 1, swap)
end,
["doc:select-word"] = function()
for idx, line1, col1 in doc():get_selections(true) do
local line1, col1 = translate.start_of_word(doc(), line1, col1)
local line2, col2 = translate.end_of_word(doc(), line1, col1)
doc():set_selections(idx, line2, col2, line1, col1)
end
local line1, col1 = doc():get_selection(true)
local line1, col1 = translate.start_of_word(doc(), line1, col1)
local line2, col2 = translate.end_of_word(doc(), line1, col1)
doc():set_selection(line2, col2, line1, col1)
end,
["doc:join-lines"] = function()
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
if line1 == line2 then line2 = line2 + 1 end
local text = doc():get_text(line1, 1, line2, math.huge)
text = text:gsub("(.-)\n[\t ]*", function(x)
return x:find("^%s*$") and x or x .. " "
end)
doc():insert(line1, 1, text)
doc():remove(line1, #text + 1, line2, math.huge)
if line1 ~= line2 or col1 ~= col2 then
doc():set_selections(idx, line1, math.huge)
end
local line1, _, line2 = doc():get_selection(true)
if line1 == line2 then line2 = line2 + 1 end
local text = doc():get_text(line1, 1, line2, math.huge)
text = text:gsub("(.-)\n[\t ]*", function(x)
return x:find("^%s*$") and x or x .. " "
end)
doc():insert(line1, 1, text)
doc():remove(line1, #text + 1, line2, math.huge)
if doc():has_selection() then
doc():set_selection(line1, math.huge)
end
end,
["doc:indent"] = function()
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2)
if l1 then
doc():set_selections(idx, l1, c1, l2, c2)
end
local text = get_indent_string()
if doc():has_selection() then
insert_at_start_of_selected_lines(text)
else
doc():text_input(text)
end
end,
["doc:unindent"] = function()
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
local l1, c1, l2, c2 = doc():indent_text(true, line1, col1, line2, col2)
if l1 then
doc():set_selections(idx, l1, c1, l2, c2)
end
end
local text = get_indent_string()
remove_from_start_of_selected_lines(text)
end,
["doc:duplicate-lines"] = function()
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2)
local text = doc():get_text(line1, 1, line2 + 1, 1)
doc():insert(line2 + 1, 1, text)
local n = line2 - line1 + 1
doc():set_selections(idx, line1 + n, col1, line2 + n, col2)
end
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
append_line_if_last_line(line2)
local text = doc():get_text(line1, 1, line2 + 1, 1)
doc():insert(line2 + 1, 1, text)
local n = line2 - line1 + 1
doc():set_selection(line1 + n, col1, line2 + n, col2, swap)
end,
["doc:delete-lines"] = function()
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2)
doc():remove(line1, 1, line2 + 1, 1)
doc():set_selections(idx, line1, col1)
end
local line1, col1, line2 = doc_multiline_selection(true)
append_line_if_last_line(line2)
doc():remove(line1, 1, line2 + 1, 1)
doc():set_selection(line1, col1)
end,
["doc:move-lines-up"] = function()
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2)
if line1 > 1 then
local text = doc().lines[line1 - 1]
doc():insert(line2 + 1, 1, text)
doc():remove(line1 - 1, 1, line1, 1)
doc():set_selections(idx, line1 - 1, col1, line2 - 1, col2)
end
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
append_line_if_last_line(line2)
if line1 > 1 then
local text = doc().lines[line1 - 1]
doc():insert(line2 + 1, 1, text)
doc():remove(line1 - 1, 1, line1, 1)
doc():set_selection(line1 - 1, col1, line2 - 1, col2, swap)
end
end,
["doc:move-lines-down"] = function()
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
append_line_if_last_line(line2 + 1)
if line2 < #doc().lines then
local text = doc().lines[line2 + 1]
doc():remove(line2 + 1, 1, line2 + 2, 1)
doc():insert(line1, 1, text)
doc():set_selections(idx, line1 + 1, col1, line2 + 1, col2)
end
local line1, col1, line2, col2, swap = doc_multiline_selection(true)
append_line_if_last_line(line2 + 1)
if line2 < #doc().lines then
local text = doc().lines[line2 + 1]
doc():remove(line2 + 1, 1, line2 + 2, 1)
doc():insert(line1, 1, text)
doc():set_selection(line1 + 1, col1, line2 + 1, col2, swap)
end
end,
["doc:toggle-line-comments"] = function()
local comment = doc().syntax.comment
if not comment then return end
local indentation = doc():get_indent_string()
local comment_text = comment .. " "
for idx, line1, _, line2 in doc_multiline_selections(true) do
local uncomment = true
local start_offset = math.huge
for line = line1, line2 do
local text = doc().lines[line]
local s = text:find("%S")
local cs, ce = text:find(comment_text, s, true)
if s and cs ~= s then
uncomment = false
start_offset = math.min(start_offset, s)
end
end
for line = line1, line2 do
local text = doc().lines[line]
local s = text:find("%S")
if uncomment then
local cs, ce = text:find(comment_text, s, true)
if ce then
doc():remove(line, cs, line, ce + 1)
end
elseif s then
doc():insert(line, start_offset, comment_text)
end
local line1, _, line2 = doc():get_selection(true)
local uncomment = true
for line = line1, line2 do
local text = doc().lines[line]
if text:find("%S") and text:find(comment_text, 1, true) ~= 1 then
uncomment = false
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,
["doc:upper-case"] = function()
@ -339,12 +296,8 @@ local commands = {
end,
["doc:save-as"] = function()
local last_doc = core.last_active_view and core.last_active_view.doc
if doc().filename then
core.command_view:set_text(doc().filename)
elseif last_doc and last_doc.filename then
local dirname, filename = core.last_active_view.doc.abs_filename:match("(.*)[/\\](.+)$")
core.command_view:set_text(core.normalize_to_project_dir(dirname) .. PATHSEP)
end
core.command_view:enter("Save As", function(filename)
save(common.home_expand(filename))
@ -361,7 +314,7 @@ local commands = {
end
end,
["file:rename"] = function()
["doc:rename"] = function()
local old_filename = doc().filename
if not old_filename then
core.error("Cannot rename unsaved doc")
@ -369,65 +322,13 @@ local commands = {
end
core.command_view:set_text(old_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)
if filename ~= old_filename then
os.remove(old_filename)
end
end, function (text)
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
end)
end, common.path_suggest)
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 +344,6 @@ local translations = {
["start-of-line"] = translate.start_of_line,
["end-of-line"] = translate.end_of_line,
["start-of-word"] = translate.start_of_word,
["start-of-indentation"] = translate.start_of_indentation,
["end-of-word"] = translate.end_of_word,
["previous-line"] = DocView.translate.previous_line,
["next-line"] = DocView.translate.next_line,
@ -458,24 +358,21 @@ for name, fn in pairs(translations) do
end
commands["doc:move-to-previous-char"] = function()
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
if line1 ~= line2 or col1 ~= col2 then
doc():set_selections(idx, line1, col1)
end
if doc():has_selection() then
local line, col = doc():get_selection(true)
doc():set_selection(line, col)
else
doc():move_to(translate.previous_char)
end
doc():move_to(translate.previous_char)
end
commands["doc:move-to-next-char"] = function()
for idx, line1, col1, line2, col2 in doc():get_selections(true) do
if line1 ~= line2 or col1 ~= col2 then
doc():set_selections(idx, line2, col2)
end
if doc():has_selection() then
local _, _, line, col = doc():get_selection(true)
doc():set_selection(line, col)
else
doc():move_to(translate.next_char)
end
doc():move_to(translate.next_char)
end
command.add("core.docview", commands)
command.add(function()
return core.active_view:is(DocView) and core.active_view.doc:has_any_selection()
end ,selection_commands)

View File

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

View File

@ -1,13 +1,12 @@
local core = require "core"
local command = require "core.command"
local common = require "core.common"
command.add(nil, {
["files:create-directory"] = function()
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
core.error("cannot create directory %q: %s", path, err)
core.error("cannot create directory %q: %s", text, err)
end
end)
end,

View File

@ -2,85 +2,67 @@ local core = require "core"
local command = require "core.command"
local config = require "core.config"
local search = require "core.doc.search"
local keymap = require "core.keymap"
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 is_DocView = core.active_view:is(DocView) and not core.active_view:is(CommandView)
return is_DocView and core.active_view.doc or (last_view and last_view.doc)
return core.active_view.doc
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 ok, line1, col1, line2, col2 = pcall(search_fn, last_view.doc,
sel[1], sel[2], text, case_sensitive, find_regex)
if ok and line1 and text ~= "" then
last_view.doc:set_selection(line2, col2, line1, col1)
last_view:scroll_to_line(line2, true)
found_expression = true
else
last_view.doc:set_selection(table.unpack(sel))
found_expression = false
local previous_finds
local last_doc
local last_fn, last_text
local function push_previous_find(doc, sel)
if last_doc ~= doc then
last_doc = doc
previous_finds = {}
end
end
local function insert_unique(t, v)
local n = #t
for i = 1, n do
if t[i] == v then return end
if #previous_finds >= max_previous_finds then
table.remove(previous_finds, 1)
end
t[n + 1] = v
table.insert(previous_finds, sel or { doc:get_selection() })
end
local function find(label, search_fn)
last_view, last_sel = core.active_view,
{ core.active_view.doc:get_selection() }
local text = last_view.doc:get_text(unpack(last_sel))
found_expression = false
local dv = core.active_view
local sel = { dv.doc:get_selection() }
local text = dv.doc:get_text(table.unpack(sel))
local found = false
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, item)
insert_unique(core.previous_find, text)
core.status_view:remove_tooltip()
if found_expression then
core.command_view:enter(label, function(text)
if found then
last_fn, last_text = search_fn, text
previous_finds = {}
push_previous_find(dv.doc, sel)
else
core.error("Couldn't find %q", text)
last_view.doc:set_selection(table.unpack(last_sel))
last_view:scroll_to_make_visible(table.unpack(last_sel))
dv.doc:set_selection(table.unpack(sel))
dv:scroll_to_make_visible(sel[1], sel[2])
end
end, function(text)
update_preview(last_sel, search_fn, text)
last_fn, last_text = search_fn, text
return core.previous_find
local ok, line1, col1, line2, col2 = pcall(search_fn, dv.doc, sel[1], sel[2], text)
if ok and line1 and text ~= "" then
dv.doc:set_selection(line2, col2, line1, col1)
dv:scroll_to_line(line2, true)
found = true
else
dv.doc:set_selection(table.unpack(sel))
found = false
end
end, function(explicit)
core.status_view:remove_tooltip()
if explicit then
last_view.doc:set_selection(table.unpack(last_sel))
last_view:scroll_to_make_visible(table.unpack(last_sel))
dv.doc:set_selection(table.unpack(sel))
dv:scroll_to_make_visible(sel[1], sel[2])
end
end)
end
@ -89,113 +71,82 @@ end
local function replace(kind, default, fn)
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)
insert_unique(core.previous_find, old)
core.command_view:set_text(old, true)
local s = string.format("Replace %s %q With", kind, old)
core.command_view:set_hidden_suggestions()
core.command_view:enter(s, function(new)
core.status_view:remove_tooltip()
insert_unique(core.previous_replace, new)
local n = doc():replace(function(text)
return fn(text, old, new)
end)
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
end, function() return core.previous_replace end, function()
core.status_view:remove_tooltip()
end)
end, function() return core.previous_find end, function()
core.status_view:remove_tooltip()
end)
end
local function has_selection()
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
local function has_unique_selection()
if not doc() then return false end
local text = nil
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
command.add(has_selection, {
["find-replace:select-next"] = function()
local l1, c1, l2, c2 = doc():get_selection(true)
local text = doc():get_text(l1, c1, l2, c2)
repeat
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
if l1 == il1 and c1 == ic1 then break end
if l2 and (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
local l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
if l2 then doc():set_selection(l2, c2, l1, c1) 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", {
["find-replace:find"] = function()
find("Find Text", function(doc, line, col, text, case_sensitive, find_regex, find_reverse)
local opt = { wrap = true, no_case = not case_sensitive, regex = find_regex, reverse = find_reverse }
find("Find Text", function(doc, line, col, text)
local opt = { wrap = true, no_case = true }
return search.find(doc, line, col, text, opt)
end)
end,
["find-replace:replace"] = function()
local l1, c1, l2, c2 = doc():get_selection()
local selected_text = doc():get_text(l1, c1, l2, c2)
replace("Text", l1 == l2 and selected_text or "", function(text, old, new)
if not find_regex then
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
["find-replace:find-pattern"] = function()
find("Find Text Pattern", function(doc, line, col, text)
local opt = { wrap = true, no_case = true, pattern = true }
return search.find(doc, line, col, text, opt)
end)
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
local result, matches = regex.gsub(regex.compile(old, "m"), text, new)
return result, #matches
end
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,
@ -217,53 +168,3 @@ command.add("core.docview", {
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
})

View File

@ -3,7 +3,6 @@ local style = require "core.style"
local DocView = require "core.docview"
local command = require "core.command"
local common = require "core.common"
local config = require "core.config"
local t = {
@ -12,25 +11,12 @@ local t = {
node:close_active_view(core.root_view.root_node)
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()
["root:close-all"] = function()
if core.confirm_close_all() then
core.root_view:close_all_docviews()
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()
local node = core.root_view:get_active_node()
local idx = node:get_view_idx(core.active_view)
@ -64,7 +50,7 @@ local t = {
table.insert(node.views, idx + 1, core.active_view)
end
end,
["root:shrink"] = function()
local node = core.root_view:get_active_node()
local parent = node:get_parent_node(core.root_view.root_node)
@ -77,7 +63,7 @@ local t = {
local parent = node:get_parent_node(core.root_view.root_node)
local n = (parent.a == node) and 0.1 or -0.1
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
end
end,
}
@ -113,8 +99,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)
end
local node = core.root_view.root_node:get_child_overlapping_point(x, y)
local sx, sy = node:get_locked_size()
if not sx and not sy then
if not node:get_locked_size() then
core.set_active_view(node.active_view)
end
end
@ -122,17 +107,5 @@ end
command.add(function()
local node = core.root_view:get_active_node()
local sx, sy = node:get_locked_size()
return not sx and not sy
return not node:get_locked_size()
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
})

View File

@ -15,8 +15,6 @@ end
local CommandView = DocView:extend()
CommandView.context = "application"
local max_suggestions = 10
local noop = function() end
@ -25,7 +23,6 @@ local default_state = {
submit = noop,
suggest = noop,
cancel = noop,
validate = function() return true end
}
@ -34,7 +31,6 @@ function CommandView:new()
self.suggestion_idx = 1
self.suggestions = {}
self.suggestions_height = 0
self.show_suggestions = true
self.last_change_id = 0
self.gutter_width = 0
self.gutter_text_brightness = 0
@ -46,11 +42,6 @@ function CommandView:new()
end
function CommandView:set_hidden_suggestions()
self.show_suggestions = false
end
function CommandView:get_name()
return View.get_name(self)
end
@ -89,29 +80,10 @@ end
function CommandView:move_suggestion_idx(dir)
if self.show_suggestions then
local n = self.suggestion_idx + dir
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
self:complete()
self.last_change_id = self.doc:get_change_id()
else
local current_suggestion = #self.suggestions > 0 and self.suggestions[self.suggestion_idx].text
local text = self:get_text()
if text == current_suggestion then
local n = self.suggestion_idx + dir
if n == 0 and self.save_suggestion then
self:set_text(self.save_suggestion)
else
self.suggestion_idx = 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
local n = self.suggestion_idx + dir
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
self:complete()
self.last_change_id = self.doc:get_change_id()
end
@ -125,15 +97,13 @@ end
function CommandView:submit()
local suggestion = self.suggestions[self.suggestion_idx]
local text = self:get_text()
if self.state.validate(text) then
local submit = self.state.submit
self:exit(true)
submit(text, suggestion)
end
local submit = self.state.submit
self:exit(true)
submit(text, suggestion)
end
function CommandView:enter(text, submit, suggest, cancel, validate)
function CommandView:enter(text, submit, suggest, cancel)
if self.state ~= default_state then
return
end
@ -141,7 +111,6 @@ function CommandView:enter(text, submit, suggest, cancel, validate)
submit = submit or noop,
suggest = suggest or noop,
cancel = cancel or noop,
validate = validate or function() return true end
}
core.set_active_view(self)
self:update_suggestions()
@ -159,8 +128,6 @@ function CommandView:exit(submitted, inexplicit)
self.doc:reset()
self.suggestions = {}
if not submitted then cancel(not inexplicit) end
self.show_suggestions = true
self.save_suggestion = nil
end
@ -214,7 +181,7 @@ function CommandView:update()
-- update suggestions box 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 = math.min(#self.suggestions, max_suggestions) * lh
self:move_towards("suggestions_height", dest)
-- update suggestion cursor offset
@ -283,9 +250,7 @@ end
function CommandView:draw()
CommandView.super.draw(self)
if self.show_suggestions then
core.root_view:defer_draw(draw_suggestions_box, self)
end
core.root_view:defer_draw(draw_suggestions_box, self)
end

View File

@ -1,8 +1,8 @@
local common = {}
function common.is_utf8_cont(s, offset)
local byte = s:byte(offset or 1)
function common.is_utf8_cont(char)
local byte = char:byte()
return byte >= 0x80 and byte < 0xc0
end
@ -22,13 +22,6 @@ function common.round(n)
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)
if type(a) ~= "table" then
return a + (b - a) * t
@ -41,59 +34,34 @@ function common.lerp(a, b, t)
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)
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
r = tonumber(r, 16)
g = tonumber(g, 16)
b = tonumber(b, 16)
a = tonumber(a, 16) or 0xff
a = 1
elseif str:match("rgba?%s*%([%d%s%.,]+%)") then
local f = str:gmatch("[%d.]+")
r = (f() or 0)
g = (f() or 0)
b = (f() or 0)
a = (f() or 1) * 0xff
a = f() or 1
else
error(string.format("bad color string '%s'", str))
end
return r, g, b, a
return r, g, b, a * 0xff
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)
return a.score > b.score
end
local function fuzzy_match_items(items, needle, files)
local function fuzzy_match_items(items, needle)
local res = {}
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
table.insert(res, { text = item, score = score })
end
@ -106,11 +74,11 @@ local function fuzzy_match_items(items, needle, files)
end
function common.fuzzy_match(haystack, needle, files)
function common.fuzzy_match(haystack, needle)
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)
return system.fuzzy_match(haystack, needle)
end
@ -118,16 +86,16 @@ 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])
table.insert(recents_ext, recents[i])
end
table.insert(recents_ext, recents[1])
local others = common.fuzzy_match(haystack, "", true)
local others = common.fuzzy_match(haystack, "")
for i = 1, #others do
table.insert(recents_ext, others[i])
end
return recents_ext
else
return fuzzy_match_items(haystack, needle, true)
return fuzzy_match_items(haystack, needle)
end
end
@ -235,19 +203,11 @@ function common.basename(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)
if HOME and string.find(text, HOME, 1, true) == 1 then
local dir_pos = #HOME + 1
-- ensure we don't replace if the text is just "$HOME" or "$HOME/" so
-- it must have a "/" following the $HOME and some characters following.
if string.find(text, PATHSEP, dir_pos, true) == dir_pos and #text > dir_pos then
return "~" .. text:sub(dir_pos)
if HOME then
local n = #HOME
if text:sub(1, n) == HOME and text:sub(n + 1, n + 1):match("[/\\\\]") then
return "~" .. text:sub(n + 1)
end
end
return text
@ -268,11 +228,18 @@ function common.home_expand(text)
end
function common.normalize_path(filename)
if PATHSEP == '\\' then
filename = filename:gsub('[/\\]', '\\')
local drive, rem = filename:match('^([a-zA-Z])(:.*)')
return drive and drive:upper() .. rem or filename
end
return filename
end
local function split_on_slash(s, sep_pattern)
local t = {}
if s:match("^[/\\]") then
t[#t + 1] = ""
end
for fragment in string.gmatch(s, "([^/\\]+)") do
t[#t + 1] = fragment
end
@ -280,76 +247,7 @@ local function split_on_slash(s, sep_pattern)
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
@ -361,80 +259,11 @@ function common.relative_path(ref_dir, dir)
end
local ups = ""
for k = i, #ref_ls do
ups = ups .. ".." .. PATHSEP
ups = ups .. "../"
end
local rel_path = ups .. table.concat(dir_ls, PATHSEP, i)
local rel_path = ups .. table.concat(dir_ls, "/", 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

View File

@ -1,18 +1,16 @@
local config = {}
config.project_scan_rate = 5
config.fps = 60
config.max_log_items = 80
config.message_timeout = 5
config.message_timeout = 3
config.mouse_wheel_scroll = 50 * SCALE
config.scroll_past_end = true
config.file_size_limit = 10
config.ignore_files = "^%."
config.symbol_pattern = "[%a_][%w_]*"
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
config.undo_merge_timeout = 0.3
config.max_undos = 10000
config.max_tabs = 8
config.always_show_tabs = true
config.highlight_current_line = true
config.line_height = 1.2
config.indent_size = 2
@ -21,20 +19,10 @@ config.line_limit = 80
config.max_symbols = 4000
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
config.trimwhitespace = false
return config

View File

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

View File

@ -1,5 +1,4 @@
local core = require "core"
local common = require "core.common"
local config = require "core.config"
local tokenizer = require "core.tokenizer"
local Object = require "core.object"
@ -25,7 +24,7 @@ function Highlighter:new(doc)
for i = self.first_invalid_line, max do
local state = (i > 1) and self.lines[i - 1].state
local line = self.lines[i]
if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
if not (line and line.init_state == state) then
self.lines[i] = self:tokenize_line(i, state)
end
end
@ -41,36 +40,16 @@ end
function Highlighter:reset()
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.max_wanted_line = 0
end
function Highlighter:invalidate(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)
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)
local res = {}

View File

@ -1,6 +1,5 @@
local Object = require "core.object"
local Highlighter = require "core.doc.highlighter"
local core = require "core"
local syntax = require "core.syntax"
local config = require "core.config"
local common = require "core.common"
@ -18,22 +17,36 @@ local function split_lines(text)
end
function Doc:new(filename, abs_filename, new_file)
self.new_file = new_file
local function 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
function Doc:new(filename)
self:reset()
if filename then
self:set_filename(filename, abs_filename)
if not new_file then
self:load(filename)
end
self:load(filename)
end
end
function Doc:reset()
self.lines = { "\n" }
self.selections = { 1, 1, 1, 1 }
self.cursor_clipboard = {}
self.selection = { a = { line=1, col=1 }, b = { line=1, col=1 } }
self.undo_stack = { idx = 1 }
self.redo_stack = { idx = 1 }
self.clean_change_id = 1
@ -47,30 +60,29 @@ function Doc:reset_syntax()
local syn = syntax.get(self.filename or "", header)
if self.syntax ~= syn then
self.syntax = syn
self.highlighter:soft_reset()
self.highlighter:reset()
end
end
function Doc:set_filename(filename, abs_filename)
function Doc:set_filename(filename)
filename = common.normalize_path(filename)
self.filename = filename
self.abs_filename = abs_filename
self.abs_filename = common.home_encode(system.absolute_path(filename))
end
function Doc:load(filename)
local fp = assert( io.open(filename, "rb") )
self:reset()
self:set_filename(filename)
self.lines = {}
local i = 1
for line in fp:lines() do
if line:byte(-1) == 13 then
line = line:sub(1, -2)
self.crlf = true
end
table.insert(self.lines, line .. "\n")
self.highlighter.lines[i] = false
i = i + 1
end
if #self.lines == 0 then
table.insert(self.lines, "\n")
@ -80,20 +92,17 @@ function Doc:load(filename)
end
function Doc:save(filename, abs_filename)
if not filename then
assert(self.filename, "no filename set to default to")
filename = self.filename
abs_filename = self.abs_filename
end
function Doc:save(filename)
filename = filename or assert(self.filename, "no filename set to default to")
local fp = assert( io.open(filename, "wb") )
for _, line in ipairs(self.lines) do
if self.crlf then line = line:gsub("\n", "\r\n") end
fp:write(line)
end
fp:close()
self:set_filename(filename, abs_filename)
self.new_file = false
if filename then
self:set_filename(filename)
end
self:reset_syntax()
self:clean()
end
@ -105,11 +114,7 @@ end
function Doc:is_dirty()
if self.new_file then
return #self.lines > 1 or #self.lines[1] > 1
else
return self.clean_change_id ~= self:get_change_id()
end
return self.clean_change_id ~= self:get_change_id()
end
@ -118,121 +123,49 @@ function Doc:clean()
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()
return self.undo_stack.idx
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)
limit = limit or math.huge
local result = {}
for idx, line1, col1, line2, col2 in self:get_selections() do
if idx > limit then break end
if line1 ~= line2 or col1 ~= col2 then
local text = self:get_text(line1, col1, line2, col2)
if text ~= "" then result[#result + 1] = text end
end
end
return table.concat(result, "\n")
end
function Doc:has_selection()
local line1, col1, line2, col2 = self:get_selection(false)
return line1 ~= line2 or col1 ~= col2
end
function Doc:has_any_selection()
for idx, line1, col1, line2, col2 in self:get_selections() do
if line1 ~= line2 or col1 ~= col2 then return true end
end
return false
end
function Doc:sanitize_selection()
for idx, line1, col1, line2, col2 in self:get_selections() do
self:set_selections(idx, line1, col1, line2, col2)
end
end
local function sort_positions(line1, col1, line2, col2)
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")
function Doc:set_selection(line1, col1, line2, col2, swap)
assert(not line2 == not col2, "expected 2 or 4 arguments")
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
line1, col1 = self:sanitize_position(line1, col1)
line2, col2 = self:sanitize_position(line2 or line1, col2 or col1)
common.splice(self.selections, (idx - 1)*4 + 1, rm == nil and 4 or rm, { line1, col1, line2, col2 })
self.selection.a.line, self.selection.a.col = line1, col1
self.selection.b.line, self.selection.b.col = line2, col2
end
function Doc:add_selection(line1, col1, line2, col2, swap)
local l1, c1 = sort_positions(line1, col1, line2 or line1, col2 or col1)
local target = #self.selections / 4 + 1
for idx, tl1, tc1 in self:get_selections(true) do
if l1 < tl1 or l1 == tl1 and c1 < tc1 then
target = idx
break
end
local function sort_positions(line1, col1, line2, col2)
if line1 > line2
or line1 == line2 and col1 > col2 then
return line2, col2, line1, col1, true
end
self:set_selections(target, line1, col1, line2, col2, swap, 0)
return line1, col1, line2, col2, false
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)
for i = (idx or (#self.selections - 3)), (idx or 5), -4 do
for j = 1, i - 4, 4 do
if self.selections[i] == self.selections[j] and
self.selections[i+1] == self.selections[j+1] then
common.splice(self.selections, i, 4)
common.splice(self.cursor_clipboard, i, 1)
break
end
end
function Doc:get_selection(sort)
local a, b = self.selection.a, self.selection.b
if sort then
return sort_positions(a.line, a.col, b.line, b.col)
end
if #self.selections <= 4 then self.cursor_clipboard = {} end
return a.line, a.col, b.line, b.col
end
local function selection_iterator(invariant, idx)
local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1)
if target > #invariant[1] or target <= 0 or (type(invariant[3]) == "number" and invariant[3] ~= idx - 1) then return end
if invariant[2] then
return idx+(invariant[3] and -1 or 1), sort_positions(table.unpack(invariant[1], target, target+4))
else
return idx+(invariant[3] and -1 or 1), table.unpack(invariant[1], target, target+4)
end
function Doc:has_selection()
local a, b = self.selection.a, self.selection.b
return not (a.line == b.line and a.col == b.col)
end
-- If idx_reverse is true, it'll reverse iterate. If nil, or false, regular iterate.
-- If a number, runs for exactly that iteration.
function Doc:get_selections(sort_intra, idx_reverse)
return selection_iterator, { self.selections, sort_intra, idx_reverse },
idx_reverse == true and ((#self.selections / 4) + 1) or ((idx_reverse or -1)+1)
function Doc:sanitize_selection()
self:set_selection(self:get_selection())
end
-- End of cursor seciton.
function Doc:sanitize_position(line, col)
line = common.clamp(line, 1, #self.lines)
@ -309,7 +242,7 @@ local function push_undo(undo_stack, time, type, ...)
end
local function pop_undo(self, undo_stack, redo_stack, modified)
local function pop_undo(self, undo_stack, redo_stack)
-- pop command
local cmd = undo_stack[undo_stack.idx - 1]
if not cmd then return end
@ -319,25 +252,21 @@ local function pop_undo(self, undo_stack, redo_stack, modified)
if cmd.type == "insert" then
local line, col, text = table.unpack(cmd)
self:raw_insert(line, col, text, redo_stack, cmd.time)
elseif cmd.type == "remove" then
local line1, col1, line2, col2 = table.unpack(cmd)
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
-- command and continue to execute it
local next = undo_stack[undo_stack.idx - 1]
if next and math.abs(cmd.time - next.time) < config.undo_merge_timeout then
return pop_undo(self, undo_stack, redo_stack, modified)
end
if modified then
self:on_text_change("undo")
return pop_undo(self, undo_stack, redo_stack)
end
end
@ -345,7 +274,6 @@ end
function Doc:raw_insert(line, col, text, undo_stack, time)
-- split text into lines and merge with line at insertion point
local lines = split_lines(text)
local len = #lines[#lines]
local before = self.lines[line]:sub(1, col - 1)
local after = self.lines[line]:sub(col)
for i = 1, #lines - 1 do
@ -355,23 +283,15 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
lines[#lines] = lines[#lines] .. after
-- splice lines into line array
common.splice(self.lines, line, 1, lines)
-- keep cursors where they should be
for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
if cline1 < line then break end
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
splice(self.lines, line, 1, lines)
-- push undo
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)
-- update highlighter and assure selection is in bounds
self.highlighter:insert_notify(line, #lines - 1)
self.highlighter:invalidate(line)
self:sanitize_selection()
end
@ -379,7 +299,7 @@ end
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
-- push undo
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)
-- get line content before/after removed text
@ -387,18 +307,10 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
local after = self.lines[line2]:sub(col2)
-- splice line into line array
common.splice(self.lines, line1, line2 - line1 + 1, { before .. after })
-- move all cursors back if they share a line with the removed text
for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
if 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
splice(self.lines, line1, line2 - line1 + 1, { before .. after })
-- update highlighter and assure selection is in bounds
self.highlighter:remove_notify(line1, line2 - line1)
self.highlighter:invalidate(line1)
self:sanitize_selection()
end
@ -407,7 +319,6 @@ function Doc:insert(line, col, text)
self.redo_stack = { idx = 1 }
line, col = self:sanitize_position(line, col)
self:raw_insert(line, col, text, self.undo_stack, system.get_time())
self:on_text_change("insert")
end
@ -417,160 +328,74 @@ function Doc:remove(line1, col1, line2, col2)
line2, col2 = self:sanitize_position(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:on_text_change("remove")
end
function Doc:undo()
pop_undo(self, self.undo_stack, self.redo_stack, false)
pop_undo(self, self.undo_stack, self.redo_stack)
end
function Doc:redo()
pop_undo(self, self.redo_stack, self.undo_stack, false)
pop_undo(self, self.redo_stack, self.undo_stack)
end
function Doc:text_input(text, idx)
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do
if line1 ~= line2 or col1 ~= col2 then
self:delete_to_cursor(sidx)
end
self:insert(line1, col1, text)
self:move_to_cursor(sidx, #text)
function Doc:text_input(text)
if self:has_selection() then
self:delete_to()
end
local line, col = self:get_selection()
self:insert(line, col, text)
self:move_to(#text)
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 new_text, n = fn(old_text)
if old_text ~= new_text then
self:insert(line2, col2, new_text)
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)
self:set_selections(idx, line1, col1, line2, col2)
self:set_selection(line1, col1, line2, col2, swap)
end
end
return n
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_cursor(idx, ...)
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
if line1 ~= line2 or col1 ~= col2 then
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 "")
function Doc:delete_to(...)
local line, col = self:get_selection(true)
if self:has_selection() then
self:remove(self:get_selection())
else
local indent = e and line:sub(1, e):gsub("\t", soft_tab) or ""
local number = #indent / #soft_tab
return e, indent:sub(1,
(rnd_up and math.ceil(number) or math.floor(number))*#soft_tab)
local line2, col2 = self:position_offset(line, col, ...)
self:remove(line, col, line2, col2)
line, col = sort_positions(line, col, line2, col2)
end
self:set_selection(line, col)
end
-- un/indents text; behaviour varies based on selection and un/indent.
-- * if there's a selection, it will stay static around the
-- text for both indenting and unindenting.
-- * if you are in the beginning whitespace of a line, and are indenting, the
-- cursor will insert the exactly appropriate amount of spaces, and jump the
-- cursor to the beginning of first non whitespace characters
-- * if you are not in the beginning whitespace of a line, and you indent, it
-- inserts the appropriate whitespace, as if you typed them normally.
-- * if you are unindenting, the cursor will jump to the start of the line,
-- and remove the appropriate amount of spaces (or a tab).
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
function Doc:move_to(...)
local line, col = self:get_selection()
self:set_selection(self:position_offset(line, col, ...))
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:on_close()
core.log_quiet("Closed doc \"%s\"", self:get_name())
function Doc:select_to(...)
local line, col, line2, col2 = self:get_selection()
line, col = self:position_offset(line, col, ...)
self:set_selection(line, col, line2, col2)
end

View File

@ -15,69 +15,36 @@ local function init_args(doc, line, col, text, opt)
opt = opt or default_opt
line, col = doc:sanitize_position(line, col)
if opt.no_case and not opt.regex then
text = text:lower()
if opt.no_case then
if opt.pattern then
text = text:gsub("%%?.", pattern_lower)
else
text = text:lower()
end
end
return doc, line, col, text, opt
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)
doc, line, col, text, opt = init_args(doc, line, col, text, opt)
local plain = not opt.pattern
local pattern = text
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
for line = line, #doc.lines do
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()
end
local s, e
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
local s, e = line_text:find(text, col, not opt.pattern)
if s then
return line, s, line, e + 1
end
col = opt.reverse and -1 or 1
col = 1
end
if opt.wrap then
opt = { no_case = opt.no_case, regex = opt.regex, reverse = opt.reverse }
if opt.reverse then
return search.find(doc, #doc.lines, #doc.lines[#doc.lines], text, opt)
else
return search.find(doc, 1, 1, text, opt)
end
opt = { no_case = opt.no_case, pattern = opt.pattern }
return search.find(doc, 1, 1, text, opt)
end
end

View File

@ -117,10 +117,6 @@ function translate.start_of_line(doc, line, col)
return line, 1
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)
return line, math.huge

View File

@ -9,7 +9,6 @@ local View = require "core.view"
local DocView = View:extend()
DocView.context = "session"
local function move_to_line_offset(dv, line, col, offset)
local xo = dv.last_x_offset
@ -56,6 +55,7 @@ function DocView:new(doc)
self.doc = assert(doc)
self.font = "code_font"
self.last_x_offset = {}
self.blink_timer = 0
end
@ -91,16 +91,13 @@ 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
return self.doc.abs_filename .. post
end
return self:get_name()
end
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
end
@ -116,8 +113,7 @@ end
function DocView:get_gutter_width()
local padding = style.padding.x * 2
return self:get_font():get_width(#self.doc.lines) + padding, padding
return self:get_font():get_width(#self.doc.lines) + style.padding.x * 2
end
@ -146,43 +142,29 @@ end
function DocView:get_col_x_offset(line, col)
local default_font = self:get_font()
local column = 1
local xoffset = 0
for _, type, text in self.doc.highlighter:each_token(line) do
local font = style.syntax_fonts[type] or default_font
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
local text = self.doc.lines[line]
if not text then return 0 end
return self:get_font():get_width(text:sub(1, col - 1))
end
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 default_font = self:get_font()
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
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
local subpixel_scale = self:get_font():subpixel_scale();
local x_subpixel = subpixel_scale * x + subpixel_scale / 2
for char in common.utf8_chars(text) do
local w = self:get_font():get_width_subpixel(char)
if xoffset >= subpixel_scale * x then
return (xoffset - x_subpixel > w / 2) and last_i or i
end
xoffset = xoffset + w
last_i = i
i = i + #char
end
return #line_text
return #text
end
@ -225,6 +207,47 @@ function DocView:scroll_to_make_visible(line, col)
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, ...)
DocView.super.on_mouse_moved(self, x, y, ...)
@ -236,41 +259,13 @@ function DocView:on_mouse_moved(x, y, ...)
if self.mouse_selecting then
local l1, c1 = self:resolve_screen_position(x, y)
local l2, c2, snap_type = table.unpack(self.mouse_selecting)
if keymap.modkeys["ctrl"] then
if l1 > l2 then l1, l2 = l2, l1 end
self.doc.selections = { }
for i = l1, l2 do
self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c2, #self.doc.lines[i]))
end
else
if snap_type then
l1, c1, l2, c2 = self:mouse_selection(self.doc, snap_type, l1, c1, l2, c2)
end
self.doc:set_selection(l1, c1, l2, c2)
end
local l2, c2 = table.unpack(self.mouse_selecting)
local clicks = self.mouse_selecting.clicks
self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2))
end
end
function DocView: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)
DocView.super.on_mouse_released(self, button)
self.mouse_selecting = nil
@ -289,18 +284,18 @@ function DocView:update()
if core.active_view == self then
self:scroll_to_make_visible(line, col)
end
core.blink_reset()
self.blink_timer = 0
self.last_line, self.last_col = line, col
end
-- update blink timer
if self == core.active_view and not self.mouse_selecting then
local T, t0 = config.blink_period, core.blink_start
local ta, tb = core.blink_timer, system.get_time()
if ((tb - t0) % T < T / 2) ~= ((ta - t0) % T < T / 2) then
local n = config.blink_period / 2
local prev = self.blink_timer
self.blink_timer = (self.blink_timer + 1 / config.fps) % config.blink_period
if (self.blink_timer > n) ~= (prev > n) then
core.redraw = true
end
core.blink_timer = tb
end
DocView.super.update(self)
@ -314,110 +309,87 @@ end
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 font = self:get_font()
local subpixel_scale = font:subpixel_scale()
local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset()
for _, type, text in self.doc.highlighter:each_token(idx) do
local color = style.syntax[type]
local font = style.syntax_fonts[type] or default_font
tx = renderer.draw_text(font, text, tx, ty, color)
tx = renderer.draw_text_subpixel(font, text, tx, ty, color)
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)
-- draw highlight if any selection ends on this line
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
local line, col = self.doc:get_selection()
-- draw selection if it overlaps this line
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
if idx >= line1 and idx <= line2 then
local text = self.doc.lines[idx]
if line1 ~= idx then col1 = 1 end
if line2 ~= idx then col2 = #text + 1 end
local x1 = x + self:get_col_x_offset(idx, col1)
local x2 = x + self:get_col_x_offset(idx, col2)
local lh = self:get_line_height()
if x1 ~= x2 then
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
end
end
local line1, col1, line2, col2 = self.doc:get_selection(true)
if idx >= line1 and idx <= line2 then
local text = self.doc.lines[idx]
if line1 ~= idx then col1 = 1 end
if line2 ~= idx then col2 = #text + 1 end
local x1 = x + self:get_col_x_offset(idx, col1)
local x2 = x + self:get_col_x_offset(idx, col2)
local lh = self:get_line_height()
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
end
-- draw line highlight if caret is on this line
if config.highlight_current_line and not self.doc:has_selection()
and line == idx and core.active_view == self then
self:draw_line_highlight(x + self.scroll.x, y)
end
-- draw line's text
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 < config.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
function DocView:draw_line_gutter(idx, x, y, width)
function DocView:draw_line_gutter(idx, x, y)
local color = style.line_number
for _, line1, _, line2 in self.doc:get_selections(true) do
if idx >= line1 and idx <= line2 then
color = style.line_number2
break
end
local line1, _, line2, _ = self.doc:get_selection(true)
if idx >= line1 and idx <= line2 then
color = style.line_number2
end
local yoffset = self:get_line_text_y_offset()
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
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()
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 lh = self:get_line_height()
local x, y = self:get_line_screen_position(minline)
local gw, gpad = self:get_gutter_width()
local _, y = self:get_line_screen_position(minline)
local x = self.position.x
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
end
local x, y = self:get_line_screen_position(minline)
local gw = self:get_gutter_width()
local pos = self.position
x, y = self:get_line_screen_position(minline)
-- 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)
core.push_clip_rect(pos.x + gw, pos.y, self.size.x, self.size.y)
for i = minline, maxline do
self:draw_line_body(i, x, y)
y = y + lh
end
self:draw_overlay()
core.pop_clip_rect()
self:draw_scrollbar()

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,18 +1,20 @@
local command = require "core.command"
local config = require "core.config"
local keymap = {}
keymap.modkeys = {}
keymap.map = {}
keymap.reverse_map = {}
local macos = PLATFORM == "Mac OS X"
local os4 = PLATFORM == "AmigaOS 4"
local modkey_map = {
["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_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 modkeys = { "ctrl", "alt", "altgr", "shift" }
local function key_to_stroke(k)
local stroke = ""
@ -25,24 +27,8 @@ local function key_to_stroke(k)
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)
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
commands = { commands }
end
@ -55,43 +41,18 @@ function keymap.add(map, overwrite)
end
end
for _, cmd in ipairs(commands) do
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
table.insert(keymap.reverse_map[cmd], stroke)
keymap.reverse_map[cmd] = stroke
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)
return table.unpack(keymap.reverse_map[cmd] or {})
return keymap.reverse_map[cmd]
end
function keymap.on_key_pressed(k, ...)
function keymap.on_key_pressed(k)
local mk = modkey_map[k]
if mk then
keymap.modkeys[mk] = true
@ -101,30 +62,18 @@ function keymap.on_key_pressed(k, ...)
end
else
local stroke = key_to_stroke(k)
local commands, performed = keymap.map[stroke]
local commands = keymap.map[stroke]
if commands then
for _, cmd in ipairs(commands) do
performed = command.perform(cmd, ...)
local performed = command.perform(cmd)
if performed then break end
end
return performed
return true
end
end
return false
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)
local mk = modkey_map[k]
@ -134,22 +83,14 @@ function keymap.on_key_released(k)
end
if macos then
local keymap_macos = require("core.keymap-macos")
keymap_macos(keymap)
return keymap
end
keymap.add_direct {
keymap.add {
["ctrl+shift+p"] = "core:find-command",
["ctrl+p"] = "core:find-file",
["ctrl+o"] = "core:open-file",
["ctrl+n"] = "core:new-doc",
["ctrl+shift+c"] = "core:change-project-folder",
["ctrl+shift+o"] = "core:open-project-folder",
["ctrl+shift+r"] = "core:restart",
["alt+return"] = "core:toggle-fullscreen",
["f11"] = "core:toggle-fullscreen",
["alt+shift+j"] = "root:split-left",
["alt+shift+l"] = "root:split-right",
@ -174,14 +115,11 @@ keymap.add_direct {
["alt+7"] = "root:switch-to-tab-7",
["alt+8"] = "root:switch-to-tab-8",
["alt+9"] = "root:switch-to-tab-9",
["wheel"] = "root:scroll",
["ctrl+f"] = "find-replace:find",
["ctrl+r"] = "find-replace:replace",
["f3"] = "find-replace:repeat-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+s"] = "doc:save",
["ctrl+shift+s"] = "doc:save-as",
@ -193,7 +131,7 @@ keymap.add_direct {
["ctrl+v"] = "doc:paste",
["ctrl+insert"] = "doc:copy",
["shift+insert"] = "doc:paste",
["escape"] = { "command:escape", "doc:select-none", "dialog:select-no" },
["escape"] = { "command:escape", "doc:select-none" },
["tab"] = { "command:complete", "doc:indent" },
["shift+tab"] = "doc:unindent",
["backspace"] = "doc:backspace",
@ -204,43 +142,35 @@ keymap.add_direct {
["shift+delete"] = "doc:delete",
["ctrl+delete"] = "doc:delete-to-next-word-end",
["ctrl+shift+delete"] = "doc:delete-to-next-word-end",
["return"] = { "command:submit", "doc:newline", "dialog:select" },
["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" },
["return"] = { "command:submit", "doc:newline" },
["keypad enter"] = { "command:submit", "doc:newline" },
["ctrl+return"] = "doc:newline-below",
["ctrl+shift+return"] = "doc:newline-above",
["ctrl+j"] = "doc:join-lines",
["ctrl+a"] = "doc:select-all",
["ctrl+d"] = { "find-replace:select-add-next", "doc:select-word" },
["ctrl+f3"] = "find-replace:select-next",
["ctrl+shift+f3"] = "find-replace:select-previous",
["ctrl+d"] = { "find-replace:select-next", "doc:select-word" },
["ctrl+l"] = "doc:select-lines",
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
["ctrl+/"] = "doc:toggle-line-comments",
["ctrl+up"] = "doc:move-lines-up",
["ctrl+down"] = "doc:move-lines-down",
["ctrl+shift+d"] = "doc:duplicate-lines",
["ctrl+shift+k"] = "doc:delete-lines",
["left"] = { "doc:move-to-previous-char", "dialog:previous-entry" },
["right"] = { "doc:move-to-next-char", "dialog:next-entry"},
["left"] = "doc:move-to-previous-char",
["right"] = "doc:move-to-next-char",
["up"] = { "command:select-previous", "doc:move-to-previous-line" },
["down"] = { "command:select-next", "doc:move-to-next-line" },
["ctrl+left"] = "doc:move-to-previous-word-start",
["ctrl+right"] = "doc:move-to-next-word-end",
["ctrl+["] = "doc:move-to-previous-block-start",
["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",
["ctrl+home"] = "doc:move-to-start-of-doc",
["ctrl+end"] = "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",
@ -249,15 +179,12 @@ keymap.add_direct {
["ctrl+shift+right"] = "doc:select-to-next-word-end",
["ctrl+shift+["] = "doc:select-to-previous-block-start",
["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",
["ctrl+shift+home"] = "doc:select-to-start-of-doc",
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
["shift+pageup"] = "doc:select-to-previous-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

View File

@ -1,45 +1,14 @@
local core = require "core"
local common = require "core.common"
local style = require "core.style"
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()
LogView.context = "session"
function LogView:new()
LogView.super.new(self)
self.last_item = core.log_items[#core.log_items]
self.expanding = {}
self.scrollable = true
self.yoffset = 0
end
@ -50,55 +19,6 @@ function LogView:get_name()
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()
local item = core.log_items[#core.log_items]
if self.last_item ~= item then
@ -107,14 +27,6 @@ function LogView:update()
self.yoffset = -(style.font:get_height() + style.padding.y)
end
local expanding = self.expanding[1]
if expanding then
self:move_towards(expanding, "current", expanding.target)
if expanding.current == expanding.target then
table.remove(self.expanding, 1)
end
end
self:move_towards("yoffset", 0)
LogView.super.update(self)
@ -123,48 +35,38 @@ end
local function draw_text_multiline(font, text, x, y, color)
local th = font:get_height()
local resx = x
local resx, resy = x, y
for line in text:gmatch("[^\n]+") do
resy = y
resx = renderer.draw_text(style.font, line, x, y, color)
y = y + th
end
return resx, y
return resx, resy
end
function LogView:draw()
self:draw_background(style.background)
local ox, oy = self:get_content_offset()
local th = style.font:get_height()
local lh = th + style.padding.y -- for one line
for _, item, x, y, w in self:each_item() do
x = x + style.padding.x
local y = oy + style.padding.y + self.yoffset
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)
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 = common.draw_text(style.code_font, style.dim, is_expanded(item) and "-" or "+", "left", x, y, w, lh)
x = x + style.padding.x
w = w - (x - self:get_content_offset())
if is_expanded(item) then
y = y + common.round(style.padding.y / 2)
_, y = draw_text_multiline(style.font, item.text, x, y, style.text)
local at = "at " .. common.home_encode(item.at)
_, y = common.draw_text(style.font, style.dim, at, "left", x, y, w, lh)
if item.info then
_, y = draw_text_multiline(style.font, item.info, x, y, style.dim)
end
else
local line, has_newline = string.match(item.text, "([^\n]+)(\n?)")
if has_newline ~= "" then
line = line .. " ..."
end
_, y = common.draw_text(style.font, style.text, line, "left", x, y, w, lh)
local subx = x
x, y = draw_text_multiline(style.font, item.text, x, y, style.text)
renderer.draw_text(style.font, " at " .. item.at, x, y, style.dim)
y = y + th
if item.info then
subx, y = draw_text_multiline(style.font, item.info, subx, y, style.dim)
y = y + th
end
y = y + style.padding.y
end
end

View File

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

View File

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

View File

@ -1,15 +0,0 @@
local modkeys = {}
modkeys.map = {
["left amiga"] = "cmd",
["right amiga"] = "cmd",
["control"] = "ctrl",
["left shift"] = "shift",
["right shift"] = "shift",
["left alt"] = "alt",
["right alt"] = "altgr",
}
modkeys.keys = { "cmd", "ctrl", "alt", "altgr", "shift" }
return modkeys

View File

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

View File

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

View File

@ -20,6 +20,17 @@ function Object:extend()
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)
local mt = getmetatable(self)
while mt do

View File

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

View File

@ -1,11 +1,519 @@
local core = require "core"
local common = require "core.common"
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 CommandView = require "core.commandview"
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 = 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
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_close = 0
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:tab_mouse_moved(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:close_view(root, view)
local new_active_view = view == self.active_view
local do_close = function()
if #self.views > 1 then
local idx = self:get_view_idx(view)
table.remove(self.views, idx)
if new_active_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"]
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
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)
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
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:tab_mouse_moved(px, py)
local tab_index = self:get_tab_overlapping_point(px, py)
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 then
self.hovered_close = tab_index
return
end
end
self.hovered_close = 0
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
-- 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
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, y1, y2)
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
renderer.draw_rect(x + w, y, ds, h, style.divider)
renderer.draw_rect(x - ds, y, ds, h, style.divider)
if view == self.active_view then
color = style.text
renderer.draw_rect(x, y, w, h, style.background)
end
local cx, cw, cspace = close_button_location(x, w)
local show_close_button = (view == self.active_view or i == self.hovered_tab)
if show_close_button then
local close_style = self.hovered_close == i and style.text or style.dim
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
end
if i == self.hovered_tab then
color = style.text
end
local padx = style.padding.x
core.push_clip_rect(x, y, show_close_button and cx - x - cspace or w - padx, h)
x, w = x + padx, w - padx * 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
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
function Node:resize(axis, value)
if self.type == 'leaf' then
-- The logic here is: accept the resize only if locked along the axis
-- and is declared "resizable". 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.locked and self.locked[axis]) and self.resizable then
assert(self.active_view.set_target_size, "internal error: the view of a resizable \"locked\" node do not provide a set_target_size method")
return self.active_view:set_target_size(axis, value)
end
else
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
local RootView = View:extend()
function RootView:new()
@ -13,14 +521,6 @@ function RootView:new()
self.root_node = Node()
self.deferred_draws = {}
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
@ -61,24 +561,6 @@ function RootView:get_primary_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
@ -95,122 +577,42 @@ function RootView:open_doc(doc)
end
function RootView:close_all_docviews(keep_active)
self.root_node:close_all_docviews(keep_active)
end
-- Function to intercept mouse pressed events on the active view.
-- Do nothing by default.
function RootView.on_view_mouse_pressed(button, x, y, clicks)
function RootView:close_all_docviews()
self.root_node:close_all_docviews()
end
function RootView:on_mouse_pressed(button, x, y, clicks)
local div = self.root_node:get_divider_overlapping_point(x, y)
local node = self.root_node:get_child_overlapping_point(x, y)
if div and (node and not node.active_view:scrollbar_overlaps_point(x, y)) then
if div then
self.dragged_divider = div
return true
end
if node.hovered_scroll_button > 0 then
node:scroll_tabs(node.hovered_scroll_button)
return true
return
end
local node = self.root_node:get_child_overlapping_point(x, y)
local idx = node:get_tab_overlapping_point(x, y)
if idx then
if button == "middle" or node.hovered_close == idx then
node:close_view(self.root_node, node.views[idx])
return true
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
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
return style.drag_overlay_tab
core.set_active_view(node.active_view)
node.active_view:on_mouse_pressed(button, x, y, clicks)
end
end
function RootView:set_show_overlay(overlay, status)
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, ...)
function RootView:on_mouse_released(...)
if self.dragged_divider then
self.dragged_divider = nil
end
if self.dragged_node then
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
self.root_node:on_mouse_released(...)
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
local accept_resize = node.a:resize(axis, value) or node.b:resize(axis, node.size[axis] - value)
if not accept_resize then
node.divider = node.divider + delta / node.size[axis]
end
@ -218,12 +620,6 @@ end
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
local node = self.dragged_divider
if node.type == "hsplit" then
@ -238,33 +634,19 @@ function RootView:on_mouse_moved(x, y, dx, dy)
end
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.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 tab_index = self.overlapping_node and self.overlapping_node:get_tab_overlapping_point(x, y)
if self.overlapping_node and self.overlapping_node:get_scroll_button_index(x, y) then
core.request_cursor("arrow")
elseif div and (self.overlapping_node and not self.overlapping_node.active_view:scrollbar_overlaps_point(x, y)) then
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
elseif tab_index then
core.request_cursor("arrow")
elseif self.overlapping_node then
core.request_cursor(self.overlapping_node.active_view.cursor)
if div then
local axis = (div.type == "hsplit" and "x" or "y")
if div.a:is_resizable(axis) and div.b:is_resizable(axis) then
system.set_cursor(div.type == "hsplit" and "sizeh" or "sizev")
end
elseif node:get_tab_overlapping_point(x, y) then
system.set_cursor("arrow")
else
system.set_cursor(node.active_view.cursor)
end
end
@ -272,7 +654,7 @@ end
function RootView:on_mouse_wheel(...)
local x, y = self.mouse.x, self.mouse.y
local node = self.root_node:get_child_overlapping_point(x, y)
return node.active_view:on_mouse_wheel(...)
node.active_view:on_mouse_wheel(...)
end
@ -286,110 +668,10 @@ function RootView:on_focus_lost(...)
core.redraw = true
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()
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_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
@ -399,16 +681,6 @@ function RootView:draw()
local t = table.remove(self.deferred_draws)
t.fn(table.unpack(t))
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

View File

@ -1,34 +1,17 @@
-- this file is used by lite-xl to setup the Lua environment when starting
VERSION = "2.0.3r1"
MOD_VERSION = "2"
-- this file is used by lite-xl to setup the Lua environment
-- when starting
VERSION = "1.16.2"
SCALE = tonumber(os.getenv("LITE_SCALE") or os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or SCALE
SCALE = tonumber(os.getenv("LITE_SCALE")) or 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'))
local prefix = EXEDIR:match("^(.+)[/\\]bin$")
DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data')
USERDIR = 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

View File

@ -6,7 +6,6 @@ local style = require "core.style"
local DocView = require "core.docview"
local LogView = require "core.logview"
local View = require "core.view"
local Object = require "core.object"
local StatusView = View:extend()
@ -30,7 +29,6 @@ function StatusView:on_mouse_pressed()
and not core.active_view:is(LogView) then
command.perform "core:open-log"
end
return true
end
@ -72,7 +70,7 @@ local function draw_items(self, items, x, y, draw_fn)
local color = style.text
for _, item in ipairs(items) do
if Object.is(item, renderer.font) then
if type(item) == "userdata" then
font = item
elseif type(item) == "table" then
color = item
@ -109,9 +107,9 @@ function StatusView:get_items()
local dv = core.active_view
local line, col = dv.doc:get_selection()
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"
local indent = dv.doc.indent_info
local indent_label = (indent and indent.type == "hard") and "tabs: " or "spaces: "
local indent_size = indent and tostring(indent.size) .. (indent.confirmed and "" or "*") or "unknown"
return {
dirty and style.accent or style.text, style.icon_font, "f",

View File

@ -21,55 +21,37 @@ style.tab_width = common.round(170 * SCALE)
--
-- 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.
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 15 * SCALE)
style.big_font = style.font:copy(46 * SCALE)
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 16 * SCALE, {antialiasing="grayscale", hinting="full"})
style.icon_big_font = style.icon_font:copy(23 * SCALE)
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 15 * SCALE)
style.font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 13 * SCALE)
style.big_font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 34 * SCALE)
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 14 * SCALE, {antialiasing="grayscale", hinting="full"})
style.icon_big_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 20 * SCALE, {antialiasing="grayscale", hinting="full"})
style.code_font = renderer.font.load(DATADIR .. "/fonts/monospace.ttf", 12 * SCALE)
style.background = { common.color "#2e2e32" } -- Docview
style.background2 = { common.color "#252529" } -- Treeview
style.background3 = { common.color "#252529" } -- Command view
style.background = { common.color "#2e2e32" }
style.background2 = { common.color "#252529" }
style.background3 = { common.color "#252529" }
style.text = { common.color "#97979c" }
style.caret = { common.color "#93DDFA" }
style.accent = { common.color "#e1e1e6" }
-- style.dim - text color for nonactive tabs, tabs divider, prefix in log and
-- search result, hotkeys for context menu and command view
style.dim = { common.color "#525257" }
style.divider = { common.color "#202024" } -- Line between nodes
style.divider = { common.color "#202024" }
style.selection = { common.color "#48484f" }
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.scrollbar = { common.color "#414146" }
style.scrollbar2 = { common.color "#4b4b52" } -- Hovered
style.nagbar = { common.color "#FF0000" }
style.nagbar_text = { common.color "#FFFFFF" }
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
style.drag_overlay = { common.color "rgba(255,255,255,0.1)" }
style.drag_overlay_tab = { common.color "#93DDFA" }
style.good = { common.color "#72b886" }
style.warn = { common.color "#FFA94D" }
style.error = { common.color "#FF3333" }
style.modified = { common.color "#1c7c9c" }
style.scrollbar2 = { common.color "#4b4b52" }
style.syntax = {}
style.syntax["normal"] = { common.color "#e1e1e6" }
style.syntax["symbol"] = { common.color "#e1e1e6" }
style.syntax["comment"] = { common.color "#676b6f" }
style.syntax["keyword"] = { common.color "#E58AC9" } -- local function end if case
style.syntax["keyword2"] = { common.color "#F77483" } -- self int float
style.syntax["keyword"] = { common.color "#E58AC9" }
style.syntax["keyword2"] = { common.color "#F77483" }
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["operator"] = { common.color "#93DDFA" } -- = + - / < >
style.syntax["operator"] = { common.color "#93DDFA" }
style.syntax["function"] = { common.color "#93DDFA" }
-- This can be used to override fonts per syntax group.
-- The syntax highlighter will take existing values from this table and
-- 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

View File

@ -3,7 +3,7 @@ local common = require "core.common"
local syntax = {}
syntax.items = {}
local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} }
local plain_text_syntax = { patterns = {}, symbols = {} }
function syntax.add(t)
@ -22,7 +22,7 @@ end
function syntax.get(filename, header)
return find(filename, "files")
or (header and find(header, "headers"))
or find(header, "headers")
or plain_text_syntax
end

View File

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

View File

@ -1,8 +1,6 @@
local syntax = require "core.syntax"
local common = require "core.common"
local tokenizer = {}
local function push_token(t, type, text)
local prev_type = t[#t-1]
local prev_text = t[#t]
@ -16,220 +14,72 @@ local function push_token(t, type, text)
end
local function push_tokens(t, syn, pattern, full_text, find_results)
if #find_results > 2 then
-- We do some manipulation with find_results so that it's arranged
-- like this:
-- { start, end, i_1, i_2, i_3, …, i_last }
-- Each position spans characters from i_n to ((i_n+1) - 1), to form
-- consecutive spans of text.
--
-- If i_1 is not equal to start, start is automatically inserted at
-- that index.
if find_results[3] ~= find_results[1] then
table.insert(find_results, 3, find_results[1])
local function is_escaped(text, idx, esc)
local byte = esc:byte()
local count = 0
for i = idx - 1, 1, -1 do
if text:byte(i) ~= byte then break end
count = count + 1
end
return count % 2 == 1
end
local function find_non_escaped(text, pattern, offset, esc)
while true do
local s, e = text:find(pattern, offset)
if not s then break end
if esc and is_escaped(text, s, esc) then
offset = e + 1
else
return s, e
end
-- 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
-- State is a 32-bit number that is four separate bytes, illustrating how many
-- differnet delimiters we have open, and which subsyntaxes we have active.
-- At most, there are 3 subsyntaxes active at the same time. Beyond that,
-- does not support further highlighting.
-- You can think of it as a maximum 4 integer (0-255) stack. It always has
-- 1 integer in it. Calling `push_subsyntax` increases the stack depth. Calling
-- `pop_subsyntax` decreases it. The integers represent the index of a pattern
-- that we're following in the syntax. The top of the stack can be any valid
-- pattern index, any integer lower in the stack must represent a pattern that
-- specifies a subsyntax.
-- If you do not have subsyntaxes in your syntax, the three most
-- singificant numbers will always be 0, the stack will only ever be length 1
-- and the state variable will only ever range from 0-255.
local function retrieve_syntax_state(incoming_syntax, state)
local current_syntax, subsyntax_info, current_pattern_idx, current_level =
incoming_syntax, nil, state, 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)
function tokenizer.tokenize(syntax, text, state)
local res = {}
local i = 1
if #incoming_syntax.patterns == 0 then
if #syntax.patterns == 0 then
return { "normal", text }
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
-- continue trying to match the end pattern of a pair if we have a state set
if current_pattern_idx > 0 then
local p = current_syntax.patterns[current_pattern_idx]
local s, e = find_text(text, p, i, false, true)
if state then
local p = syntax.patterns[state]
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
push_token(res, subsyntax_info.type, text:sub(i, e))
-- On finding unescaped delimiter, pop it.
pop_subsyntax()
push_token(res, p.type, text:sub(i, e))
state = nil
i = e + 1
else
push_token(res, p.type, text:sub(i))
break
end
end
-- find matching pattern
local matched = false
for n, p in ipairs(current_syntax.patterns) do
local find_results = { find_text(text, p, i, true, false) }
if find_results[1] then
-- matched pattern; make and add tokens
push_tokens(res, current_syntax, p, text, find_results)
for n, p in ipairs(syntax.patterns) do
local pattern = (type(p.pattern) == "table") and p.pattern[1] or p.pattern
local s, e = text:find("^" .. pattern, i)
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
if type(p.pattern or p.regex) == "table" then
-- If we have a subsyntax, push that onto the subsyntax stack.
if p.syntax then
push_subsyntax(p, n)
else
set_subsyntax_pattern_idx(n)
end
if type(p.pattern) == "table" then
state = n
end
-- move cursor past this token
i = find_results[2] + 1
i = e + 1
matched = true
break
end
@ -237,13 +87,8 @@ function tokenizer.tokenize(incoming_syntax, text, state)
-- consume character if we didn't match
if not matched then
local n = 0
-- reach the next character
while text:byte(i + n + 1) and common.is_utf8_cont(text, i + n + 1) do
n = n + 1
end
push_token(res, "normal", text:sub(i, i + n))
i = i + n + 1
push_token(res, "normal", text:sub(i, i))
i = i + 1
end
end

View File

@ -7,10 +7,6 @@ local Object = require "core.object"
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()
self.position = { x = 0, y = 0 }
@ -28,12 +24,7 @@ function View:move_towards(t, k, dest, rate)
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)
t[k] = common.lerp(val, dest, rate or 0.5)
end
if val ~= dest then
core.redraw = true
@ -102,10 +93,14 @@ function View:on_text_input(text)
-- no-op
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
function View:get_content_bounds()
local x = self.scroll.x
local y = self.scroll.y
@ -136,7 +131,7 @@ end
function View:draw_background(color)
local x, y = self.position.x, self.position.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

Binary file not shown.

Binary file not shown.

BIN
data/fonts/font.ttf Normal file

Binary file not shown.

Binary file not shown.

BIN
data/fonts/monospace.ttf Normal file

Binary file not shown.

View File

@ -1,4 +1,3 @@
-- mod-version:2 -- lite-xl 2.0
local core = require "core"
local common = require "core.common"
local config = require "core.config"
@ -8,65 +7,25 @@ local keymap = require "core.keymap"
local translate = require "core.doc.translate"
local RootView = require "core.rootview"
local DocView = require "core.docview"
local Doc = require "core.doc"
config.plugins.autocomplete = {
-- Amount of characters that need to be written for autocomplete
min_len = 3,
-- The max amount of visible items
max_height = 6,
-- The max amount of scrollable items
max_suggestions = 100,
}
config.autocomplete_max_suggestions = 6
local autocomplete = {}
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 }
function autocomplete.add(t, triggered_manually)
function autocomplete.add(t)
local items = {}
for text, info in pairs(t.items) do
if type(info) == "table" then
table.insert(
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 }
info = (type(info) == "string") and info
table.insert(items, setmetatable({ text = text, info = info }, mt))
end
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
end
--
-- Thread that scans open document symbols and cache them
--
local max_symbols = config.max_symbols
local max_symbols = config.max_symbols or 2000
core.add_thread(function()
local cache = setmetatable({}, { __mode = "k" })
@ -149,39 +108,16 @@ local last_line, last_col
local function reset_suggestions()
suggestions_idx = 1
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
local function in_table(value, table_array)
for i, element in pairs(table_array) do
if element == value then
return true
end
end
return false
end
local function update_suggestions()
local doc = core.active_view.doc
local filename = doc and doc.filename or ""
local map = autocomplete.map
if triggered_manually then
map = autocomplete.map_manually
end
-- get all relevant suggestions for given filename
local items = {}
for _, v in pairs(map) do
for _, v in pairs(autocomplete.map) do
if common.match_pattern(filename, v.files) then
for _, item in pairs(v.items) do
table.insert(items, item)
@ -192,7 +128,7 @@ local function update_suggestions()
-- fuzzy match, remove duplicates and store
items = common.fuzzy_match(items, partial)
local j = 1
for i = 1, config.plugins.autocomplete.max_suggestions do
for i = 1, config.autocomplete_max_suggestions do
suggestions[i] = items[j]
while items[j] and items[i].text == items[j].text do
items[i].info = items[i].info or items[j].info
@ -201,6 +137,7 @@ local function update_suggestions()
end
end
local function get_partial_symbol()
local doc = core.active_view.doc
local line2, col2 = doc:get_selection()
@ -208,12 +145,14 @@ local function get_partial_symbol()
return doc:get_text(line1, col1, line2, col2)
end
local function get_active_view()
if getmetatable(core.active_view) == DocView then
return core.active_view
end
end
local function get_suggestions_rect(av)
if #suggestions == 0 then
return 0, 0, 0, 0
@ -235,67 +174,15 @@ local function get_suggestions_rect(av)
max_width = math.max(max_width, w)
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
x - style.padding.x,
y - style.padding.y,
max_width + style.padding.x * 2,
max_items * (th + style.padding.y) + style.padding.y
#suggestions * (th + style.padding.y) + style.padding.y
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)
if #suggestions <= 0 then
return
end
local ah = config.plugins.autocomplete.max_height
-- draw background rect
local rx, ry, rw, rh = get_suggestions_rect(av)
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 lh = font:get_height() + style.padding.y
local y = ry + style.padding.y / 2
local show_count = #suggestions <= ah and #suggestions or ah
local start_index = suggestions_idx > ah and (suggestions_idx-(ah-1)) or 1
for i=start_index, start_index+show_count-1, 1 do
if not suggestions[i] then
break
end
local s = suggestions[i]
for i, s in ipairs(suggestions) do
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)
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)
end
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
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
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()
if av then
-- update partial symbol and suggestions
partial = get_partial_symbol()
if #partial >= config.plugins.autocomplete.min_len or triggered_manually then
if #partial >= 3 then
update_suggestions()
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
last_line, last_col = av.doc:get_selection()
else
reset_suggestions()
end
@ -381,30 +232,6 @@ local function show_autocomplete()
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(...)
update(...)
@ -413,19 +240,13 @@ RootView.update = function(...)
if av then
-- reset suggestions if caret was moved
local line, col = av.doc:get_selection()
if not triggered_manually then
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
if line ~= last_line or col ~= last_col then
reset_suggestions()
end
end
end
RootView.draw = function(...)
draw(...)
@ -436,53 +257,12 @@ RootView.draw = function(...)
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()
return get_active_view() and #suggestions > 0
end
command.add(predicate, {
["autocomplete:complete"] = function()
local doc = core.active_view.doc
@ -502,19 +282,12 @@ command.add(predicate, {
suggestions_idx = math.min(suggestions_idx + 1, #suggestions)
end,
["autocomplete:cycle"] = function()
local newidx = suggestions_idx + 1
suggestions_idx = newidx > #suggestions and 1 or newidx
end,
["autocomplete:cancel"] = function()
reset_suggestions()
end,
})
--
-- Keymaps
--
keymap.add {
["tab"] = "autocomplete:complete",
["up"] = "autocomplete:previous",

View File

@ -1,8 +1,8 @@
-- mod-version:2 -- lite-xl 2.0
local core = require "core"
local config = require "core.config"
local Doc = require "core.doc"
local times = setmetatable({}, { __mode = "k" })
local function update_time(doc)
@ -10,6 +10,7 @@ local function update_time(doc)
times[doc] = info.modified
end
local function reload_doc(doc)
local fp = io.open(doc.filename, "r")
local text = fp:read("*a")
@ -25,19 +26,23 @@ local function reload_doc(doc)
core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename)
end
local on_modify = core.on_dirmonitor_modify
core.on_dirmonitor_modify = function(dir, filepath)
local abs_filename = dir.name .. PATHSEP .. filepath
for _, doc in ipairs(core.docs) do
local info = system.get_file_info(doc.filename or "")
if doc.abs_filename == abs_filename and info and times[doc] ~= info.modified then
reload_doc(doc)
break
core.add_thread(function()
while true do
-- check all doc modified times
for _, doc in ipairs(core.docs) do
local info = system.get_file_info(doc.filename or "")
if info and times[doc] ~= info.modified then
reload_doc(doc)
end
coroutine.yield()
end
-- wait for next scan
coroutine.yield(config.project_scan_rate)
end
on_modify(dir, filepath)
end
end)
-- patch `Doc.save|load` to store modified time
local load = Doc.load

View File

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

View File

@ -1,4 +1,3 @@
-- mod-version:2 -- lite-xl 2.0
local core = require "core"
local command = require "core.command"
local common = require "core.common"
@ -76,7 +75,7 @@ local function get_non_empty_lines(syntax, lines)
end
local auto_detect_max_lines = 100
local auto_detect_max_lines = 200
local function detect_indent_stat(doc)
local stat = {}
@ -99,16 +98,21 @@ local function detect_indent_stat(doc)
end
local doc_text_input = Doc.text_input
local adjust_threshold = 4
local function update_cache(doc)
local type, size, score = detect_indent_stat(doc)
local score_threshold = 4
if score < score_threshold then
-- use default values
type = config.tab_type
size = config.indent_size
end
cache[doc] = { type = type, size = size, confirmed = (score >= score_threshold) }
cache[doc] = { type = type, size = size, confirmed = (score >= adjust_threshold) }
doc.indent_info = cache[doc]
if score < adjust_threshold and Doc.text_input == doc_text_input then
Doc.text_input = function(self, ...)
doc_text_input(self, ...)
update_cache(self)
end
elseif score >= adjust_threshold and Doc.text_input ~= doc_text_input then
Doc.text_input = doc_text_input
end
end
@ -121,86 +125,31 @@ end
local clean = Doc.clean
function Doc:clean(...)
clean(self, ...)
local _, _, confirmed = self:get_indent_info()
if not confirmed then
update_cache(self)
update_cache(self)
end
local function with_indent_override(doc, fn, ...)
local c = cache[doc]
if not c then
return fn(...)
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
local function set_indent_type(doc, type)
local _, indent_size = doc:get_indent_info()
cache[doc] = {type = type,
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
)
local perform = command.perform
function command.perform(...)
return with_indent_override(core.active_view.doc, perform, ...)
end
local function set_indent_size(doc, size)
local indent_type = doc:get_indent_info()
cache[doc] = {type = indent_type,
size = size,
confirmed = true}
doc.indent_info = cache[doc]
local draw = DocView.draw
function DocView:draw(...)
return with_indent_override(self.doc, draw, self, ...)
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
})

View File

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

View File

@ -1,25 +1,20 @@
-- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax"
syntax.add {
name = "C",
files = { "%.c$", "%.h$", "%.inl$" },
files = { "%.c$", "%.h$", "%.inl$", "%.cpp$", "%.hpp$" },
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 = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
{ pattern = "[%a_][%w_]*", type = "symbol" },
{ pattern = "#include%s()<.->", type = {"keyword", "string"} },
{ pattern = "#[%a_][%w_]*", type = "keyword" },
{ pattern = "//.-\n", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" },
{ pattern = { "#", "[^\\]\n" }, 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 = "[%a_][%w_]*%f[(]", type = "function" },
{ pattern = "[%a_][%w_]*", type = "symbol" },
},
symbols = {
["if"] = "keyword",
@ -33,6 +28,8 @@ syntax.add {
["continue"] = "keyword",
["return"] = "keyword",
["goto"] = "keyword",
["struct"] = "keyword",
["union"] = "keyword",
["typedef"] = "keyword",
["enum"] = "keyword",
["extern"] = "keyword",
@ -44,7 +41,8 @@ syntax.add {
["case"] = "keyword",
["default"] = "keyword",
["auto"] = "keyword",
["void"] = "keyword2",
["const"] = "keyword",
["void"] = "keyword",
["int"] = "keyword2",
["short"] = "keyword2",
["long"] = "keyword2",
@ -56,17 +54,6 @@ syntax.add {
["true"] = "literal",
["false"] = "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",
},
}

View File

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

View File

@ -1,8 +1,6 @@
-- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax"
syntax.add {
name = "CSS",
files = { "%.css$" },
patterns = {
{ pattern = "\\.", type = "normal" },
@ -11,8 +9,7 @@ syntax.add {
{ pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = "[%a][%w-]*%s*%f[:]", type = "keyword" },
{ pattern = "#%x%x%x%x%x%x%f[%W]",type = "string" },
{ pattern = "#%x%x%x%f[%W]", type = "string" },
{ pattern = "#%x+", type = "string" },
{ pattern = "-?%d+[%d%.]*p[xt]", type = "number" },
{ pattern = "-?%d+[%d%.]*deg", type = "number" },
{ pattern = "-?%d+[%d%.]*", type = "number" },

View File

@ -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 = {},
}

View File

@ -1,19 +1,17 @@
-- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax"
syntax.add {
name = "JavaScript",
files = { "%.js$", "%.json$", "%.cson$" },
comment = "//",
patterns = {
{ pattern = "//.-\n", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" },
{ pattern = { '/[^= ]', '/', '\\' },type = "string" },
{ pattern = { '/', '/', '\\' }, type = "string" },
{ pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = { "`", "`", '\\' }, type = "string" },
{ pattern = "0x[%da-fA-F_]+n?", type = "number" },
{ pattern = "-?%d+[%d%.eE_n]*", type = "number" },
{ pattern = "0x[%da-fA-F]+", type = "number" },
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
{ pattern = "-?%.?%d+", type = "number" },
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
@ -42,7 +40,6 @@ syntax.add {
["if"] = "keyword",
["import"] = "keyword",
["in"] = "keyword",
["of"] = "keyword",
["instanceof"] = "keyword",
["let"] = "keyword",
["new"] = "keyword",

View File

@ -1,34 +1,25 @@
-- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax"
syntax.add {
name = "Lua",
files = "%.lua$",
headers = "^#!.*[ /]lua",
comment = "--",
patterns = {
{ pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = { "%[%[", "%]%]" }, type = "string" },
{ pattern = { "%-%-%[%[", "%]%]"}, type = "comment" },
{ pattern = "%-%-.-\n", type = "comment" },
{ pattern = "0x%x+%.%x*[pP][-+]?%d+", type = "number" },
{ pattern = "0x%x+%.%x*", type = "number" },
{ pattern = "0x%.%x+[pP][-+]?%d+", type = "number" },
{ pattern = "0x%.%x+", type = "number" },
{ pattern = "0x%x+[pP][-+]?%d+", type = "number" },
{ pattern = "0x%x+", type = "number" },
{ pattern = "%d%.%d*[eE][-+]?%d+", type = "number" },
{ pattern = "%d%.%d*", type = "number" },
{ pattern = "%.?%d*[eE][-+]?%d+", type = "number" },
{ pattern = "%.?%d+", type = "number" },
{ 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" },
{ pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = { "%[%[", "%]%]" }, type = "string" },
{ pattern = { "%-%-%[%[", "%]%]"}, type = "comment" },
{ pattern = "%-%-.-\n", type = "comment" },
{ pattern = "-?0x%x+", type = "number" },
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
{ pattern = "-?%.?%d+", type = "number" },
{ pattern = "<%a+>", type = "keyword2" },
{ pattern = "%.%.%.?", type = "operator" },
{ pattern = "[<>~=]=", type = "operator" },
{ pattern = "[%+%-=/%*%^%%#<>]", type = "operator" },
{ pattern = "[%a_][%w_]*%s*%f[(\"{]", type = "function" },
{ pattern = "[%a_][%w_]*", type = "symbol" },
{ pattern = "::[%a_][%w_]*::", type = "function" },
},
symbols = {
["if"] = "keyword",

View File

@ -1,56 +1,21 @@
-- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax"
syntax.add {
name = "Markdown",
files = { "%.md$", "%.markdown$" },
patterns = {
{ pattern = "\\.", type = "normal" },
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" },
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
{ pattern = { "```perl", "```" }, type = "string", syntax = ".pl" },
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
{ pattern = { "```javascript", "```" }, type = "string", syntax = ".js" },
{ pattern = { "```html", "```" }, type = "string", syntax = ".html" },
{ pattern = { "```xml", "```" }, type = "string", syntax = ".xml" },
{ pattern = { "```css", "```" }, type = "string", syntax = ".css" },
{ pattern = { "```lua", "```" }, type = "string", syntax = ".lua" },
{ pattern = { "```bash", "```" }, type = "string", syntax = ".sh" },
{ pattern = { "```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" },
{ pattern = "\\.", type = "normal" },
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
{ 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 = { },
}

View File

@ -1,22 +1,20 @@
-- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax"
syntax.add {
name = "Python",
files = { "%.py$", "%.pyw$", "%.rpy$" },
files = { "%.py$", "%.pyw$" },
headers = "^#!.*[ /]python",
comment = "#",
patterns = {
{ pattern = { "#", "\n" }, type = "comment" },
{ pattern = { '[ruU]?"""', '"""'; '\\' }, type = "string" },
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
{ pattern = "0x[%da-fA-F]+", type = "number" },
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
{ pattern = "-?%.?%d+", type = "number" },
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
{ pattern = "[%a_][%w_]*", type = "symbol" },
{ pattern = { "#", "\n" }, type = "comment" },
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
{ pattern = { '"""', '"""' }, type = "string" },
{ pattern = "0x[%da-fA-F]+", type = "number" },
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
{ pattern = "-?%.?%d+", type = "number" },
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
{ pattern = "[%a_][%w_]*", type = "symbol" },
},
symbols = {
["class"] = "keyword",

View File

@ -1,9 +1,7 @@
-- mod-version:2 -- lite-xl 2.0
local syntax = require "core.syntax"
syntax.add {
name = "XML",
files = { "%.xml$" },
files = { "%.xml$", "%.html?$" },
headers = "<%?xml",
patterns = {
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },

View File

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

View File

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

View File

@ -1,4 +1,3 @@
-- mod-version:2 -- lite-xl 2.0
local core = require "core"
local common = require "core.common"
local keymap = require "core.keymap"
@ -9,7 +8,6 @@ local View = require "core.view"
local ResultsView = View:extend()
ResultsView.context = "session"
function ResultsView:new(text, fn)
ResultsView.super.new(self)
@ -31,10 +29,7 @@ local function find_all_matches_in_file(t, filename, fn)
for line in fp:lines() do
local s = fn(line)
if s then
-- Insert maximum 256 characters. If we insert more, for compiled files, which can have very long lines
-- 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 })
table.insert(t, { file = filename, text = line, line = n, col = s })
core.redraw = true
end
if n % 100 == 0 then coroutine.yield() end
@ -92,7 +87,7 @@ end
function ResultsView:on_mouse_pressed(...)
local caught = ResultsView.super.on_mouse_pressed(self, ...)
if not caught then
return self:open_selected_result()
self:open_selected_result()
end
end
@ -108,7 +103,6 @@ function ResultsView:open_selected_result()
dv.doc:set_selection(res.line, res.col)
dv:scroll_to_line(res.line, false, true)
end)
return true
end
@ -172,17 +166,12 @@ function ResultsView:draw()
local ox, oy = self:get_content_offset()
local x, y = ox + style.padding.x, oy + style.padding.y
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
if self.searching then
if files_number then
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
per * 100, self.last_file_idx, files_number,
#self.results, self.query)
else
text = string.format("Searching (%d files, %d matches) for %q...",
self.last_file_idx, #self.results, self.query)
end
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
per * 100, self.last_file_idx, files_number,
#self.results, self.query)
else
text = string.format("Found %d matches for %q",
#self.results, self.query)
@ -239,12 +228,9 @@ command.add(nil, {
end)
end,
["project-search:find-regex"] = function()
core.command_view:enter("Find Regex In Project", function(text)
local re = regex.compile(text, "i")
begin_search(text, function(line_text)
return regex.cmatch(re, line_text)
end)
["project-search:find-pattern"] = function()
core.command_view:enter("Find Pattern In Project", function(text)
begin_search(text, function(line_text) return line_text:find(text) end)
end)
end,
@ -278,38 +264,12 @@ command.add(ResultsView, {
["project-search:refresh"] = function()
core.active_view:refresh()
end,
["project-search:move-to-previous-page"] = function()
local view = core.active_view
view.scroll.to.y = view.scroll.to.y - view.size.y
end,
["project-search:move-to-next-page"] = function()
local view = core.active_view
view.scroll.to.y = view.scroll.to.y + view.size.y
end,
["project-search:move-to-start-of-doc"] = function()
local view = core.active_view
view.scroll.to.y = 0
end,
["project-search:move-to-end-of-doc"] = function()
local view = core.active_view
view.scroll.to.y = view:get_scrollable_size()
end
})
keymap.add {
["f5"] = "project-search:refresh",
["ctrl+shift+f"] = "project-search:find",
["up"] = "project-search:select-previous",
["down"] = "project-search:select-next",
["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"
["f5"] = "project-search:refresh",
["ctrl+shift+f"] = "project-search:find",
["up"] = "project-search:select-previous",
["down"] = "project-search:select-next",
["return"] = "project-search:open-selected",
}

View File

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

View File

@ -1,4 +1,3 @@
-- mod-version:2 -- lite-xl 2.0
local core = require "core"
local config = require "core.config"
local command = require "core.command"
@ -59,6 +58,6 @@ command.add("core.docview", {
})
keymap.add_direct {
keymap.add {
["ctrl+shift+q"] = "reflow:reflow"
}

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
-- mod-version:2 -- lite-xl 2.0
local core = require "core"
local common = require "core.common"
local command = require "core.command"
@ -6,17 +5,8 @@ local config = require "core.config"
local keymap = require "core.keymap"
local style = require "core.style"
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
local function get_depth(filename)
local n = 1
@ -26,11 +16,6 @@ local function get_depth(filename)
return n
end
local function replace_alpha(color, alpha)
local r, g, b = table.unpack(color)
return { r, g, b, alpha }
end
local TreeView = View:extend()
@ -41,18 +26,7 @@ function TreeView:new()
self.init_size = true
self.target_size = default_treeview_size
self.cache = {}
self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 }
self.item_icon_width = 0
self.item_text_spacing = 0
local on_dirmonitor_modify = core.on_dirmonitor_modify
function core.on_dirmonitor_modify(dir, filepath)
if self.cache[dir.name] then
self.cache[dir.name][filepath] = nil
end
on_dirmonitor_modify(dir, filepath)
end
self.last = {}
end
@ -64,7 +38,7 @@ function TreeView:set_target_size(axis, value)
end
function TreeView:get_cached(dir, item, dirname)
function TreeView:get_cached(item, dirname)
local dir_cache = self.cache[dirname]
if not dir_cache then
dir_cache = {}
@ -90,7 +64,6 @@ function TreeView:get_cached(dir, item, dirname)
end
t.name = basename
t.type = item.type
t.dir = dir -- points to top level "dir" item
dir_cache[cache_name] = t
end
return t
@ -107,21 +80,21 @@ function TreeView:get_item_height()
end
function TreeView:invalidate_cache(dirname)
for _, v in pairs(self.cache[dirname]) do
v.skip = nil
end
end
function TreeView:check_cache()
-- invalidate cache's skip values if project_files has changed
for i = 1, #core.project_directories do
local dir = core.project_directories[i]
-- invalidate cache's skip values if directory is declared dirty
if dir.is_dirty and self.cache[dir.name] then
self:invalidate_cache(dir.name)
local last_files = self.last[dir.name]
if not last_files then
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
dir.is_dirty = false
end
end
@ -137,14 +110,14 @@ function TreeView:each_item()
for k = 1, #core.project_directories do
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)
count_lines = count_lines + 1
y = y + h
local i = 1
while i <= #dir.files and dir_cached.expanded do
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)
count_lines = count_lines + 1
@ -170,36 +143,16 @@ function TreeView:each_item()
end
function TreeView:get_text_bounding_box(item, x, y, w, h)
local icon_width = style.icon_font:get_width("D")
local xoffset = item.depth * style.padding.x + style.padding.x + icon_width
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
self.hovered_item = nil
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
item_changed = true
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
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
@ -212,38 +165,30 @@ local function create_directory_in(item)
core.error("cannot create directory %q: %s", dirname, err)
end
item.expanded = true
core.reschedule_project_scan()
end)
end
function TreeView:on_mouse_pressed(button, x, y, clicks)
local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks)
if caught or button ~= "left" then
return true
if caught then
return
end
local hovered_item = self.hovered_item
if not hovered_item then
return false
elseif hovered_item.type == "dir" then
if not self.hovered_item then
return
elseif self.hovered_item.type == "dir" then
if keymap.modkeys["ctrl"] and button == "left" then
create_directory_in(hovered_item)
create_directory_in(self.hovered_item)
else
hovered_item.expanded = not hovered_item.expanded
if hovered_item.dir.files_limit then
core.update_project_subdir(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
core.project_subdir_set_show(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
end
self.hovered_item.expanded = not self.hovered_item.expanded
end
else
core.try(function()
if core.last_active_view and core.active_view == self then
core.set_active_view(core.last_active_view)
end
local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename)
local doc_filename = common.relative_path(core.project_dir, self.hovered_item.abs_filename)
core.root_view:open_doc(core.open_doc(doc_filename))
end)
end
return true
end
@ -257,16 +202,6 @@ function TreeView:update()
self:move_towards(self.size, "x", dest)
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)
end
@ -276,115 +211,50 @@ function TreeView:get_scrollable_size()
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()
self:draw_background(style.background2)
local _y, _h = self.position.y, self.size.y
local icon_width = style.icon_font:get_width("D")
local spacing = style.icon_font:get_width("f") / 2
local doc = core.active_view.doc
local active_filename = doc and system.absolute_path(doc.filename or "")
for item, x,y,w,h in self:each_item() do
if y + h >= _y and y < _y + _h then
self:draw_item(item,
item.abs_filename == active_filename,
item == self.hovered_item,
x, y, w, h)
local color = style.text
-- highlight active_view doc
if item.abs_filename == active_filename then
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
self:draw_scrollbar()
if self.hovered_item and self.tooltip.alpha > 0 then
core.root_view:defer_draw(self.draw_tooltip, self)
end
end
@ -399,10 +269,9 @@ local treeview_node = node:split("left", view, {x = true}, true)
-- 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()
if config.toolbarview ~= false and toolbar_plugin then
local 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))
@ -413,181 +282,12 @@ if config.plugins.toolbarview ~= false and toolbar_plugin then
})
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
-- register commands and keymap
command.add(nil, {
["treeview:toggle"] = function()
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,
})
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

View File

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

View File

@ -1,8 +1,6 @@
-- 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)
@ -12,7 +10,7 @@ local function workspace_files_for(project_dir)
if not info_wsdir then
local ok, err = system.mkdir(workspace_dir)
if not ok then
error("cannot create workspace directory: \"" .. err .. "\"")
error("cannot create workspace directory: %s", err)
end
end
return coroutine.wrap(function()
@ -30,7 +28,7 @@ local function workspace_files_for(project_dir)
end
local function consume_workspace_file(project_dir)
local function load_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()
@ -86,7 +84,6 @@ local function save_view(view)
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 {
@ -101,26 +98,16 @@ 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
local ok, doc = pcall(core.open_doc, t.filename)
if not ok then
return DocView(core.open_doc())
end
local dv = DocView(doc)
if t.text then doc:insert(1, 1, t.text) end
doc:set_selection(table.unpack(t.selection))
dv.last_line, dv.last_col = 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
return dv
end
return require(t.module)()
@ -153,19 +140,13 @@ 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
for _, 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
if v.active then res = view end
node:add_view(view)
end
if active_view then
node:set_active_view(active_view)
if t.active_view then
node:set_active_view(node.views[t.active_view])
end
return res
else
@ -202,7 +183,7 @@ end
local function load_workspace()
local workspace = consume_workspace_file(core.project_dir)
local workspace = load_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)

50
data/user/init.lua Normal file
View File

@ -0,0 +1,50 @@
-- 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"
------------------------------ Themes ----------------------------------------
-- light theme:
-- core.reload_module("colors.summer")
--------------------------- Key bindings -------------------------------------
-- key binding:
-- keymap.add { ["ctrl+escape"] = "core:quit" }
------------------------------- Fonts ----------------------------------------
-- customize fonts:
-- style.font = renderer.font.load(DATADIR .. "/fonts/font.ttf", 13 * SCALE)
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/monospace.ttf", 12 * SCALE)
--
-- font names used by lite:
-- style.font : user interface
-- style.big_font : big text in welcome screen
-- style.icon_font : icons
-- style.icon_big_font : toolbar 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
------------------------------ Plugins ----------------------------------------
-- enable or disable plugin loading setting config entries:
-- enable trimwhitespace, otherwise it is disable by default:
-- config.trimwhitespace = true
--
-- disable detectindent, otherwise it is enabled by default
-- config.detectindent = false

View File

@ -1,5 +1,5 @@
{
"name": "icons",
"name": "",
"css_prefix_text": "icon-",
"css_use_suffix": false,
"hinting": true,
@ -83,48 +83,6 @@
"css": "cancel-1",
"code": 67,
"src": "fontawesome"
},
{
"uid": "04f022b8bd044d4ccfffd3887ff72088",
"css": "window-minimize",
"code": 95,
"src": "fontawesome"
},
{
"uid": "d0e62145dbf40f30e47b3819b8b43a8f",
"css": "window-restore",
"code": 119,
"src": "fontawesome"
},
{
"uid": "7394501fc0b17cb7bda99538f92e26d6",
"css": "window-close",
"code": 88,
"src": "fontawesome"
},
{
"uid": "559647a6f430b3aeadbecd67194451dd",
"css": "menu-1",
"code": 77,
"src": "fontawesome"
},
{
"uid": "07f0832c07f3d9713fffb06c8bffa027",
"css": "window-maximize",
"code": 87,
"src": "fontawesome"
},
{
"uid": "d870630ff8f81e6de3958ecaeac532f2",
"css": "left-open",
"code": 60,
"src": "fontawesome"
},
{
"uid": "399ef63b1e23ab1b761dfbb5591fa4da",
"css": "right-open",
"code": 62,
"src": "fontawesome"
}
]
}
}

View File

@ -0,0 +1,448 @@
{
"name": "",
"css_prefix_text": "icon-",
"css_use_suffix": false,
"hinting": true,
"units_per_em": 1000,
"ascent": 850,
"glyphs": [
{
"uid": "9dd9e835aebe1060ba7190ad2b2ed951",
"css": "search-1",
"code": 76,
"src": "fontawesome"
},
{
"uid": "12f4ece88e46abd864e40b35e05b11cd",
"css": "ok-1",
"code": 59402,
"src": "fontawesome"
},
{
"uid": "43ab845088317bd348dee1d975700c48",
"css": "ok-circled-1",
"code": 59403,
"src": "fontawesome"
},
{
"uid": "ad33e708f4d2e25c5056c931da1528d6",
"css": "ok-circled2",
"code": 59405,
"src": "fontawesome"
},
{
"uid": "1400d5103edd2fa6d2d61688fee79a5a",
"css": "ok-squared",
"code": 61770,
"src": "fontawesome"
},
{
"uid": "5211af474d3a9848f67f945e2ccaf143",
"css": "cancel-1",
"code": 67,
"src": "fontawesome"
},
{
"uid": "0f4cae16f34ae243a6144c18a003f2d8",
"css": "cancel-circled-1",
"code": 99,
"src": "fontawesome"
},
{
"uid": "d7271d490b71df4311e32cdacae8b331",
"css": "home-1",
"code": 59407,
"src": "fontawesome"
},
{
"uid": "3d4ea8a78dc34efe891f3a0f3d961274",
"css": "info",
"code": 61737,
"src": "fontawesome"
},
{
"uid": "ce3cf091d6ebd004dd0b52d24074e6e3",
"css": "help",
"code": 61736,
"src": "fontawesome"
},
{
"uid": "17ebadd1e3f274ff0205601eef7b9cc4",
"css": "help-circled-1",
"code": 59408,
"src": "fontawesome"
},
{
"uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
"css": "info-circled-1",
"code": 59409,
"src": "fontawesome"
},
{
"uid": "c1f1975c885aa9f3dad7810c53b82074",
"css": "lock",
"code": 59410,
"src": "fontawesome"
},
{
"uid": "657ab647f6248a6b57a5b893beaf35a9",
"css": "lock-open-1",
"code": 59411,
"src": "fontawesome"
},
{
"uid": "05376be04a27d5a46e855a233d6e8508",
"css": "lock-open-alt-1",
"code": 61758,
"src": "fontawesome"
},
{
"uid": "3db5347bd219f3bce6025780f5d9ef45",
"css": "tag",
"code": 59412,
"src": "fontawesome"
},
{
"uid": "a3f89e106175a5c5c4e9738870b12e55",
"css": "tags",
"code": 59413,
"src": "fontawesome"
},
{
"uid": "7034e4d22866af82bef811f52fb1ba46",
"css": "code",
"code": 61729,
"src": "fontawesome"
},
{
"uid": "d35a1d35efeb784d1dc9ac18b9b6c2b6",
"css": "pencil-1",
"code": 59414,
"src": "fontawesome"
},
{
"uid": "44fae3bfdd54754dc68ec50d37efea37",
"css": "pencil-squared",
"code": 61771,
"src": "fontawesome"
},
{
"uid": "41087bc74d4b20b55059c60a33bf4008",
"css": "edit",
"code": 59415,
"src": "fontawesome"
},
{
"uid": "ecb97add13804c190456025e43ec003b",
"css": "keyboard",
"code": 61724,
"src": "fontawesome"
},
{
"uid": "c76b7947c957c9b78b11741173c8349b",
"css": "attention-1",
"code": 33,
"src": "fontawesome"
},
{
"uid": "00391fac5d419345ffcccd95b6f76263",
"css": "attention-alt-1",
"code": 61738,
"src": "fontawesome"
},
{
"uid": "b035c28eba2b35c6ffe92aee8b0df507",
"css": "attention-circled",
"code": 59417,
"src": "fontawesome"
},
{
"uid": "f48ae54adfb27d8ada53d0fd9e34ee10",
"css": "trash-empty",
"code": 59418,
"src": "fontawesome"
},
{
"uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
"css": "doc-1",
"code": 102,
"src": "fontawesome"
},
{
"uid": "c8585e1e5b0467f28b70bce765d5840c",
"css": "docs",
"code": 61637,
"src": "fontawesome"
},
{
"uid": "5408be43f7c42bccee419c6be53fdef5",
"css": "doc-text",
"code": 61686,
"src": "fontawesome"
},
{
"uid": "178053298e3e5b03551d754d4b9acd8b",
"css": "doc-inv",
"code": 61787,
"src": "fontawesome"
},
{
"uid": "c08a1cde48d96cba21d8c05fa7d7feb1",
"css": "doc-text-inv",
"code": 61788,
"src": "fontawesome"
},
{
"uid": "9daa1fdf0838118518a7e22715e83abc",
"css": "file-pdf",
"code": 61889,
"src": "fontawesome"
},
{
"uid": "310ffd629da85142bc8669f010556f2d",
"css": "file-word",
"code": 61890,
"src": "fontawesome"
},
{
"uid": "edcd4022de8d8df266ef7c42d2658ca5",
"css": "file-powerpoint",
"code": 61892,
"src": "fontawesome"
},
{
"uid": "3c961c1a8d874815856fc6637dc5a13c",
"css": "file-image",
"code": 61893,
"src": "fontawesome"
},
{
"uid": "e80ae555c1413a4ec18b33fb348b4049",
"css": "file-archive",
"code": 61894,
"src": "fontawesome"
},
{
"uid": "81db033e704eb7c586a365559d7c0f36",
"css": "file-audio",
"code": 61895,
"src": "fontawesome"
},
{
"uid": "dd69d9aa589ea7bc0a82a3fe67039f4b",
"css": "file-video",
"code": 61896,
"src": "fontawesome"
},
{
"uid": "26613a2e6bc41593c54bead46f8c8ee3",
"css": "file-code",
"code": 61897,
"src": "fontawesome"
},
{
"uid": "f8aa663c489bcbd6e68ec8147dca841e",
"css": "folder-1",
"code": 100,
"src": "fontawesome"
},
{
"uid": "c95735c17a10af81448c7fed98a04546",
"css": "folder-open-1",
"code": 68,
"src": "fontawesome"
},
{
"uid": "b091a8bd0fdade174951f17d936f51e4",
"css": "folder-empty-1",
"code": 61716,
"src": "fontawesome"
},
{
"uid": "6533bdc16ab201eb3f3b27ce989cab33",
"css": "folder-open-empty-1",
"code": 61717,
"src": "fontawesome"
},
{
"uid": "559647a6f430b3aeadbecd67194451dd",
"css": "menu-1",
"code": 61641,
"src": "fontawesome"
},
{
"uid": "e99461abfef3923546da8d745372c995",
"css": "cog",
"code": 59422,
"src": "fontawesome"
},
{
"uid": "98687378abd1faf8f6af97c254eb6cd6",
"css": "cog-alt",
"code": 59423,
"src": "fontawesome"
},
{
"uid": "5bb103cd29de77e0e06a52638527b575",
"css": "wrench",
"code": 59424,
"src": "fontawesome"
},
{
"uid": "0b2b66e526028a6972d51a6f10281b4b",
"css": "zoom-in",
"code": 59425,
"src": "fontawesome"
},
{
"uid": "d25d10efa900f529ad1d275657cfd30e",
"css": "zoom-out",
"code": 59426,
"src": "fontawesome"
},
{
"uid": "f3f90c8c89795da30f7444634476ea4f",
"css": "angle-left",
"code": 61700,
"src": "fontawesome"
},
{
"uid": "7bf14281af5633a597f85b061ef1cfb9",
"css": "angle-right",
"code": 43,
"src": "fontawesome"
},
{
"uid": "5de9370846a26947e03f63142a3f1c07",
"css": "angle-up",
"code": 61701,
"src": "fontawesome"
},
{
"uid": "e4dde1992f787163e2e2b534b8c8067d",
"css": "angle-down",
"code": 45,
"src": "fontawesome"
},
{
"uid": "bbfb51903f40597f0b70fd75bc7b5cac",
"css": "trash",
"code": 61944,
"src": "fontawesome"
},
{
"uid": "ea2d9a8c51ca42b38ef0d2a07f16d9a7",
"css": "chart-line",
"code": 103,
"src": "fontawesome"
},
{
"uid": "f4445feb55521283572ee88bc304f928",
"css": "floppy",
"code": 83,
"src": "fontawesome"
},
{
"uid": "b429436ec5a518c78479d44ef18dbd60",
"css": "paste",
"code": 61674,
"src": "fontawesome"
},
{
"uid": "8772331a9fec983cdb5d72902a6f9e0e",
"css": "scissors",
"code": 59428,
"src": "fontawesome"
},
{
"uid": "9755f76110ae4d12ac5f9466c9152031",
"css": "book",
"code": 59429,
"src": "fontawesome"
},
{
"uid": "f9cbf7508cd04145ade2800169959eef",
"css": "font",
"code": 59430,
"src": "fontawesome"
},
{
"uid": "d3b3f17bc3eb7cd809a07bbd4d178bee",
"css": "resize-vertical",
"code": 59431,
"src": "fontawesome"
},
{
"uid": "3c73d058e4589b65a8d959c0fc8f153d",
"css": "resize-horizontal",
"code": 59432,
"src": "fontawesome"
},
{
"uid": "e594fc6e5870b4ab7e49f52571d52577",
"css": "resize-full",
"code": 59433,
"src": "fontawesome"
},
{
"uid": "5278ef7773e948d56c4d442c8c8c98cf",
"css": "lightbulb",
"code": 61675,
"src": "fontawesome"
},
{
"uid": "598a5f2bcf3521d1615de8e1881ccd17",
"css": "clock",
"code": 59434,
"src": "fontawesome"
},
{
"uid": "1c4068ed75209e21af36017df8871802",
"css": "down-big",
"code": 59435,
"src": "fontawesome"
},
{
"uid": "555ef8c86832e686fef85f7af2eb7cde",
"css": "left-big",
"code": 59436,
"src": "fontawesome"
},
{
"uid": "ad6b3fbb5324abe71a9c0b6609cbb9f1",
"css": "right-big",
"code": 59437,
"src": "fontawesome"
},
{
"uid": "95376bf082bfec6ce06ea1cda7bd7ead",
"css": "up-big",
"code": 59438,
"src": "fontawesome"
},
{
"uid": "107ce08c7231097c7447d8f4d059b55f",
"css": "ellipsis",
"code": 61761,
"src": "fontawesome"
},
{
"uid": "750058837a91edae64b03d60fc7e81a7",
"css": "ellipsis-vert",
"code": 61762,
"src": "fontawesome"
},
{
"uid": "8fb55fd696d9a0f58f3b27c1d8633750",
"css": "table",
"code": 61646,
"src": "fontawesome"
},
{
"uid": "53dd31a6cc6438192b2d7b09b1c1dd45",
"css": "columns",
"code": 61659,
"src": "fontawesome"
}
]
}

View File

@ -10,30 +10,25 @@ copy_directory_from_repo () {
fi
local dirname="$1"
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 () {
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 --retry 5 --retry-delay 3 --insecure -L "https://github.com/rxi/lite-colors/archive/master.zip" -o "$build/rxi-lite-colors.zip" || exit 1
mkdir -p "$build/third/data/colors" "$build/third/data/plugins"
unzip -qq "$build/lite-xl-colors.zip" -d "$build"
mv "$build/lite-xl-colors-master/colors" "$build/third/data"
rm -fr "$build/lite-xl-colors-master"
rm "$build/lite-xl-colors.zip"
unzip -qq "$build/rxi-lite-colors.zip" -d "$build"
mv "$build/lite-colors-master/colors" "$build/third/data"
rm -fr "$build/lite-colors-master"
rm "$build/rxi-lite-colors.zip"
}
lite_branch=master
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
@ -47,7 +42,7 @@ workdir=".repackage"
rm -fr "$workdir" && mkdir "$workdir" && pushd "$workdir"
fetch_packages_from_github () {
assets=($($wget -q -nv -O- https://api.github.com/repos/lite-xl/lite-xl/releases/latest | grep "browser_download_url" | cut -d '"' -f 4))
assets=($($wget -q -nv -O- https://api.github.com/repos/franko/lite-xl/releases/latest | grep "browser_download_url" | cut -d '"' -f 4))
for url in "${assets[@]}"; do
echo "getting: $url"
@ -78,8 +73,6 @@ for filename in $(ls -1 *.zip *.tar.*); do
fi
rm "$filename"
find lite-xl -name lite -exec chmod a+x '{}' \;
start_file=$(find lite-xl -name start.lua)
lite_version=$(cat "$start_file" | awk 'match($0, /^\s*VERSION\s*=\s*"(.+)"/, a) { print(a[1]) }')
xcoredir="$(find lite-xl -type d -name 'core')"
coredir="$(dirname $xcoredir)"
echo "coredir: $coredir"
@ -88,7 +81,6 @@ for filename in $(ls -1 *.zip *.tar.*); do
rm -fr "$coredir/$module_name"
(cd .. && copy_directory_from_repo --strip-components=1 "data/$module_name" "$workdir/$coredir")
done
sed -i "s/@PROJECT_VERSION@/$lite_version/g" "$start_file"
for module_name in plugins colors; do
cp -r "third/data/$module_name" "$coredir"
done

View File

@ -21,7 +21,7 @@ while [[ "$#" -gt 0 ]]; do
;;
-plugin=*)
# should be like -plugin=franko/lite-plugins/master/plugins/autowrap.lua
plugins+=("${1#-plugin=}")
plugins+=("$(github_raw_content "${1#-plugin=}")")
;;
-global)
option_global=on
@ -68,48 +68,32 @@ copy_lite_build () {
rm -fr "$rundir"
mkdir -p "$bindir" "$datadir"
if [ ! -z ${run_windows+x} ]; then
cp "$builddir/src/lite-xl.exe" "$bindir"
cp "$builddir/src/lite.exe" "$bindir"
else
cp "$builddir/src/lite-xl" "$bindir"
cp "$builddir/src/lite" "$bindir"
fi
mkdir -p "$datadir/core"
for module_name in core plugins colors fonts; do
cp -r "data/$module_name" "$datadir"
done
# The start.lua file is generated by meson in $builddir but
# there is already a start.lua file in data/core so the command below
# should be executed after we copy the data/core directory.
cp "$builddir/start.lua" "$datadir/core"
}
run_lite () {
if [ ! -z ${option_global+x} ]; then
echo "running \"lite-xl ${pargs[@]:1}\""
exec "$bindir/lite-xl" "${pargs[@]:1}"
echo "running \"lite ${pargs[@]:1}\""
exec "$bindir/lite" "${pargs[@]:1}"
else
echo "running \"lite-xl ${pargs[@]:1}\" with local HOME"
echo "running \"lite ${pargs[@]:1}\" with local HOME"
if [ ! -z ${run_windows+x} ]; then
USERPROFILE="$userdir" exec "$bindir/lite-xl" "${pargs[@]:1}"
USERPROFILE="$userdir" exec "$bindir/lite" "${pargs[@]:1}"
else
HOME="$userdir" exec "$bindir/lite-xl" "${pargs[@]:1}"
HOME="$userdir" exec "$bindir/lite" "${pargs[@]:1}"
fi
fi
}
github_raw_content () {
echo "https://raw.githubusercontent.com/$1"
}
fetch_plugins () {
for name in "$@"; do
local url="$(github_raw_content "$name")"
local modname="${url##*/}"
if [ "$modname" == init.lua ]; then
local m1="${name#*/}"
modname="${m1%%/*}.lua"
fi
echo "installed $name as $modname from $url"
curl --insecure -L "$url" -o "$datadir/plugins/${modname}"
curl --insecure -L "$name" -o "$datadir/plugins/${name##*/}"
done
}

200
doc/usage.md Normal file
View File

@ -0,0 +1,200 @@
# 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 generally 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.
## Add directories to a project
In addition to the project directories it is possible to add other directories
using the command `core:add-directory`.
Once added a directory it will be shown in the tree-view on the left side and
the additional files will be reachable using the `ctrl+p` command (find file).
The additonal files will be also visible when searching across the project.
The additional directories can be removed using the command `core:remove-directory`.
When you will open again Lite XL on the same project folder the application will
remember your workspace including the additonal project directories.
Since version 1.15 Lite XL does not need a workspace plugin as it is now
bundled with the editor.
## Create new empty directory
Using the command `files:create-directory` or control-click in a directory in the
tree-view to create a new empty subdirectory.
## 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

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