From 433f5c0c5939b19b10e7fdc57bd3d3ad9b96950f Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Wed, 15 Nov 2023 15:47:21 +0200 Subject: [PATCH 01/31] Add install.sh --- install.sh | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 install.sh diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..77af874 --- /dev/null +++ b/install.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +ln -s /root/dev/toolbox/db.sh /var/www/sites/db +ln -s /root/dev/toolbox/clone.sh /var/www/sites/clone +ln -s /root/dev/toolbox/copy.sh /var/www/sites/copy +ln -s /root/dev/toolbox/delete.sh /var/www/sites/delete +ln -s /root/dev/toolbox/ddiff.sh /var/www/sites/ddiff +ln -s /root/dev/toolbox/dinfo.sh /var/www/sites/dinfo +ln -s /root/dev/toolbox/new.sh /var/www/sites/new From 06cd1b89734a320d131555120d3c9b641427f8ba Mon Sep 17 00:00:00 2001 From: root Date: Wed, 15 Nov 2023 15:48:18 +0200 Subject: [PATCH 02/31] rename diff --- toolbox/{diff.sh => ddiff.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename toolbox/{diff.sh => ddiff.sh} (100%) diff --git a/toolbox/diff.sh b/toolbox/ddiff.sh similarity index 100% rename from toolbox/diff.sh rename to toolbox/ddiff.sh From e4ca0e648b361b1cf2a5483e5f101f0180a0e804 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 15 Nov 2023 15:48:40 +0200 Subject: [PATCH 03/31] rename diff --- toolbox/{info.sh => dinfo.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename toolbox/{info.sh => dinfo.sh} (100%) diff --git a/toolbox/info.sh b/toolbox/dinfo.sh similarity index 100% rename from toolbox/info.sh rename to toolbox/dinfo.sh From 26da6599b3e8c75d1394bb9f4b167e6bec25e859 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 15 Nov 2023 15:49:12 +0200 Subject: [PATCH 04/31] rename diff --- install.sh => toolbox/install.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename install.sh => toolbox/install.sh (100%) diff --git a/install.sh b/toolbox/install.sh similarity index 100% rename from install.sh rename to toolbox/install.sh From dca393d6312d219504fdce42ff54d7184eb41af8 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 15 Nov 2023 15:57:06 +0200 Subject: [PATCH 05/31] sites added --- toolbox/{delete => delete.sh} | 0 toolbox/install.sh | 10 ++++++++++ 2 files changed, 10 insertions(+) rename toolbox/{delete => delete.sh} (100%) mode change 100644 => 100755 toolbox/install.sh diff --git a/toolbox/delete b/toolbox/delete.sh similarity index 100% rename from toolbox/delete rename to toolbox/delete.sh diff --git a/toolbox/install.sh b/toolbox/install.sh old mode 100644 new mode 100755 index 77af874..ff53075 --- a/toolbox/install.sh +++ b/toolbox/install.sh @@ -7,3 +7,13 @@ ln -s /root/dev/toolbox/delete.sh /var/www/sites/delete ln -s /root/dev/toolbox/ddiff.sh /var/www/sites/ddiff ln -s /root/dev/toolbox/dinfo.sh /var/www/sites/dinfo ln -s /root/dev/toolbox/new.sh /var/www/sites/new +ln -s /root/dev/toolbox/site0.sh /var/www/sites/site0 +ln -s /root/dev/toolbox/site1.sh /var/www/sites/site1 +ln -s /root/dev/toolbox/site2.sh /var/www/sites/site2 +ln -s /root/dev/toolbox/site3.sh /var/www/sites/site3 +ln -s /root/dev/toolbox/site4.sh /var/www/sites/site4 +ln -s /root/dev/toolbox/site5.sh /var/www/sites/site5 +ln -s /root/dev/toolbox/site6.sh /var/www/sites/site6 +ln -s /root/dev/toolbox/site7.sh /var/www/sites/site7 +ln -s /root/dev/toolbox/site8.sh /var/www/sites/site8 +ln -s /root/dev/toolbox/site9.sh /var/www/sites/site9 From 56ce80b730d791681983f650c552c55218fffa9f Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Thu, 16 Nov 2023 09:23:26 +0200 Subject: [PATCH 06/31] Update lamp/lamp.md --- lamp/lamp.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lamp/lamp.md b/lamp/lamp.md index 154ecbc..c1c510b 100644 --- a/lamp/lamp.md +++ b/lamp/lamp.md @@ -66,6 +66,17 @@ php -v curl localhost/info.php ``` +`/etc/php/ini` + +``` +upload_max_filesize = 48M +post_max_size = 48M +memory_limit = 512M +max_execution_time = 300 +max_input_time = 3000 +max_input_vars = 3000 +``` + ### Composer ```bash From 391a744385414253f1e301024f1a72f712922888 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Thu, 16 Nov 2023 11:25:33 +0200 Subject: [PATCH 07/31] Add vim/xdebug.md --- vim/xdebug.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 vim/xdebug.md diff --git a/vim/xdebug.md b/vim/xdebug.md new file mode 100644 index 0000000..1af8221 --- /dev/null +++ b/vim/xdebug.md @@ -0,0 +1,27 @@ +# XDebug + +### Install + +```sh +dnf install php82-php-pecl-xdebug3 +``` + +### Config + +`/etc/php.d/90-xdebug.ini` + +```ini +[XDebug] +zend_extension=/opt/remi/php82/root/usr/lib64/php/modules/xdebug.so +xdebug.remote_enable=1 +xdebug.remote_autostart=1 +xdebug.mode=develop,gcstats,coverage,profile,debug +xdebug.client_host=localhost +xdebug.client_port=9003 +xdebug.idekey="NVIM" +xdebug.log=/tmp/xdebug.log +xdebug.start_with_request=yes +;xdebug.start_with_request=trigger +``` + + From 0b2d3487fbda93ad6d50664179b15a56d2c1b320 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Thu, 16 Nov 2023 11:27:02 +0200 Subject: [PATCH 08/31] Update vim/xdebug.md --- vim/xdebug.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vim/xdebug.md b/vim/xdebug.md index 1af8221..a0ec5db 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -25,3 +25,8 @@ xdebug.start_with_request=yes ``` +### Test + +```sh +php --info +``` From 0bfb593f92661f5ee758db354c46936e4b9953fe Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Thu, 16 Nov 2023 11:33:55 +0200 Subject: [PATCH 09/31] Update vim/vim.md --- vim/vim.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vim/vim.md b/vim/vim.md index 575bc00..be8d5b9 100644 --- a/vim/vim.md +++ b/vim/vim.md @@ -53,8 +53,8 @@ nvim ```sh mkdir ~/.config/nvim/lua/user && cd $_ -wget https://git.vaidis.eu/stevaidis/Dev-Enviroment/raw/branch/main/vim/init.lua -wget https://git.vaidis.eu/stevaidis/Dev-Enviroment/raw/branch/main/vim/polish.lua +wget https://git.vaidis.eu/stevaidis/dev/raw/branch/main/vim/init.lua +wget https://git.vaidis.eu/stevaidis/dev/raw/branch/main/vim/polish.lua nvim ``` From 7e0416d01683f16ed470b827e58090cdc0a6e1f5 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Thu, 16 Nov 2023 11:48:16 +0200 Subject: [PATCH 10/31] Update vim/vim.md --- vim/vim.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vim/vim.md b/vim/vim.md index be8d5b9..67c8355 100644 --- a/vim/vim.md +++ b/vim/vim.md @@ -66,7 +66,7 @@ nvim ```ini [XDebug] -zend_extension=/opt/remi/php74/root/usr/lib64/php/modules/xdebug.so +zend_extension=/opt/remi/php82/root/usr/lib64/php/modules/xdebug.so xdebug.remote_enable=1 xdebug.remote_autostart=1 xdebug.mode=debug,develop From 02c38cfa1ef7bd6a5a634fd6c26633efbc8b1bcd Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Thu, 16 Nov 2023 12:09:12 +0200 Subject: [PATCH 11/31] Update vim/vim.md --- vim/vim.md | 1 - 1 file changed, 1 deletion(-) diff --git a/vim/vim.md b/vim/vim.md index 67c8355..e0b25b4 100644 --- a/vim/vim.md +++ b/vim/vim.md @@ -14,7 +14,6 @@ (https://rockylinux.org) ```sh -dnf -y install ninja-build dnf -y install gcc gcc-c++ make cmake unzip gettext curl python3-pip ``` From c3b8a7d22c16fede485d6a780100dcd092110463 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Thu, 16 Nov 2023 13:57:38 +0200 Subject: [PATCH 12/31] Update vim/vim.md --- vim/vim.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/vim/vim.md b/vim/vim.md index e0b25b4..44ef3be 100644 --- a/vim/vim.md +++ b/vim/vim.md @@ -57,9 +57,15 @@ wget https://git.vaidis.eu/stevaidis/dev/raw/branch/main/vim/polish.lua nvim ``` -## Debuging +## XDebug -(https://xdebug.org) +### Install + +```sh +dnf install php82-php-pecl-xdebug3 +``` + +### Configure `/etc/php.ini` @@ -76,7 +82,7 @@ xdebug.log=/tmp/xdebug.log xdebug.start_with_request=trigger ``` -Debug adapter installation +### Adapter ```sh cd ~ @@ -96,6 +102,9 @@ Debug chrome plugin to trigger debugger send the cookie `XDEBUG_SESSION=XDEBUG_ECLIPSE; Path=/; Expires=Sat, 27 Jul 2024 08:17:10 GMT;` + + + ## LSP server (https://intelephense.com) From 68ca3059a455bb37478e9cd4e547cf361e28945c Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 09:25:39 +0200 Subject: [PATCH 13/31] Update vim/xdebug.md --- vim/xdebug.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/vim/xdebug.md b/vim/xdebug.md index a0ec5db..047acdf 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -20,13 +20,27 @@ xdebug.client_host=localhost xdebug.client_port=9003 xdebug.idekey="NVIM" xdebug.log=/tmp/xdebug.log + +; always initiate a debugging session xdebug.start_with_request=yes + +; activates if a "trigger" is present ;xdebug.start_with_request=trigger ``` +To signal the debugger to initiate connections, Xdebug will look whether the XDEBUG_SESSION environment variable is present. + +``` +export XDEBUG_SESSION=1 +``` ### Test ```sh php --info ``` + + +### debug + +- port 9003 must not been blocked From 9389d4aa391400975af02f13b078e17b30b0796a Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 09:41:40 +0200 Subject: [PATCH 14/31] Update vim/xdebug.md --- vim/xdebug.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vim/xdebug.md b/vim/xdebug.md index 047acdf..7885e3e 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -20,6 +20,7 @@ xdebug.client_host=localhost xdebug.client_port=9003 xdebug.idekey="NVIM" xdebug.log=/tmp/xdebug.log +xdebug.log_level = 7 ; always initiate a debugging session xdebug.start_with_request=yes @@ -27,6 +28,7 @@ xdebug.start_with_request=yes ; activates if a "trigger" is present ;xdebug.start_with_request=trigger ``` +### Trigger To signal the debugger to initiate connections, Xdebug will look whether the XDEBUG_SESSION environment variable is present. @@ -34,6 +36,12 @@ To signal the debugger to initiate connections, Xdebug will look whether the XDE export XDEBUG_SESSION=1 ``` + +```sh +tail -f /tmp/xdebug.log +``` + + ### Test ```sh From a9426397c1c69e23653656adaccd584f82f9db2f Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 09:45:31 +0200 Subject: [PATCH 15/31] Update vim/xdebug.md --- vim/xdebug.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vim/xdebug.md b/vim/xdebug.md index 7885e3e..f313bb4 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -28,6 +28,17 @@ xdebug.start_with_request=yes ; activates if a "trigger" is present ;xdebug.start_with_request=trigger ``` + +`php -v` + +``` +PHP 8.2.12 (cli) (built: Oct 24 2023 19:22:16) (NTS gcc x86_64) +Copyright (c) The PHP Group +Zend Engine v4.2.12, Copyright (c) Zend Technologies + with Zend OPcache v8.2.12, Copyright (c), by Zend Technologies + with Xdebug v3.2.2, Copyright (c) 2002-2023, by Derick Rethans +``` + ### Trigger To signal the debugger to initiate connections, Xdebug will look whether the XDEBUG_SESSION environment variable is present. @@ -36,6 +47,7 @@ To signal the debugger to initiate connections, Xdebug will look whether the XDE export XDEBUG_SESSION=1 ``` +Watch the logs ```sh tail -f /tmp/xdebug.log From 5d169b4bf01f6978a363d9024cd070c34d65cae4 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 09:47:04 +0200 Subject: [PATCH 16/31] Update vim/xdebug.md --- vim/xdebug.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vim/xdebug.md b/vim/xdebug.md index f313bb4..aa8f090 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -39,6 +39,11 @@ Zend Engine v4.2.12, Copyright (c) Zend Technologies with Xdebug v3.2.2, Copyright (c) 2002-2023, by Derick Rethans ``` +Show current settings + +```sh +php -r 'xdebug_info();' +``` ### Trigger To signal the debugger to initiate connections, Xdebug will look whether the XDEBUG_SESSION environment variable is present. From 5c6681bfa77b7a2a8ac19d03025a2095f3734216 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 10:25:08 +0200 Subject: [PATCH 17/31] Update vim/xdebug.md --- vim/xdebug.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vim/xdebug.md b/vim/xdebug.md index aa8f090..a55710e 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -1,5 +1,7 @@ # XDebug +- XDebug uses the DBGP protocol + ### Install ```sh From 2a8cfd16d873dde184a681c2453e605518bcb5eb Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 10:53:06 +0200 Subject: [PATCH 18/31] Update vim/xdebug.md --- vim/xdebug.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vim/xdebug.md b/vim/xdebug.md index a55710e..cb5b99d 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -48,6 +48,17 @@ php -r 'xdebug_info();' ``` ### Trigger + +Xdebug will initiate a debug session in the presence of the XDEBUG_SESSION HTTP cookie. + +You can pick any value for the cookie, unless xdebug.trigger_value is set. + +A typical header looks like: + +``` +Cookie: XDEBUG_SESSION=start +``` + To signal the debugger to initiate connections, Xdebug will look whether the XDEBUG_SESSION environment variable is present. ``` From 8de099735962e3603c244344ea7fea2260eb536b Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 10:55:05 +0200 Subject: [PATCH 19/31] Update vim/xdebug.md --- vim/xdebug.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vim/xdebug.md b/vim/xdebug.md index cb5b99d..03eb9a2 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -2,7 +2,7 @@ - XDebug uses the DBGP protocol -### Install +## Install ```sh dnf install php82-php-pecl-xdebug3 @@ -46,7 +46,7 @@ Show current settings ```sh php -r 'xdebug_info();' ``` -### Trigger +## Trigger Xdebug will initiate a debug session in the presence of the XDEBUG_SESSION HTTP cookie. From 4da1752782cd1eaa9dca712600341435aa3655ba Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 11:44:10 +0200 Subject: [PATCH 20/31] Update vim/xdebug.md --- vim/xdebug.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/vim/xdebug.md b/vim/xdebug.md index 03eb9a2..01f58d3 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -46,6 +46,26 @@ Show current settings ```sh php -r 'xdebug_info();' ``` + +## Adapter + +`packages/php-debug-adapter/extension/package.json` + +```json +"port": { + "type": "number", + "description": "Port on which to listen for Xdebug", + "default": 9003 +}, +"php.debug.ideKey": { + "type": "string", + "default": "vsc", + "description": "A unique key that allows the proxy to match requests to your editor. Only used when proxy configuration includes replacement.", + "scope": "machine-overridable" +} +``` + + ## Trigger From 8c71715433ea9bc669ff7d7fa3d7b85ac793c41e Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 11:45:24 +0200 Subject: [PATCH 21/31] Update vim/xdebug.md --- vim/xdebug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vim/xdebug.md b/vim/xdebug.md index 01f58d3..361ccdb 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -49,7 +49,7 @@ php -r 'xdebug_info();' ## Adapter -`packages/php-debug-adapter/extension/package.json` +`.local/share/nvim/mason/ packages/php-debug-adapter/extension/package.json` ```json "port": { From d69b3542d7ef4e8514198010bfa3ba988f0cc64f Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 11:45:37 +0200 Subject: [PATCH 22/31] Update vim/xdebug.md --- vim/xdebug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vim/xdebug.md b/vim/xdebug.md index 361ccdb..4c66fa1 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -49,7 +49,7 @@ php -r 'xdebug_info();' ## Adapter -`.local/share/nvim/mason/ packages/php-debug-adapter/extension/package.json` +`.local/share/nvim/mason/packages/php-debug-adapter/extension/package.json` ```json "port": { From 175b3bf6ac1229d818dfd717adddb33a95d9a486 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 11:47:46 +0200 Subject: [PATCH 23/31] Update vim/xdebug.md --- vim/xdebug.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/vim/xdebug.md b/vim/xdebug.md index 4c66fa1..5e47375 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -49,6 +49,23 @@ php -r 'xdebug_info();' ## Adapter +```sh +cd ~ +git clone https://github.com/xdebug/vscode-php-debug.git +cd vscode-php-debug +npm install && npm run build +``` + +Debug adapter nvim plugin + +`:MasonInstall php-debug-adapter` + +Debug chrome plugin + +(https://chrome.google.com/webstore/detail/xdebug-helper/eadndfjplgieldjbigjakmdgkmoaaaoc) + +to trigger debugger send the cookie `XDEBUG_SESSION=XDEBUG_ECLIPSE; Path=/; Expires=Sat, 27 Jul 2024 08:17:10 GMT;` + `.local/share/nvim/mason/packages/php-debug-adapter/extension/package.json` ```json From 8cbdc3fef3f3d12952a9df778a8f46e21bef6025 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 11:48:46 +0200 Subject: [PATCH 24/31] Update vim/vim.md --- vim/vim.md | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) diff --git a/vim/vim.md b/vim/vim.md index 44ef3be..9691d78 100644 --- a/vim/vim.md +++ b/vim/vim.md @@ -57,54 +57,6 @@ wget https://git.vaidis.eu/stevaidis/dev/raw/branch/main/vim/polish.lua nvim ``` -## XDebug - -### Install - -```sh -dnf install php82-php-pecl-xdebug3 -``` - -### Configure - -`/etc/php.ini` - -```ini -[XDebug] -zend_extension=/opt/remi/php82/root/usr/lib64/php/modules/xdebug.so -xdebug.remote_enable=1 -xdebug.remote_autostart=1 -xdebug.mode=debug,develop -xdebug.client_host=localhost -xdebug.client_port=9000 -xdebug.idekey="xdebug" -xdebug.log=/tmp/xdebug.log -xdebug.start_with_request=trigger -``` - -### Adapter - -```sh -cd ~ -git clone https://github.com/xdebug/vscode-php-debug.git -cd vscode-php-debug -npm install && npm run build -``` - -Debug adapter nvim plugin - -`:MasonInstall php-debug-adapter` - -Debug chrome plugin - -(https://chrome.google.com/webstore/detail/xdebug-helper/eadndfjplgieldjbigjakmdgkmoaaaoc) - -to trigger debugger send the cookie `XDEBUG_SESSION=XDEBUG_ECLIPSE; Path=/; Expires=Sat, 27 Jul 2024 08:17:10 GMT;` - - - - - ## LSP server (https://intelephense.com) From 641761915a7eb9e9488e07e8c75bcd08912e0827 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 11:49:14 +0200 Subject: [PATCH 25/31] Update vim/xdebug.md --- vim/xdebug.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/vim/xdebug.md b/vim/xdebug.md index 5e47375..ddb9086 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -8,8 +8,6 @@ dnf install php82-php-pecl-xdebug3 ``` -### Config - `/etc/php.d/90-xdebug.ini` ```ini From c366b8f4f2e40696aa2e0ffac908c96eea461657 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 11:49:43 +0200 Subject: [PATCH 26/31] Update vim/xdebug.md --- vim/xdebug.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/vim/xdebug.md b/vim/xdebug.md index ddb9086..c6425f0 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -2,8 +2,6 @@ - XDebug uses the DBGP protocol -## Install - ```sh dnf install php82-php-pecl-xdebug3 ``` From 1de1c642614bd98b7db9434c5021ae455722a0b5 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Fri, 17 Nov 2023 12:06:27 +0200 Subject: [PATCH 27/31] Update vim/xdebug.md --- vim/xdebug.md | 1 + 1 file changed, 1 insertion(+) diff --git a/vim/xdebug.md b/vim/xdebug.md index c6425f0..d045f91 100644 --- a/vim/xdebug.md +++ b/vim/xdebug.md @@ -111,6 +111,7 @@ tail -f /tmp/xdebug.log php --info ``` +`https://github.com/banago/simple-php-website` ### debug From a002064040117aeed0afdfd8873823abdcafeab6 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Sun, 31 Dec 2023 00:04:21 +0200 Subject: [PATCH 28/31] site files --- endpoints/bundle_AAAAA/bundle_AAAAA.info.yml | 8 + .../bundle_AAAAA/bundle_AAAAA.routing.yml | 39 +++ .../src/Controller/CategoryController.php | 65 +++++ .../src/Controller/NodeController.php | 163 ++++++++++++ .../src/Controller/NodesController.php | 238 ++++++++++++++++++ .../builders_test/builders_test.info.yml | 6 + .../builders_test/src/ArticleBuilder.php | 101 ++++++++ .../builders_test/src/ArticleTypeBuilder.php | 36 +++ .../modules/builders_test/src/FileBuilder.php | 34 +++ .../modules/builders_test/src/PageBuilder.php | 30 +++ .../modules/builders_test/src/UserBuilder.php | 30 +++ .../bundle_article_test.info.yml | 6 + ...node.article.field_article_description.yml | 20 ++ ...field.node.article.field_article_files.yml | 26 ++ ...ield.node.article.field_article_photos.yml | 37 +++ ....field.node.article.field_article_type.yml | 28 +++ ...ield.node.article.field_article_videos.yml | 26 ++ ...field.node.article.field_facebook_page.yml | 22 ++ .../field.field.node.article.field_source.yml | 22 ++ ...field.field.node.article.field_summary.yml | 20 ++ ...eld.field.node.article.field_thumbnail.yml | 37 +++ .../field.field.node.article.field_url.yml | 22 ++ .../field.field.user.user.field_user_name.yml | 19 ++ ...eld.field.user.user.field_user_surname.yml | 19 ++ ...storage.node.field_article_description.yml | 18 ++ ...field.storage.node.field_article_files.yml | 22 ++ ...ield.storage.node.field_article_photos.yml | 29 +++ .../field.storage.node.field_article_type.yml | 19 ++ ...ield.storage.node.field_article_videos.yml | 22 ++ ...field.storage.node.field_facebook_page.yml | 18 ++ .../field.storage.node.field_source.yml | 18 ++ .../field.storage.node.field_summary.yml | 18 ++ .../field.storage.node.field_thumbnail.yml | 29 +++ .../install/field.storage.node.field_url.yml | 18 ++ .../field.storage.user.field_user_name.yml | 20 ++ .../field.storage.user.field_user_surname.yml | 20 ++ .../config/install/node.type.article.yml | 9 + .../tests/src/Kernel/GetArticleTypesTest.php | 41 +++ .../tests/src/Kernel/GetArticlesTest.php | 134 ++++++++++ endpoints/endpoint-get-delete.sh | 18 ++ endpoints/endpoint-get.sh | 103 ++++++++ endpoints/endpoint-post-delete.sh | 18 ++ endpoints/endpoint-post.sh | 102 ++++++++ .../endpoint_get_AAAAA.info.yml | 8 + .../endpoint_get_AAAAA.routing.yml | 39 +++ .../src/Controller/CategoryController.php | 65 +++++ .../src/Controller/NodeController.php | 172 +++++++++++++ .../src/Controller/NodesController.php | 237 +++++++++++++++++ .../builders_test/builders_test.info.yml | 6 + .../builders_test/src/ArticleBuilder.php | 101 ++++++++ .../builders_test/src/ArticleTypeBuilder.php | 36 +++ .../modules/builders_test/src/FileBuilder.php | 34 +++ .../modules/builders_test/src/PageBuilder.php | 30 +++ .../modules/builders_test/src/UserBuilder.php | 30 +++ .../bundle_article_test.info.yml | 6 + ...node.article.field_article_description.yml | 20 ++ ...field.node.article.field_article_files.yml | 26 ++ ...ield.node.article.field_article_photos.yml | 37 +++ ....field.node.article.field_article_type.yml | 28 +++ ...ield.node.article.field_article_videos.yml | 26 ++ ...field.node.article.field_facebook_page.yml | 22 ++ .../field.field.node.article.field_source.yml | 22 ++ ...field.field.node.article.field_summary.yml | 20 ++ ...eld.field.node.article.field_thumbnail.yml | 37 +++ .../field.field.node.article.field_url.yml | 22 ++ .../field.field.user.user.field_user_name.yml | 19 ++ ...eld.field.user.user.field_user_surname.yml | 19 ++ ...storage.node.field_article_description.yml | 18 ++ ...field.storage.node.field_article_files.yml | 22 ++ ...ield.storage.node.field_article_photos.yml | 29 +++ .../field.storage.node.field_article_type.yml | 19 ++ ...ield.storage.node.field_article_videos.yml | 22 ++ ...field.storage.node.field_facebook_page.yml | 18 ++ .../field.storage.node.field_source.yml | 18 ++ .../field.storage.node.field_summary.yml | 18 ++ .../field.storage.node.field_thumbnail.yml | 29 +++ .../install/field.storage.node.field_url.yml | 18 ++ .../field.storage.user.field_user_name.yml | 20 ++ .../field.storage.user.field_user_surname.yml | 20 ++ .../config/install/node.type.article.yml | 9 + .../tests/src/Kernel/GetNodeTypesTest.php | 41 +++ .../tests/src/Kernel/GetNodesTest.php | 134 ++++++++++ .../endpoint_post_AAAAA/dotsoft.info.yml | 7 + .../endpoint_post_AAAAA/dotsoft.routing.yml | 7 + .../src/Controller/PostController.php | 54 ++++ ...field.user.user.field_export_directory.yml | 29 +++ ...ld.storage.user.field_export_directory.yml | 19 ++ .../dotsoft_config_user.info.yml | 7 + .../src/Kernel/PostMemberRegistrationTest.php | 80 ++++++ toolbox/copy.sh | 42 ++-- toolbox/delete.sh | 1 + toolbox/site.sh | 5 +- toolbox/site0.sh | 2 +- toolbox/site1.sh | 2 +- toolbox/site2.sh | 2 +- toolbox/site3.sh | 2 +- toolbox/site4.sh | 2 +- toolbox/site6.sh | 2 +- toolbox/site7.sh | 2 +- toolbox/site8.sh | 2 +- toolbox/site9.sh | 2 +- 101 files changed, 3464 insertions(+), 32 deletions(-) create mode 100644 endpoints/bundle_AAAAA/bundle_AAAAA.info.yml create mode 100644 endpoints/bundle_AAAAA/bundle_AAAAA.routing.yml create mode 100644 endpoints/bundle_AAAAA/src/Controller/CategoryController.php create mode 100644 endpoints/bundle_AAAAA/src/Controller/NodeController.php create mode 100644 endpoints/bundle_AAAAA/src/Controller/NodesController.php create mode 100644 endpoints/bundle_AAAAA/tests/modules/builders_test/builders_test.info.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/builders_test/src/ArticleBuilder.php create mode 100644 endpoints/bundle_AAAAA/tests/modules/builders_test/src/ArticleTypeBuilder.php create mode 100644 endpoints/bundle_AAAAA/tests/modules/builders_test/src/FileBuilder.php create mode 100644 endpoints/bundle_AAAAA/tests/modules/builders_test/src/PageBuilder.php create mode 100644 endpoints/bundle_AAAAA/tests/modules/builders_test/src/UserBuilder.php create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/bundle_article_test.info.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_description.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_files.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_photos.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_type.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_videos.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_facebook_page.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_source.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_summary.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_thumbnail.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_url.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_name.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_surname.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_description.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_files.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_photos.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_type.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_videos.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_facebook_page.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_source.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_summary.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_thumbnail.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_url.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_name.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_surname.yml create mode 100644 endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/node.type.article.yml create mode 100644 endpoints/bundle_AAAAA/tests/src/Kernel/GetArticleTypesTest.php create mode 100644 endpoints/bundle_AAAAA/tests/src/Kernel/GetArticlesTest.php create mode 100755 endpoints/endpoint-get-delete.sh create mode 100755 endpoints/endpoint-get.sh create mode 100755 endpoints/endpoint-post-delete.sh create mode 100755 endpoints/endpoint-post.sh create mode 100644 endpoints/endpoint_get_AAAAA/endpoint_get_AAAAA.info.yml create mode 100644 endpoints/endpoint_get_AAAAA/endpoint_get_AAAAA.routing.yml create mode 100644 endpoints/endpoint_get_AAAAA/src/Controller/CategoryController.php create mode 100644 endpoints/endpoint_get_AAAAA/src/Controller/NodeController.php create mode 100644 endpoints/endpoint_get_AAAAA/src/Controller/NodesController.php create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/builders_test/builders_test.info.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/ArticleBuilder.php create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/ArticleTypeBuilder.php create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/FileBuilder.php create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/PageBuilder.php create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/UserBuilder.php create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/bundle_article_test.info.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_description.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_files.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_photos.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_type.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_videos.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_facebook_page.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_source.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_summary.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_thumbnail.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_url.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_name.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_surname.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_description.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_files.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_photos.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_type.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_videos.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_facebook_page.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_source.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_summary.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_thumbnail.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_url.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_name.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_surname.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/node.type.article.yml create mode 100644 endpoints/endpoint_get_AAAAA/tests/src/Kernel/GetNodeTypesTest.php create mode 100644 endpoints/endpoint_get_AAAAA/tests/src/Kernel/GetNodesTest.php create mode 100644 endpoints/endpoint_post_AAAAA/dotsoft.info.yml create mode 100644 endpoints/endpoint_post_AAAAA/dotsoft.routing.yml create mode 100644 endpoints/endpoint_post_AAAAA/src/Controller/PostController.php create mode 100644 endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/config/install/field.field.user.user.field_export_directory.yml create mode 100644 endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/config/install/field.storage.user.field_export_directory.yml create mode 100644 endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/dotsoft_config_user.info.yml create mode 100644 endpoints/endpoint_post_AAAAA/tests/src/Kernel/PostMemberRegistrationTest.php diff --git a/endpoints/bundle_AAAAA/bundle_AAAAA.info.yml b/endpoints/bundle_AAAAA/bundle_AAAAA.info.yml new file mode 100644 index 0000000..6c6b0b4 --- /dev/null +++ b/endpoints/bundle_AAAAA/bundle_AAAAA.info.yml @@ -0,0 +1,8 @@ +name: 'AAAAA bundle' +description: 'AAAAA bundle' +package: DOTSOFT + +type: module +version: 1.0 + +core_version_requirement: ^10 diff --git a/endpoints/bundle_AAAAA/bundle_AAAAA.routing.yml b/endpoints/bundle_AAAAA/bundle_AAAAA.routing.yml new file mode 100644 index 0000000..dc3c137 --- /dev/null +++ b/endpoints/bundle_AAAAA/bundle_AAAAA.routing.yml @@ -0,0 +1,39 @@ +bundle_AAAAA.node: + path: '/api/{lang}/AAAAAs/{alias}' + defaults: + _controller: 'Drupal\bundle_AAAAA\Controller\NodeController::getNode' + methods: [GET] + requirements: + _access: 'TRUE' + options: + no_cache: 'TRUE' + +bundle_AAAAA.nodes: + path: '/api/{lang}/AAAAAs' + defaults: + _controller: 'Drupal\bundle_AAAAA\Controller\NodesController::getNodes' + methods: [GET] + requirements: + _access: 'TRUE' + options: + no_cache: 'TRUE' + +# bundle_AAAAA.categories: +# path: '/api/{lang}/AAAAAs/categories' +# defaults: +# _controller: 'Drupal\bundle_AAAAA\Controller\CategoryController::getCategories' +# methods: [GET] +# requirements: +# _access: 'TRUE' +# options: +# no_cache: 'TRUE' + +# bundle_AAAAA.nodes_category: +# path: '/api/{lang}/AAAAAs/categories/{category}' +# defaults: +# _controller: 'Drupal\bundle_AAAAA\Controller\NodesController::getNodes' +# methods: [GET] +# requirements: +# _access: 'TRUE' +# options: +# no_cache: 'TRUE' diff --git a/endpoints/bundle_AAAAA/src/Controller/CategoryController.php b/endpoints/bundle_AAAAA/src/Controller/CategoryController.php new file mode 100644 index 0000000..00b63f2 --- /dev/null +++ b/endpoints/bundle_AAAAA/src/Controller/CategoryController.php @@ -0,0 +1,65 @@ +response = [ + 'code' => $this->statusCode, + 'lang' => $lang, + 'categories' => $this->buildVocabulary($lang)]; + + return new JsonResponse($this->response, $this->statusCode); + } + + // RESPONSE ARRAY + + private function buildVocabulary($lang): array { + $terms = $this->loadTerms($lang); + $terms_response = []; + foreach ($terms as $type) { + $terms_response[] = [ + 'id' => $type->id(), + 'name' => $type->getName() + ]; + } + return $terms_response; + } + + // RESPONSE ARRAY VALUES + + private function loadTerms($lang) { + + // All terms from the vocabulary + $terms=\Drupal::entityTypeManager() + ->getStorage('taxonomy_term') + ->loadByProperties([ + 'vid' => self::VOCABULARY, + ]); + + // Current language only + $termList=[]; + foreach($terms as $term) { + if($term->hasTranslation($lang)){ + $tid = $term->id(); + $translated_term = \Drupal::service('entity.repository')->getTranslationFromContext($term, $lang); + $termList[$tid] = $translated_term; + } + } + return $termList; + } + +} diff --git a/endpoints/bundle_AAAAA/src/Controller/NodeController.php b/endpoints/bundle_AAAAA/src/Controller/NodeController.php new file mode 100644 index 0000000..2a97f9d --- /dev/null +++ b/endpoints/bundle_AAAAA/src/Controller/NodeController.php @@ -0,0 +1,163 @@ +errorAliasMissing(); + return new JsonResponse($this->response, $this->statusCode); + } + + // check if alias exist in drupal + $path_alias = '/'.$alias; + $path = \Drupal::service('path_alias.manager')->getPathByAlias($path_alias); + if ( $path_alias == $path) { + $this->errorNodeNotExist($alias, $path); + return new JsonResponse($this->response, $this->statusCode); + } + + // check if node has translation + $nid = Url::fromUri('internal:' . $path_alias)->getRouteParameters()['node']; + $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid); + if (! $node->hasTranslation($lang)) { + $this->errorTranslationNotExist($lang, $alias); + return new JsonResponse($this->response, $this->statusCode); + } + + // build response + $this->buildNodeResponse($lang, $alias); + return new JsonResponse($this->response, $this->statusCode); + } + + // Response + + private function buildNodeResponse($lang, $alias) { + $path = \Drupal::service('path_alias.manager')->getPathByAlias($alias); + $nid = Url::fromUri('internal:/' . $path)->getRouteParameters()['node']; + $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid)->getTranslation($lang); + + $uid = $node->getOwnerId(); + $user = \Drupal\user\Entity\User::load($uid); + $name = $user->getDisplayName(); + + $this->response = [ + 'code' => $this->statusCode, + 'alias' => $alias, + 'path' => $path, + 'nid' => $nid, + 'title' => $node->get('title')->value, + 'body' => $node->get('body')->value, + 'lang' => $node->get('langcode')->value, + 'alias' => $node->get('path')->alias, + 'created' => $node->get('created')->value, + 'author' => $name, + // 'files' => $this->getFiles($node, 'field_file'), + // 'images' => $this->getImages($node, 'field_image'), + // 'category' => $this->getTerms($node, self::FIELD_CATEGORY) + // // single value taxonomy field + // 'tag' => ( + // $node + // ->get('field_tags') + // ->entity)?\Drupal::service('entity.repository') + // ->getTranslationFromContext( + // $node->get('field_tags')->entity, + // $node->currentTranslation + // )->getName():'' + ]; + } + + + // Multiple vallue fields + + // private function getTerms(Node $node, string $field): array { + // $terms = $node->get($field)->referencedEntities(); + // $response = []; + // foreach ($terms as $term) { + // $name = $term->getName(); + // $tid = $term->id(); + // $response[] = array( + // 'name' => $name, + // 'id' => $tid + // ); + // } + // return $response; + // } + + // private function getFiles(Node $node, string $field): array { + // $uris = []; + // foreach ($node->get($field) as $value) { + // $file = \Drupal::entityTypeManager() + // ->getStorage('file') + // ->load($value->getValue()['target_id']); + // + // $url = \Drupal::service('file_url_generator') + // ->generateAbsoluteString($file->getFileUri()); + // + // $uris[] = array("url" => $url); + // } + // return $uris; + // } + + // private function getImages(Node $node, string $field): array { + // $uris = []; + // foreach ($node->get($field)->getValue() as $value) { + // $file = \Drupal::entityTypeManager() + // ->getStorage('file') + // ->load($value['target_id']); + // $url = \Drupal::service('file_url_generator') + // ->generateAbsoluteString($file->getFileUri()); + // $uris[] = array("url" => $url, "alt" => $value['alt']); + // } + // return $uris; + // } + + // Error functions + + private function errorNodeNotExist($alias, $path) { + $this->statusCode = self::HTTP_NOT_FOUND; + $this->response = [ + 'code' => $this->statusCode, + "message" => "Node with alias " . $alias . " does not exist." . $path + ]; + } + + private function errorTranslationNotExist($lang, $alias) { + $this->statusCode = self::HTTP_NOT_FOUND; + $this->response = [ + 'code' => $this->statusCode, + "message" => "Node with alias " . $alias . " does not have a translation for language code " . $lang + ]; + } + + private function errorAliasMissing() { + $this->statusCode = self::HTTP_BAD_REQUEST; + $this->response = [ + 'code' => $this->statusCode, + 'message' => 'Query parameter "alias" is mandatory and is missing.' + ]; + } + // \Drupal::logger('hello')->notice(''.$nid); +} diff --git a/endpoints/bundle_AAAAA/src/Controller/NodesController.php b/endpoints/bundle_AAAAA/src/Controller/NodesController.php new file mode 100644 index 0000000..b926bb1 --- /dev/null +++ b/endpoints/bundle_AAAAA/src/Controller/NodesController.php @@ -0,0 +1,238 @@ +loadNodes($request, $lang, $category); + $nodes_response = $this->buildNodes($nodes, $lang, $category); + + $this->response = [ + 'code' => $this->statusCode, + 'count' => count($nodes_response), + 'nodes' => $nodes_response + ]; + return new JsonResponse($this->response, $this->statusCode); + } + + // Response List + + private function buildNodes(array $nodes, $lang): array { + $nodes_response = []; + foreach ($nodes as $node) { + // don't show node if it hasn't translation + if ( $node->hasTranslation($lang)) { + $node = $node->getTranslation($lang); + $published = $node->get('status')->value; + + // get user name + $uid = $node->getOwnerId(); + $user = \Drupal\user\Entity\User::load($uid); + $name = $user->getDisplayName(); + + if ($published == 1) { + $nodes_response[] = [ + 'id' => $node->id(), + 'title' => $node->getTitle(), + 'body' => $node->get('body')->value, + 'lang' => $node->get('langcode')->value, + 'created' => $node->get('created')->value, + 'alias' => $node->get('path')->alias, + 'author' => $name, + // 'file' => $this->getFiles($node, 'field_file'), + // 'image' => $this->getImages($node, 'field_image'), + // 'category_id' => ( + // $node + // ->get(self::FIELD_CATEGORY) + // ->entity)?\Drupal::service('entity.repository') + // ->getTranslationFromContext( + // $node->get(self::FIELD_CATEGORY)->entity, + // $node->currentTranslation + // )->id():'', + // 'category_label' => ( + // $node + // ->get(self::FIELD_CATEGORY) + // ->entity)?\Drupal::service('entity.repository') + // ->getTranslationFromContext( + // $node->get(self::FIELD_CATEGORY)->entity, + // $node->currentTranslation + // )->getName():'', + // 'subcategory_id' => ( + // $node + // ->get(self::FIELD_SUBCATEGORY) + // ->entity)?\Drupal::service('entity.repository') + // ->getTranslationFromContext( + // $node->get(self::FIELD_SUBCATEGORY)->entity, + // $node->currentTranslation + // )->id():'', + // 'subcategory_label' => ( + // $node + // ->get(self::FIELD_SUBCATEGORY) + // ->entity)?\Drupal::service('entity.repository') + // ->getTranslationFromContext( + // $node->get(self::FIELD_SUBCATEGORY)->entity, + // $node->currentTranslation + // )->getName():'', + // 'terms' => $this->getTerms($node, 'field_tags') + ]; + } + } + } + return $nodes_response; + } + + // Multivalue fields + + private function getTerms(Node $node, string $field): array { + $terms = $node->get($field)->referencedEntities(); + $response = []; + foreach ($terms as $term) { + $name = $term->getName(); + $tid = $term->id(); + $response[] = array( + 'name' => $name, + 'id' => $tid + ); + } + return $response; + } + + private function getFiles(Node $node, string $field): array { + $uris = []; + foreach ($node->get($field) as $value) { + $file = \Drupal::entityTypeManager() + ->getStorage('file') + ->load($value->getValue()['target_id']); + $url = \Drupal::service('file_url_generator') + ->generateAbsoluteString($file->getFileUri()); + $uris[] = array("url" => $url); + } + return $uris; + } + + private function getImages(Node $node, string $field): array { + $uris = []; + foreach ($node->get($field)->getValue() as $value) { + $file = \Drupal::entityTypeManager() + ->getStorage('file') + ->load($value['target_id']); + $url = \Drupal::service('file_url_generator') + ->generateAbsoluteString($file->getFileUri()); + $uris[] = array("url" => $url, "alt" => $value['alt']); + } + return $uris; + } + + + // get response data + + private function loadNodes($request, $lang, $category) { + + // build query for nodes + $query = \Drupal::entityTypeManager() + ->getStorage('node') + ->getQuery() + ->accessCheck(false); + + // category is a URL parameter, matched with a taxonomy field + if ($category != 'all'){ + $query->condition(self::FIELD_CATEGORY, $category); + } + + // add subcategory URL query filter, matched with a taxonomy field + if ($request->query->get('subcategory')){ + $or_group = $query->orConditionGroup(); + $terms = explode(',', $request->query->get('subcategory')); + foreach ($terms as $term) { + $or_group->condition(self::FIELD_SUBCATEGORY.'.target_id', $term); + } + $query->condition($or_group); + } + // add pager + $query->range($request->get('start'), $request->get('length')); + + // sort results by DESC or ASC + if ($request->get('sort')) { + $sort = $request->get('sort'); + if(strcasecmp($sort, 'desc') == 0 || strcasecmp($sort, 'asc') == 0){ + $sort = $request->get('sort'); + } + else{ + $sort = 'DESC'; + } + } + else{ + $sort = 'DESC'; + } + + // sort results by field + if ($request->get('sortby')) { + $sortby = $request->get('sortby'); + if(strcasecmp($sortby, 'title') == 0 || strcasecmp($sortby, 'created') == 0){ + $sortby = $request->get('sortby'); + } + else{ + $sortby = 'created'; + } + } + else{ + $sortby = 'created'; + } + + // execute query to get node ids + $nodeIds = $query + ->condition('type', self::NODE_TYPE) + ->sort($sortby, $sort, $lang) + ->execute(); + + // get nodes from ids + $nodes=\Drupal::entityTypeManager() + ->getStorage('node') + ->loadMultiple($nodeIds); + + // Response List + $nodeList=[]; + foreach ($nodes as $node) { + $tid = $node->id(); + $nodeList[$tid] = $node->hasTranslation($lang) ? $node->getTranslation($lang) : $node; + $node->currentTranslation = $lang; + } + return $nodeList; + + } + // \Drupal::logger('Bundle AAAAA')->notice(''.$some_var); +} diff --git a/endpoints/bundle_AAAAA/tests/modules/builders_test/builders_test.info.yml b/endpoints/bundle_AAAAA/tests/modules/builders_test/builders_test.info.yml new file mode 100644 index 0000000..fed70b2 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/builders_test/builders_test.info.yml @@ -0,0 +1,6 @@ +name: 'Builders test' +description: 'Builders test' +package: Testing + +type: module +version: 1.0 diff --git a/endpoints/bundle_AAAAA/tests/modules/builders_test/src/ArticleBuilder.php b/endpoints/bundle_AAAAA/tests/modules/builders_test/src/ArticleBuilder.php new file mode 100644 index 0000000..2313550 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/builders_test/src/ArticleBuilder.php @@ -0,0 +1,101 @@ +node = Node::create([ + 'type' => 'article', + 'langcode' => $this->languageCode, + 'title' => $this->title, + 'field_article_type' => $this->typeId, + 'field_article_description' => $this->description, + 'field_thumbnail' => $this->thumbnail, + 'uid' => $this->authorId, + 'created' => $this->created + ]); + } + + public static function create(): ArticleBuilder { + return new ArticleBuilder(); + } + + public function withLanguageCode(string $languageCode): ArticleBuilder { + $this->languageCode = $languageCode; + $this->node->set('langcode', $this->languageCode); + return $this; + } + + public function withType(int $typeId): ArticleBuilder { + $this->typeId = $typeId; + $this->node->set('field_article_type', $this->typeId); + return $this; + } + + public function withSummary(string $summary): ArticleBuilder { + $this->summary = $summary; + $this->node->set('field_summary', $this->summary); + return $this; + } + + public function withPhotos(array $photos): ArticleBuilder { + $this->photos = $photos; + $this->node->set('field_article_photos', $this->photos); + return $this; + } + + public function withVideos(array $videos): ArticleBuilder { + $this->videos = $videos; + $this->node->set('field_article_videos', $this->videos); + return $this; + } + + public function withFiles(array $files): ArticleBuilder { + $this->files = $files; + $this->node->set('field_article_files', $this->files); + return $this; + } + + public function withSources(array $sources): ArticleBuilder { + $this->sources = $sources; + $this->node->set('field_source', $this->sources); + return $this; + } + + public function withLinks(array $links): ArticleBuilder { + $this->links = $links; + $this->node->set('field_url', $this->links); + return $this; + } + + public function withFacebookPage(string $facebookPage): ArticleBuilder { + $this->facebookPage = $facebookPage; + $this->node->set('field_facebook_page', $this->facebookPage); + return $this; + } + + public function build(): Node { + $this->node->save(); + return $this->node; + } + +} diff --git a/endpoints/bundle_AAAAA/tests/modules/builders_test/src/ArticleTypeBuilder.php b/endpoints/bundle_AAAAA/tests/modules/builders_test/src/ArticleTypeBuilder.php new file mode 100644 index 0000000..8974088 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/builders_test/src/ArticleTypeBuilder.php @@ -0,0 +1,36 @@ +term = Term::create([ + 'vid' => 'article_types', + 'langcode' => $this->languageCode, + 'name' => $this->name + ]); + } + + public static function create(): ArticleTypeBuilder { + return new ArticleTypeBuilder(); + } + + public function withLanguageCode(string $languageCode): ArticleTypeBuilder { + $this->languageCode = $languageCode; + $this->term->set('langcode', $this->languageCode); + return $this; + } + + public function build(): Term { + $this->term->save(); + return $this->term; + } + +} diff --git a/endpoints/bundle_AAAAA/tests/modules/builders_test/src/FileBuilder.php b/endpoints/bundle_AAAAA/tests/modules/builders_test/src/FileBuilder.php new file mode 100644 index 0000000..88fe796 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/builders_test/src/FileBuilder.php @@ -0,0 +1,34 @@ +file = File::create([ + 'uri' => $this->baseUrl . '/' . $this->fileName + ]); + } + + public static function create(): FileBuilder { + return new FileBuilder(); + } + + public function withFileName(string $fileName): FileBuilder { + $this->fileName = $fileName; + $this->file->set('uri', $this->baseUrl . '/' . $this->fileName); + return $this; + } + + public function build(): File { + $this->file->save(); + return $this->file; + } + +} diff --git a/endpoints/bundle_AAAAA/tests/modules/builders_test/src/PageBuilder.php b/endpoints/bundle_AAAAA/tests/modules/builders_test/src/PageBuilder.php new file mode 100644 index 0000000..c599712 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/builders_test/src/PageBuilder.php @@ -0,0 +1,30 @@ +node = Node::create([ + 'type' => 'page', + 'langcode' => $this->languageCode, + 'title' => $this->title + ]); + } + + public static function create(): PageBuilder { + return new PageBuilder(); + } + + public function build(): Node { + $this->node->save(); + return $this->node; + } + +} diff --git a/endpoints/bundle_AAAAA/tests/modules/builders_test/src/UserBuilder.php b/endpoints/bundle_AAAAA/tests/modules/builders_test/src/UserBuilder.php new file mode 100644 index 0000000..c839d34 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/builders_test/src/UserBuilder.php @@ -0,0 +1,30 @@ +user = User::create([ + 'name' => $this->name . '@' . $this->surname . '.com', + 'field_user_name' => $this->name, + 'field_user_surname' => $this->surname + ]); + } + + public static function create(): UserBuilder { + return new UserBuilder(); + } + + public function build(): User { + $this->user->save(); + return $this->user; + } + +} diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/bundle_article_test.info.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/bundle_article_test.info.yml new file mode 100644 index 0000000..67ad7c3 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/bundle_article_test.info.yml @@ -0,0 +1,6 @@ +name: 'Article bundle test' +description: 'Article bundle test' +package: Testing + +type: module +version: 1.0 diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_description.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_description.yml new file mode 100644 index 0000000..c067d4f --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_description.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_article_description + - node.type.article + module: + - text +id: node.article.field_article_description +field_name: field_article_description +entity_type: node +bundle: article +label: 'Description' +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: text_long diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_files.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_files.yml new file mode 100644 index 0000000..ebd4622 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_files.yml @@ -0,0 +1,26 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_article_files + - node.type.article + module: + - file +id: node.article.field_article_files +field_name: field_article_files +entity_type: node +bundle: article +label: 'Files' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'txt rtf doc csv docx ppt pptx xls xlsx xlsm pdf odf odg odp ods odt fodt fods fodp fodg key numbers pages asc zip ppsx svg' + max_filesize: '' + description_field: false + handler: 'default:file' + handler_settings: { } +field_type: file diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_photos.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_photos.yml new file mode 100644 index 0000000..9f30672 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_photos.yml @@ -0,0 +1,37 @@ +langcode: el +status: true +dependencies: + config: + - field.storage.node.field_article_photos + - node.type.article + module: + - image +id: node.article.field_article_photos +field_name: field_article_photos +entity_type: node +bundle: article +label: 'Photos' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'png gif jpg jpeg' + max_filesize: '' + max_resolution: '' + min_resolution: '' + alt_field: true + alt_field_required: true + title_field: true + title_field_required: false + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_type.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_type.yml new file mode 100644 index 0000000..5e0e056 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_type.yml @@ -0,0 +1,28 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_article_type + - node.type.article + - taxonomy.vocabulary.article_types +id: node.article.field_article_type +field_name: field_article_type +entity_type: node +bundle: article +label: 'Article type' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:taxonomy_term' + handler_settings: + target_bundles: + article_types: article_types + sort: + field: name + direction: asc + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_videos.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_videos.yml new file mode 100644 index 0000000..e00f836 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_videos.yml @@ -0,0 +1,26 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_article_videos + - node.type.article + module: + - file +id: node.article.field_article_videos +field_name: field_article_videos +entity_type: node +bundle: article +label: 'Videos' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'mp4 ogv' + max_filesize: '' + description_field: false + handler: 'default:file' + handler_settings: { } +field_type: file diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_facebook_page.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_facebook_page.yml new file mode 100644 index 0000000..101467e --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_facebook_page.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_facebook_page + - node.type.article + module: + - link +id: node.article.field_facebook_page +field_name: field_facebook_page +entity_type: node +bundle: article +label: 'Facebook page' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + link_type: 17 + title: 1 +field_type: link diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_source.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_source.yml new file mode 100644 index 0000000..0024ee3 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_source.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_source + - node.type.article + module: + - link +id: node.article.field_source +field_name: field_source +entity_type: node +bundle: article +label: 'Sources' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + link_type: 17 + title: 1 +field_type: link diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_summary.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_summary.yml new file mode 100644 index 0000000..185bd62 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_summary.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_summary + - node.type.article + module: + - text +id: node.article.field_summary +field_name: field_summary +entity_type: node +bundle: article +label: 'Summary' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: text_long diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_thumbnail.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_thumbnail.yml new file mode 100644 index 0000000..e47a9fd --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_thumbnail.yml @@ -0,0 +1,37 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_thumbnail + - node.type.article + module: + - image +id: node.article.field_thumbnail +field_name: field_thumbnail +entity_type: node +bundle: article +label: 'Κεντρική φωτογραφία' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'png gif jpg jpeg' + max_filesize: '' + max_resolution: '' + min_resolution: '' + alt_field: true + alt_field_required: true + title_field: true + title_field_required: false + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_url.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_url.yml new file mode 100644 index 0000000..d2a74d5 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_url.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_url + - node.type.article + module: + - link +id: node.article.field_url +field_name: field_url +entity_type: node +bundle: article +label: 'Links' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + link_type: 17 + title: 1 +field_type: link diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_name.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_name.yml new file mode 100644 index 0000000..2d45fbd --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_name.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_user_name + module: + - user +id: user.user.field_user_name +field_name: field_user_name +entity_type: user +bundle: user +label: 'Name' +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_surname.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_surname.yml new file mode 100644 index 0000000..645ae84 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_surname.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_user_surname + module: + - user +id: user.user.field_user_surname +field_name: field_user_surname +entity_type: user +bundle: user +label: 'Surname' +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_description.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_description.yml new file mode 100644 index 0000000..a63e632 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_description.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - node + - text +id: node.field_article_description +field_name: field_article_description +entity_type: node +type: text_long +settings: { } +module: text +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_files.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_files.yml new file mode 100644 index 0000000..a4485a1 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_files.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - file + - node +id: node.field_article_files +field_name: field_article_files +entity_type: node +type: file +settings: + display_field: true + display_default: true + uri_scheme: public + target_type: file +module: file +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_photos.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_photos.yml new file mode 100644 index 0000000..ce3af2c --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_photos.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + module: + - file + - image + - node +id: node.field_article_photos +field_name: field_article_photos +entity_type: node +type: image +settings: + uri_scheme: public + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + target_type: file + display_field: false + display_default: false +module: image +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_type.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_type.yml new file mode 100644 index 0000000..6dd81b0 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_type.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - node + - taxonomy +id: node.field_article_type +field_name: field_article_type +entity_type: node +type: entity_reference +settings: + target_type: taxonomy_term +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_videos.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_videos.yml new file mode 100644 index 0000000..c6b9847 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_videos.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - file + - node +id: node.field_article_videos +field_name: field_article_videos +entity_type: node +type: file +settings: + display_field: true + display_default: true + uri_scheme: public + target_type: file +module: file +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_facebook_page.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_facebook_page.yml new file mode 100644 index 0000000..2955555 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_facebook_page.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - link + - node +id: node.field_facebook_page +field_name: field_facebook_page +entity_type: node +type: link +settings: { } +module: link +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_source.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_source.yml new file mode 100644 index 0000000..6c682a1 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_source.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - link + - node +id: node.field_source +field_name: field_source +entity_type: node +type: link +settings: { } +module: link +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_summary.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_summary.yml new file mode 100644 index 0000000..45b33a8 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_summary.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - node + - text +id: node.field_summary +field_name: field_summary +entity_type: node +type: text_long +settings: { } +module: text +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_thumbnail.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_thumbnail.yml new file mode 100644 index 0000000..66fe3f4 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_thumbnail.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + module: + - file + - image + - node +id: node.field_thumbnail +field_name: field_thumbnail +entity_type: node +type: image +settings: + uri_scheme: public + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + target_type: file + display_field: false + display_default: false +module: image +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_url.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_url.yml new file mode 100644 index 0000000..66e9816 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_url.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - link + - node +id: node.field_url +field_name: field_url +entity_type: node +type: link +settings: { } +module: link +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_name.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_name.yml new file mode 100644 index 0000000..4729510 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_name.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - user +id: user.field_user_name +field_name: field_user_name +entity_type: user +type: string +settings: + max_length: 255 + is_ascii: false + case_sensitive: false +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_surname.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_surname.yml new file mode 100644 index 0000000..af7d1d1 --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_surname.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - user +id: user.field_user_surname +field_name: field_user_surname +entity_type: user +type: string +settings: + max_length: 255 + is_ascii: false + case_sensitive: false +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/node.type.article.yml b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/node.type.article.yml new file mode 100644 index 0000000..58610fc --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/modules/bundle_article_test/config/install/node.type.article.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +name: 'Article' +type: article +description: '' +help: '' +new_revision: false +preview_mode: 1 +display_submitted: false diff --git a/endpoints/bundle_AAAAA/tests/src/Kernel/GetArticleTypesTest.php b/endpoints/bundle_AAAAA/tests/src/Kernel/GetArticleTypesTest.php new file mode 100644 index 0000000..1ed481c --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/src/Kernel/GetArticleTypesTest.php @@ -0,0 +1,41 @@ +installEntitySchema('taxonomy_term'); + } + + public function testShouldReturnListOfArticleTypesInRequestedLanguage() { + ArticleTypeBuilder::create()->build(); + ArticleTypeBuilder::create()->withLanguageCode('el')->build(); + ArticleTypeBuilder::create()->build(); + + $request = Request::create('/type/article?languageCode=en'); + $response = $this->container->get('http_kernel')->handle($request); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('{"code":200,"languageCode":"en","types":[{"id":"1","name":"Type"},{"id":"3","name":"Type"}]}', $response->getContent()); + } + + public function testShouldReturnErrorMessageWhenLanguageCodeQueryParameterIsMissing() { + $request = Request::create('/type/article'); + $response = $this->container->get('http_kernel')->handle($request); + + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals('{"code":400,"message":"Query parameter \u0022languageCode\u0022 is mandatory and is missing."}', $response->getContent()); + } + +} diff --git a/endpoints/bundle_AAAAA/tests/src/Kernel/GetArticlesTest.php b/endpoints/bundle_AAAAA/tests/src/Kernel/GetArticlesTest.php new file mode 100644 index 0000000..2e8c2cb --- /dev/null +++ b/endpoints/bundle_AAAAA/tests/src/Kernel/GetArticlesTest.php @@ -0,0 +1,134 @@ +installEntitySchema('taxonomy_term'); + $this->installEntitySchema('user'); + $this->installSchema('system', ['sequences']); + $this->installEntitySchema('node'); + $this->installEntitySchema('file'); + $this->installSchema('file', ['file_usage']); + $this->installConfig('bundle_article_test'); + } + + public function testShouldReturnListOfArticles() { + ArticleTypeBuilder::create()->build(); + FileBuilder::create()->build(); + UserBuilder::create()->build(); + PageBuilder::create()->build(); + ArticleBuilder::create()->build(); + ArticleBuilder::create()->build(); + + $request = Request::create('/articles?languageCode=en'); + $response = $this->container->get('http_kernel')->handle($request); + + $secondArticleAsJson = '{"id":"2","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $thirdArticleAsJson = '{"id":"3","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('{"code":200,"languageCode":"en","articles":[' . $secondArticleAsJson . ',' . $thirdArticleAsJson . ']}', $response->getContent()); + } + + + public function testShouldReturnListOfArticlesInRequestedLanguage() { + ArticleTypeBuilder::create()->build(); + $photo = FileBuilder::create()->build(); + $video = FileBuilder::create()->withFileName('filename.mp4')->build(); + $file = FileBuilder::create()->withFileName('filename.pdf')->build(); + UserBuilder::create()->build(); + ArticleBuilder::create()->withSummary('Summary') + ->withPhotos([$photo->id(), $photo->id(), $photo->id()]) + ->withVideos([$video->id(), $video->id(), $video->id()]) + ->withFiles([$file->id(), $file->id(), $file->id()]) + ->withSources(['https://example.com/source1', 'https://example.com/source2', 'https://example.com/source3']) + ->withLinks(['https://example.com/link1', 'https://example.com/link2', 'https://example.com/link3']) + ->withFacebookPage('https://facebook.com/event1') + ->build(); + ArticleBuilder::create()->withLanguageCode('el')->build(); + ArticleBuilder::create()->build(); + + $request = Request::create('/articles?languageCode=en'); + $response = $this->container->get('http_kernel')->handle($request); + + $firstArticleAsJson = '{"id":"1","typeId":"1","typeName":"Type","title":"Title","summary":"Summary","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":["https:\/\/example.com\/filename.jpg","https:\/\/example.com\/filename.jpg","https:\/\/example.com\/filename.jpg"],"videos":["https:\/\/example.com\/filename.mp4","https:\/\/example.com\/filename.mp4","https:\/\/example.com\/filename.mp4"],"files":["https:\/\/example.com\/filename.pdf","https:\/\/example.com\/filename.pdf","https:\/\/example.com\/filename.pdf"],"sources":["https:\/\/example.com\/source1","https:\/\/example.com\/source2","https:\/\/example.com\/source3"],"links":["https:\/\/example.com\/link1","https:\/\/example.com\/link2","https:\/\/example.com\/link3"],"facebookEvent":"https:\/\/facebook.com\/event1","author":"Name Surname","date":"1234567890"}'; + $thirdArticleAsJson = '{"id":"3","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('{"code":200,"languageCode":"en","articles":[' . $firstArticleAsJson . ',' . $thirdArticleAsJson . ']}', $response->getContent()); + } + + public function testShouldReturnArticleWithRequestedIdAndLanguage() { + ArticleTypeBuilder::create()->build(); + FileBuilder::create()->build(); + UserBuilder::create()->build(); + ArticleBuilder::create()->build(); + ArticleBuilder::create()->build(); + ArticleBuilder::create()->build(); + + $request = Request::create('/articles?languageCode=en&id=2'); + $response = $this->container->get('http_kernel')->handle($request); + + $secondArticleAsJson = '{"id":"2","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('{"code":200,"languageCode":"en","articles":[' . $secondArticleAsJson . ']}', $response->getContent()); + } + + public function testShouldReturnErrorWhenArticleForRequestedIdDoesNotExist() { + $request = Request::create('/articles?languageCode=en&id=1'); + $response = $this->container->get('http_kernel')->handle($request); + + $this->assertEquals(404, $response->getStatusCode()); + $this->assertEquals('{"code":404,"message":"Article with id 1 does not exist."}', $response->getContent()); + } + + public function testShouldReturnListOfArticlesForRequestedType() { + ArticleTypeBuilder::create()->build(); + ArticleTypeBuilder::create()->build(); + FileBuilder::create()->build(); + UserBuilder::create()->build(); + ArticleBuilder::create()->build(); + ArticleBuilder::create()->withType(2)->build(); + ArticleBuilder::create()->build(); + + $request = Request::create('/articles?languageCode=en&typeId=1'); + $response = $this->container->get('http_kernel')->handle($request); + + $firstArticleAsJson = '{"id":"1","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $thirdArticleAsJson = '{"id":"3","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('{"code":200,"languageCode":"en","articles":[' . $firstArticleAsJson . ',' . $thirdArticleAsJson . ']}', $response->getContent()); + } + + public function testShouldReturnErrorMessageWhenLanguageCodeQueryParameterIsMissing() { + $request = Request::create('/articles'); + $response = $this->container->get('http_kernel')->handle($request); + + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals('{"code":400,"message":"Query parameter \u0022languageCode\u0022 is mandatory and is missing."}', $response->getContent()); + } + +} diff --git a/endpoints/endpoint-get-delete.sh b/endpoints/endpoint-get-delete.sh new file mode 100755 index 0000000..28df15b --- /dev/null +++ b/endpoints/endpoint-get-delete.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +SITE=${1} +NODE=${2} + +BASE_DIR="/var/www/sites" +MODULES="modules/custom" + +[ "$#" -lt 2 ] && echo "Argument missing" && exit +[ ! -d ${SITE} ] && echo "Directory ${SITE} does not exist" && exit +[ -d ${SITE}/web ] && SITE=${SITE}/web + +echo -en " 🌈 Disable module ${B}bundle_${NODE}${E}" +cd /var/www/sites/${SITE} +../vendor/drush/drush/drush pmu "bundle_${NODE}" + +echo -en " 📂 Delete directory ${B}${SITE}/modules/custom/bundle_${NODE}${E}" +rm "/var/www/sites/${SITE}/modules/custom/bundle_${NODE}" -rf diff --git a/endpoints/endpoint-get.sh b/endpoints/endpoint-get.sh new file mode 100755 index 0000000..d8e2c21 --- /dev/null +++ b/endpoints/endpoint-get.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +SITE=${1} +NODE=${2} + +G="\e[0;32m" +B="\e[0;34m" +W="\e[0;97m" +E="\e[00m" + +SOURCE="/root/dev/endpoints" +BASE_DIR="/var/www/sites" +MODULES="modules/custom" + + + +[ "$#" -lt 2 ] && echo "Argument missing" && exit +[ ! -d ${SITE} ] && echo "Directory ${SITE} does not exist" && exit +[ -d ${SITE}/web ] && SITE=${SITE}/web + + + +function log() { + [ $1 == 0 ] && echo -e " ${G}[ OK ]${E}"; return + echo -e " ${R}[FAIL]${E}" + exit +} + + + +echo +echo -en " 📂 Copy template module dir to ${B}${SITE}${E}" +cp -r "${SOURCE}/endpoint_get_AAAAA" "${BASE_DIR}/${SITE}/${MODULES}/endpoint_get_${NODE}" +log $? + +cd "${BASE_DIR}/${SITE}/${MODULES}/endpoint_get_${NODE}" + +# Rename files + +echo -en " 🍓 Rename ${B}endpoint_get_${NODE}.info.yml${E} file" +mv "endpoint_get_AAAAA.info.yml" "endpoint_get_${NODE}.info.yml" +log $? + +echo -en " 🍓 Rename ${B}endpoint_get_${NODE}.routing.yml${E} file" +mv "endpoint_get_AAAAA.routing.yml" "endpoint_get_${NODE}.routing.yml" +log $? + +# Edit files + +echo -en " ⭐ Edit ${B}endpoint_get_${NODE}.info.yml${E} file" +sed -i "s/AAAAA/${NODE}/g" "endpoint_get_${NODE}.info.yml" +log $? + +echo -en " ⭐ Edit ${B}endpoint_get_${NODE}.routing.yml${E} file" +sed -i "s/AAAAA/${NODE}/g" "endpoint_get_${NODE}.routing.yml" +log $? + +echo -en " ⭐ Edit ${B}src/Controller/NodeController.php${E} file" +sed -i "s/AAAAA/${NODE}/g" "src/Controller/NodeController.php" +log $? + +echo -en " ⭐ Edit ${B}src/Controller/NodesController.php${E} file" +sed -i "s/AAAAA/${NODE}/g" "src/Controller/NodesController.php" +log $? + +echo -en " ⭐ Edit ${B}src/Controller/CategoryController.php${E} file" +sed -i "s/AAAAA/${NODE}/g" "src/Controller/CategoryController.php" +log $? + +exit +# Enable module + +echo -e " 🌈 Enable module" +cd /var/www/sites/${SITE} +../vendor/drush/drush/drush en "endpoint_get_${NODE}" + +# Test endpoint + +BASE_URL="http://$(ls -l /var/www/ | grep ${SITE} | awk {'print $9'}).vm7" +API_URL="api/el" +NODE_URL="${BASE_URL}/${API_URL}/${NODE}" +NODES_URL="${BASE_URL}/${API_URL}/${NODE}s" + +cat "endpoint-get-${NODE}.routing.yml" | grep path + +# echo "BASE_URL :" ${BASE_URL} +# echo "API_URL :" ${API_URL} +# echo "NODE :" ${NODE} +# echo "NODE_URL :" ${NODE_URL} +# echo "NODEs_URL:" ${NODES_URL} + +ENDPOINTS=( + api/en/${NODE}s + api/el/${NODE}s +) + +for ENDPOINT in ${ENDPOINTS[@]}; do + ENDPOINT_STATUS=$(curl -sI --location --request GET $BASE_URL/$ENDPOINT?_format=json \ + -b cookie.txt \ + --header "Content-type: application/json" | grep HTTP | awk {'print $2 " " $3'}) + echo -e " 🍒 GET $ENDPOINT: $ENDPOINT_STATUS" +done + diff --git a/endpoints/endpoint-post-delete.sh b/endpoints/endpoint-post-delete.sh new file mode 100755 index 0000000..28df15b --- /dev/null +++ b/endpoints/endpoint-post-delete.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +SITE=${1} +NODE=${2} + +BASE_DIR="/var/www/sites" +MODULES="modules/custom" + +[ "$#" -lt 2 ] && echo "Argument missing" && exit +[ ! -d ${SITE} ] && echo "Directory ${SITE} does not exist" && exit +[ -d ${SITE}/web ] && SITE=${SITE}/web + +echo -en " 🌈 Disable module ${B}bundle_${NODE}${E}" +cd /var/www/sites/${SITE} +../vendor/drush/drush/drush pmu "bundle_${NODE}" + +echo -en " 📂 Delete directory ${B}${SITE}/modules/custom/bundle_${NODE}${E}" +rm "/var/www/sites/${SITE}/modules/custom/bundle_${NODE}" -rf diff --git a/endpoints/endpoint-post.sh b/endpoints/endpoint-post.sh new file mode 100755 index 0000000..5f760a6 --- /dev/null +++ b/endpoints/endpoint-post.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +SITE=${1} +NODE=${2} + +G="\e[0;32m" +B="\e[0;34m" +W="\e[0;97m" +E="\e[00m" + +SOURCE="/root/Dev-Enviroment/toolbox/endpoints_maker" +BASE_DIR="/var/www/sites" +MODULES="modules/custom" + + + +[ "$#" -lt 2 ] && echo "Argument missing" && exit +[ ! -d ${SITE} ] && echo "Directory ${SITE} does not exist" && exit +[ -d ${SITE}/web ] && SITE=${SITE}/web + + + +function log() { + [ $1 == 0 ] && echo -e " ${G}[ OK ]${E}"; return + echo -e " ${R}[FAIL]${E}" + exit +} + + + +echo +echo -en " 📂 Copy template module dir to ${B}${SITE}${E}" +cp -r "${SOURCE}/bundle_AAAAA" "${BASE_DIR}/${SITE}/${MODULES}/bundle_${NODE}" +log $? + +cd "${BASE_DIR}/${SITE}/${MODULES}/bundle_${NODE}" + +# Rename files + +echo -en " 🍓 Rename ${B}bundle_${NODE}.info.yml${E} file" +mv "bundle_AAAAA.info.yml" "bundle_${NODE}.info.yml" +log $? + +echo -en " 🍓 Rename ${B}bundle_${NODE}.routing.yml${E} file" +mv "bundle_AAAAA.routing.yml" "bundle_${NODE}.routing.yml" +log $? + +# Edit files + +echo -en " ⭐ Edit ${B}bundle_${NODE}.info.yml${E} file" +sed -i "s/AAAAA/${NODE}/g" "bundle_${NODE}.info.yml" +log $? + +echo -en " ⭐ Edit ${B}bundle_${NODE}.routing.yml${E} file" +sed -i "s/AAAAA/${NODE}/g" "bundle_${NODE}.routing.yml" +log $? + +echo -en " ⭐ Edit ${B}src/Controller/NodeController.php${E} file" +sed -i "s/AAAAA/${NODE}/g" "src/Controller/NodeController.php" +log $? + +echo -en " ⭐ Edit ${B}src/Controller/NodesController.php${E} file" +sed -i "s/AAAAA/${NODE}/g" "src/Controller/NodesController.php" +log $? + +echo -en " ⭐ Edit ${B}src/Controller/CategoryController.php${E} file" +sed -i "s/AAAAA/${NODE}/g" "src/Controller/CategoryController.php" +log $? + +# Enable module + +echo -e " 🌈 Enable module" +cd /var/www/sites/${SITE} +../vendor/drush/drush/drush en "bundle_${NODE}" + +# Test endpoint + +BASE_URL="http://$(ls -l /var/www/ | grep ${SITE} | awk {'print $9'}).vm7" +API_URL="api/el" +NODE_URL="${BASE_URL}/${API_URL}/${NODE}" +NODES_URL="${BASE_URL}/${API_URL}/${NODE}s" + +cat "bundle_${NODE}.routing.yml" | grep path + +# echo "BASE_URL :" ${BASE_URL} +# echo "API_URL :" ${API_URL} +# echo "NODE :" ${NODE} +# echo "NODE_URL :" ${NODE_URL} +# echo "NODEs_URL:" ${NODES_URL} + +ENDPOINTS=( + api/en/${NODE}s + api/el/${NODE}s +) + +for ENDPOINT in ${ENDPOINTS[@]}; do + ENDPOINT_STATUS=$(curl -sI --location --request GET $BASE_URL/$ENDPOINT?_format=json \ + -b cookie.txt \ + --header "Content-type: application/json" | grep HTTP | awk {'print $2 " " $3'}) + echo -e " 🍒 GET $ENDPOINT: $ENDPOINT_STATUS" +done + diff --git a/endpoints/endpoint_get_AAAAA/endpoint_get_AAAAA.info.yml b/endpoints/endpoint_get_AAAAA/endpoint_get_AAAAA.info.yml new file mode 100644 index 0000000..75e9840 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/endpoint_get_AAAAA.info.yml @@ -0,0 +1,8 @@ +name: 'Endpoint GET for AAAAA' +description: 'Endpoint GET for AAAAA' +package: DOTSOFT + +type: module +version: 1.0 + +core_version_requirement: ^10 diff --git a/endpoints/endpoint_get_AAAAA/endpoint_get_AAAAA.routing.yml b/endpoints/endpoint_get_AAAAA/endpoint_get_AAAAA.routing.yml new file mode 100644 index 0000000..7a06de6 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/endpoint_get_AAAAA.routing.yml @@ -0,0 +1,39 @@ +endpoint_get_AAAAA.node: + path: '/api/{lang}/AAAAAs/{alias}' + defaults: + _controller: 'Drupal\endpoint_get_AAAAA\Controller\NodeController::getNode' + methods: [GET] + requirements: + _access: 'TRUE' + options: + no_cache: 'TRUE' + +endpoint_get_AAAAA.nodes: + path: '/api/{lang}/AAAAAs' + defaults: + _controller: 'Drupal\endpoint_get_AAAAA\Controller\NodesController::getNodes' + methods: [GET] + requirements: + _access: 'TRUE' + options: + no_cache: 'TRUE' + +# endpoint_get_AAAAA.categories: +# path: '/api/{lang}/AAAAAs/categories' +# defaults: +# _controller: 'Drupal\endpoint_get_AAAAA\Controller\CategoryController::getCategories' +# methods: [GET] +# requirements: +# _access: 'TRUE' +# options: +# no_cache: 'TRUE' + +# endpoint_get_AAAAA.nodes_category: +# path: '/api/{lang}/AAAAAs/categories/{category}' +# defaults: +# _controller: 'Drupal\endpoint_get_AAAAA\Controller\NodesController::getNodes' +# methods: [GET] +# requirements: +# _access: 'TRUE' +# options: +# no_cache: 'TRUE' diff --git a/endpoints/endpoint_get_AAAAA/src/Controller/CategoryController.php b/endpoints/endpoint_get_AAAAA/src/Controller/CategoryController.php new file mode 100644 index 0000000..00b63f2 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/src/Controller/CategoryController.php @@ -0,0 +1,65 @@ +response = [ + 'code' => $this->statusCode, + 'lang' => $lang, + 'categories' => $this->buildVocabulary($lang)]; + + return new JsonResponse($this->response, $this->statusCode); + } + + // RESPONSE ARRAY + + private function buildVocabulary($lang): array { + $terms = $this->loadTerms($lang); + $terms_response = []; + foreach ($terms as $type) { + $terms_response[] = [ + 'id' => $type->id(), + 'name' => $type->getName() + ]; + } + return $terms_response; + } + + // RESPONSE ARRAY VALUES + + private function loadTerms($lang) { + + // All terms from the vocabulary + $terms=\Drupal::entityTypeManager() + ->getStorage('taxonomy_term') + ->loadByProperties([ + 'vid' => self::VOCABULARY, + ]); + + // Current language only + $termList=[]; + foreach($terms as $term) { + if($term->hasTranslation($lang)){ + $tid = $term->id(); + $translated_term = \Drupal::service('entity.repository')->getTranslationFromContext($term, $lang); + $termList[$tid] = $translated_term; + } + } + return $termList; + } + +} diff --git a/endpoints/endpoint_get_AAAAA/src/Controller/NodeController.php b/endpoints/endpoint_get_AAAAA/src/Controller/NodeController.php new file mode 100644 index 0000000..7d2bb45 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/src/Controller/NodeController.php @@ -0,0 +1,172 @@ +errorAliasMissing(); + return new JsonResponse($this->response, $this->statusCode); + } + + /* check if alias exist in drupal */ + $path_alias = '/'.$alias; + $path = \Drupal::service('path_alias.manager')->getPathByAlias($path_alias); + if ( $path_alias == $path) { + $this->errorNodeNotExist($alias, $path); + return new JsonResponse($this->response, $this->statusCode); + } + + /* check if node has translation */ + $nid = Url::fromUri('internal:' . $path_alias)->getRouteParameters()['node']; + $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid); + if (! $node->hasTranslation($lang)) { + $this->errorTranslationNotExist($lang, $alias); + return new JsonResponse($this->response, $this->statusCode); + } + + /* build response */ + $this->buildNodeResponse($lang, $alias); + return new JsonResponse($this->response, $this->statusCode); + } + + /* Response */ + + private function buildNodeResponse($lang, $alias) { + $path = \Drupal::service('path_alias.manager')->getPathByAlias($alias); + $nid = Url::fromUri('internal:/' . $path)->getRouteParameters()['node']; + $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid)->getTranslation($lang); + + /* user name */ + $uid = $node->getOwnerId(); + $user = \Drupal\user\Entity\User::load($uid); + $name = $user->getDisplayName(); + + /* absolute urls for body inline images */ + $base_url = Url::fromRoute('')->setAbsolute()->toString(); + $url_components = parse_url($base_url); + $domain = $url_components['scheme'].'://'.$url_components['host'].':'.$url_components['port']; + $body = str_replace( + '/sites/default/', + $domain.'/sites/default/', + $node->get('body')->value + ); + + $this->response = [ + 'code' => $this->statusCode, + 'alias' => $alias, + 'path' => $path, + 'nid' => $nid, + 'title' => $node->get('title')->value, + 'body' => $body, + 'lang' => $node->get('langcode')->value, + 'alias' => $node->get('path')->alias, + 'created' => $node->get('created')->value, + 'author' => $name, + // 'files' => $this->getFiles($node, 'field_file'), + // 'images' => $this->getImages($node, 'field_image'), + // 'category' => ( + // $node + // ->get(self::FIELD_CATEGORY) + // ->entity)?\Drupal::service('entity.repository') + // ->getTranslationFromContext( + // $node->get(self::FIELD_CATEGORY)->entity, + // $node->currentTranslation + // )->getName():'', + // 'subcategory' => $this->getTerms($node, self::FIELD_SUBCATEGORY) + ]; + } + + + /* Multiple value fields */ + + // private function getTerms(Node $node, string $field): array { + // $terms = $node->get($field)->referencedEntities(); + // $response = []; + // foreach ($terms as $term) { + // $name = $term->getName(); + // $tid = $term->id(); + // $response[] = array( + // 'name' => $name, + // 'id' => $tid + // ); + // } + // return $response; + // } + + // private function getFiles(Node $node, string $field): array { + // $uris = []; + // foreach ($node->get($field) as $value) { + // $file = \Drupal::entityTypeManager() + // ->getStorage('file') + // ->load($value->getValue()['target_id']); + // $url = \Drupal::service('file_url_generator') + // ->generateAbsoluteString($file->getFileUri()); + // $uris[] = array("url" => $url); + // } + // return $uris; + // } + + // private function getImages(Node $node, string $field): array { + // $uris = []; + // foreach ($node->get($field)->getValue() as $value) { + // $file = \Drupal::entityTypeManager() + // ->getStorage('file') + // ->load($value['target_id']); + // $url = \Drupal::service('file_url_generator') + // ->generateAbsoluteString($file->getFileUri()); + // $uris[] = array("url" => $url, "alt" => $value['alt']); + // } + // return $uris; + // } + + /* Error functions */ + + private function errorNodeNotExist($alias, $path) { + $this->statusCode = self::HTTP_NOT_FOUND; + $this->response = [ + 'code' => $this->statusCode, + "message" => "Node with alias " . $alias . " does not exist." . $path + ]; + } + + private function errorTranslationNotExist($lang, $alias) { + $this->statusCode = self::HTTP_NOT_FOUND; + $this->response = [ + 'code' => $this->statusCode, + "message" => "Node with alias " . $alias . " does not have a translation for language code " . $lang + ]; + } + + private function errorAliasMissing() { + $this->statusCode = self::HTTP_BAD_REQUEST; + $this->response = [ + 'code' => $this->statusCode, + 'message' => 'Query parameter "alias" is mandatory and is missing.' + ]; + } + // \Drupal::logger('hello')->notice(''.$nid); +} diff --git a/endpoints/endpoint_get_AAAAA/src/Controller/NodesController.php b/endpoints/endpoint_get_AAAAA/src/Controller/NodesController.php new file mode 100644 index 0000000..228d2e6 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/src/Controller/NodesController.php @@ -0,0 +1,237 @@ +loadNodes($request, $lang, $category); + $nodes_response = $this->buildNodes($nodes, $lang, $category); + + $this->response = [ + 'code' => $this->statusCode, + 'count' => count($nodes_response), + 'nodes' => $nodes_response + ]; + return new JsonResponse($this->response, $this->statusCode); + } + + /* Response List */ + + private function buildNodes(array $nodes, $lang): array { + $nodes_response = []; + foreach ($nodes as $node) { + /* don't show node if it hasn't translation */ + if ( $node->hasTranslation($lang)) { + $node = $node->getTranslation($lang); + $published = $node->get('status')->value; + + /* get user name */ + $uid = $node->getOwnerId(); + $user = \Drupal\user\Entity\User::load($uid); + $name = $user->getDisplayName(); + + /* absolute urls for body inline images */ + $base_url = Url::fromRoute('')->setAbsolute()->toString(); + $url_components = parse_url($base_url); + $domain = $url_components['scheme'].'://'.$url_components['host'].':'.$url_components['port']; + $body = str_replace( + '/sites/default/', + $domain.'/sites/default/', + $node->get('body')->value + ); + + if ($published == 1) { + $nodes_response[] = [ + 'id' => $node->id(), + 'title' => $node->getTitle(), + 'body' => $body, + 'lang' => $node->get('langcode')->value, + 'created' => $node->get('created')->value, + 'alias' => $node->get('path')->alias, + 'author' => $name, + // 'file' => $this->getFiles($node, 'field_file'), + // 'image' => $this->getImages($node, 'field_image'), + // 'category_id' => ( + // $node + // ->get(self::FIELD_CATEGORY) + // ->entity)?\Drupal::service('entity.repository') + // ->getTranslationFromContext( + // $node->get(self::FIELD_CATEGORY)->entity, + // $node->currentTranslation + // )->id():'', + // 'category_label' => ( + // $node + // ->get(self::FIELD_CATEGORY) + // ->entity)?\Drupal::service('entity.repository') + // ->getTranslationFromContext( + // $node->get(self::FIELD_CATEGORY)->entity, + // $node->currentTranslation + // )->getName():'', + // 'subcategory' => $this->getTerms($node, self::FIELD_SUBCATEGORY) + ]; + } + } + } + return $nodes_response; + } + + /* Multivalue fields */ + + // private function getTerms(Node $node, string $field): array { + // $terms = $node->get($field)->referencedEntities(); + // $response = []; + // foreach ($terms as $term) { + // $name = $term->getName(); + // $tid = $term->id(); + // $response[] = array( + // 'name' => $name, + // 'id' => $tid + // ); + // } + // return $response; + // } + + // private function getFiles(Node $node, string $field): array { + // $uris = []; + // foreach ($node->get($field) as $value) { + // $file = \Drupal::entityTypeManager() + // ->getStorage('file') + // ->load($value->getValue()['target_id']); + // $url = \Drupal::service('file_url_generator') + // ->generateAbsoluteString($file->getFileUri()); + // $uris[] = array("url" => $url); + // } + // return $uris; + // } + + // private function getImages(Node $node, string $field): array { + // $uris = []; + // foreach ($node->get($field)->getValue() as $value) { + // $file = \Drupal::entityTypeManager() + // ->getStorage('file') + // ->load($value['target_id']); + // $url = \Drupal::service('file_url_generator') + // ->generateAbsoluteString($file->getFileUri()); + // $uris[] = array("url" => $url, "alt" => $value['alt']); + // } + // return $uris; + // } + + /* get response data */ + + private function loadNodes($request, $lang, $category) { + + /* build query for nodes */ + $query = \Drupal::entityTypeManager() + ->getStorage('node') + ->getQuery() + ->accessCheck(false); + + /* category is a URL parameter, matched with a taxonomy field */ + if ($category != 'all'){ + $query->condition(self::FIELD_CATEGORY, $category); + } + + /* add subcategory URL query filter, matched with a taxonomy field */ + if ($request->query->get('subcategory')){ + $or_group = $query->orConditionGroup(); + $terms = explode(',', $request->query->get('subcategory')); + foreach ($terms as $term) { + $or_group->condition(self::FIELD_SUBCATEGORY.'.target_id', $term); + } + $query->condition($or_group); + } + + /* add pager */ + $query->range($request->get('start'), $request->get('length')); + + /* sort results by DESC or ASC */ + if ($request->get('sort')) { + $sort = $request->get('sort'); + if(strcasecmp($sort, 'desc') == 0 || strcasecmp($sort, 'asc') == 0){ + $sort = $request->get('sort'); + } + else{ + $sort = 'DESC'; + } + } + else{ + $sort = 'DESC'; + } + + /* sort results by field */ + if ($request->get('sortby')) { + $sortby = $request->get('sortby'); + if(strcasecmp($sortby, 'title') == 0 || strcasecmp($sortby, 'created') == 0){ + $sortby = $request->get('sortby'); + } + else{ + $sortby = 'created'; + } + } + else{ + $sortby = 'created'; + } + + /* execute query to get node ids */ + $nodeIds = $query + ->condition('type', self::NODE_TYPE) + ->sort($sortby, $sort, $lang) + ->execute(); + + /* get nodes from ids */ + $nodes=\Drupal::entityTypeManager() + ->getStorage('node') + ->loadMultiple($nodeIds); + + /* Response List */ + $nodeList=[]; + foreach ($nodes as $node) { + $tid = $node->id(); + $nodeList[$tid] = $node->hasTranslation($lang) ? $node->getTranslation($lang) : $node; + $node->currentTranslation = $lang; + } + return $nodeList; + + } + // \Drupal::logger('Bundle AAAAA')->notice(''.$some_var); +} diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/builders_test.info.yml b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/builders_test.info.yml new file mode 100644 index 0000000..fed70b2 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/builders_test.info.yml @@ -0,0 +1,6 @@ +name: 'Builders test' +description: 'Builders test' +package: Testing + +type: module +version: 1.0 diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/ArticleBuilder.php b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/ArticleBuilder.php new file mode 100644 index 0000000..2313550 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/ArticleBuilder.php @@ -0,0 +1,101 @@ +node = Node::create([ + 'type' => 'article', + 'langcode' => $this->languageCode, + 'title' => $this->title, + 'field_article_type' => $this->typeId, + 'field_article_description' => $this->description, + 'field_thumbnail' => $this->thumbnail, + 'uid' => $this->authorId, + 'created' => $this->created + ]); + } + + public static function create(): ArticleBuilder { + return new ArticleBuilder(); + } + + public function withLanguageCode(string $languageCode): ArticleBuilder { + $this->languageCode = $languageCode; + $this->node->set('langcode', $this->languageCode); + return $this; + } + + public function withType(int $typeId): ArticleBuilder { + $this->typeId = $typeId; + $this->node->set('field_article_type', $this->typeId); + return $this; + } + + public function withSummary(string $summary): ArticleBuilder { + $this->summary = $summary; + $this->node->set('field_summary', $this->summary); + return $this; + } + + public function withPhotos(array $photos): ArticleBuilder { + $this->photos = $photos; + $this->node->set('field_article_photos', $this->photos); + return $this; + } + + public function withVideos(array $videos): ArticleBuilder { + $this->videos = $videos; + $this->node->set('field_article_videos', $this->videos); + return $this; + } + + public function withFiles(array $files): ArticleBuilder { + $this->files = $files; + $this->node->set('field_article_files', $this->files); + return $this; + } + + public function withSources(array $sources): ArticleBuilder { + $this->sources = $sources; + $this->node->set('field_source', $this->sources); + return $this; + } + + public function withLinks(array $links): ArticleBuilder { + $this->links = $links; + $this->node->set('field_url', $this->links); + return $this; + } + + public function withFacebookPage(string $facebookPage): ArticleBuilder { + $this->facebookPage = $facebookPage; + $this->node->set('field_facebook_page', $this->facebookPage); + return $this; + } + + public function build(): Node { + $this->node->save(); + return $this->node; + } + +} diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/ArticleTypeBuilder.php b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/ArticleTypeBuilder.php new file mode 100644 index 0000000..8974088 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/ArticleTypeBuilder.php @@ -0,0 +1,36 @@ +term = Term::create([ + 'vid' => 'article_types', + 'langcode' => $this->languageCode, + 'name' => $this->name + ]); + } + + public static function create(): ArticleTypeBuilder { + return new ArticleTypeBuilder(); + } + + public function withLanguageCode(string $languageCode): ArticleTypeBuilder { + $this->languageCode = $languageCode; + $this->term->set('langcode', $this->languageCode); + return $this; + } + + public function build(): Term { + $this->term->save(); + return $this->term; + } + +} diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/FileBuilder.php b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/FileBuilder.php new file mode 100644 index 0000000..88fe796 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/FileBuilder.php @@ -0,0 +1,34 @@ +file = File::create([ + 'uri' => $this->baseUrl . '/' . $this->fileName + ]); + } + + public static function create(): FileBuilder { + return new FileBuilder(); + } + + public function withFileName(string $fileName): FileBuilder { + $this->fileName = $fileName; + $this->file->set('uri', $this->baseUrl . '/' . $this->fileName); + return $this; + } + + public function build(): File { + $this->file->save(); + return $this->file; + } + +} diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/PageBuilder.php b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/PageBuilder.php new file mode 100644 index 0000000..c599712 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/PageBuilder.php @@ -0,0 +1,30 @@ +node = Node::create([ + 'type' => 'page', + 'langcode' => $this->languageCode, + 'title' => $this->title + ]); + } + + public static function create(): PageBuilder { + return new PageBuilder(); + } + + public function build(): Node { + $this->node->save(); + return $this->node; + } + +} diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/UserBuilder.php b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/UserBuilder.php new file mode 100644 index 0000000..c839d34 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/builders_test/src/UserBuilder.php @@ -0,0 +1,30 @@ +user = User::create([ + 'name' => $this->name . '@' . $this->surname . '.com', + 'field_user_name' => $this->name, + 'field_user_surname' => $this->surname + ]); + } + + public static function create(): UserBuilder { + return new UserBuilder(); + } + + public function build(): User { + $this->user->save(); + return $this->user; + } + +} diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/bundle_article_test.info.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/bundle_article_test.info.yml new file mode 100644 index 0000000..67ad7c3 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/bundle_article_test.info.yml @@ -0,0 +1,6 @@ +name: 'Article bundle test' +description: 'Article bundle test' +package: Testing + +type: module +version: 1.0 diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_description.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_description.yml new file mode 100644 index 0000000..c067d4f --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_description.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_article_description + - node.type.article + module: + - text +id: node.article.field_article_description +field_name: field_article_description +entity_type: node +bundle: article +label: 'Description' +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: text_long diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_files.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_files.yml new file mode 100644 index 0000000..ebd4622 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_files.yml @@ -0,0 +1,26 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_article_files + - node.type.article + module: + - file +id: node.article.field_article_files +field_name: field_article_files +entity_type: node +bundle: article +label: 'Files' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'txt rtf doc csv docx ppt pptx xls xlsx xlsm pdf odf odg odp ods odt fodt fods fodp fodg key numbers pages asc zip ppsx svg' + max_filesize: '' + description_field: false + handler: 'default:file' + handler_settings: { } +field_type: file diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_photos.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_photos.yml new file mode 100644 index 0000000..9f30672 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_photos.yml @@ -0,0 +1,37 @@ +langcode: el +status: true +dependencies: + config: + - field.storage.node.field_article_photos + - node.type.article + module: + - image +id: node.article.field_article_photos +field_name: field_article_photos +entity_type: node +bundle: article +label: 'Photos' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'png gif jpg jpeg' + max_filesize: '' + max_resolution: '' + min_resolution: '' + alt_field: true + alt_field_required: true + title_field: true + title_field_required: false + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_type.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_type.yml new file mode 100644 index 0000000..5e0e056 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_type.yml @@ -0,0 +1,28 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_article_type + - node.type.article + - taxonomy.vocabulary.article_types +id: node.article.field_article_type +field_name: field_article_type +entity_type: node +bundle: article +label: 'Article type' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:taxonomy_term' + handler_settings: + target_bundles: + article_types: article_types + sort: + field: name + direction: asc + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_videos.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_videos.yml new file mode 100644 index 0000000..e00f836 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_article_videos.yml @@ -0,0 +1,26 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_article_videos + - node.type.article + module: + - file +id: node.article.field_article_videos +field_name: field_article_videos +entity_type: node +bundle: article +label: 'Videos' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'mp4 ogv' + max_filesize: '' + description_field: false + handler: 'default:file' + handler_settings: { } +field_type: file diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_facebook_page.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_facebook_page.yml new file mode 100644 index 0000000..101467e --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_facebook_page.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_facebook_page + - node.type.article + module: + - link +id: node.article.field_facebook_page +field_name: field_facebook_page +entity_type: node +bundle: article +label: 'Facebook page' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + link_type: 17 + title: 1 +field_type: link diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_source.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_source.yml new file mode 100644 index 0000000..0024ee3 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_source.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_source + - node.type.article + module: + - link +id: node.article.field_source +field_name: field_source +entity_type: node +bundle: article +label: 'Sources' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + link_type: 17 + title: 1 +field_type: link diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_summary.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_summary.yml new file mode 100644 index 0000000..185bd62 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_summary.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_summary + - node.type.article + module: + - text +id: node.article.field_summary +field_name: field_summary +entity_type: node +bundle: article +label: 'Summary' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: text_long diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_thumbnail.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_thumbnail.yml new file mode 100644 index 0000000..e47a9fd --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_thumbnail.yml @@ -0,0 +1,37 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_thumbnail + - node.type.article + module: + - image +id: node.article.field_thumbnail +field_name: field_thumbnail +entity_type: node +bundle: article +label: 'Κεντρική φωτογραφία' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'png gif jpg jpeg' + max_filesize: '' + max_resolution: '' + min_resolution: '' + alt_field: true + alt_field_required: true + title_field: true + title_field_required: false + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_url.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_url.yml new file mode 100644 index 0000000..d2a74d5 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.node.article.field_url.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_url + - node.type.article + module: + - link +id: node.article.field_url +field_name: field_url +entity_type: node +bundle: article +label: 'Links' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + link_type: 17 + title: 1 +field_type: link diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_name.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_name.yml new file mode 100644 index 0000000..2d45fbd --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_name.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_user_name + module: + - user +id: user.user.field_user_name +field_name: field_user_name +entity_type: user +bundle: user +label: 'Name' +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_surname.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_surname.yml new file mode 100644 index 0000000..645ae84 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.field.user.user.field_user_surname.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_user_surname + module: + - user +id: user.user.field_user_surname +field_name: field_user_surname +entity_type: user +bundle: user +label: 'Surname' +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_description.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_description.yml new file mode 100644 index 0000000..a63e632 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_description.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - node + - text +id: node.field_article_description +field_name: field_article_description +entity_type: node +type: text_long +settings: { } +module: text +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_files.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_files.yml new file mode 100644 index 0000000..a4485a1 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_files.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - file + - node +id: node.field_article_files +field_name: field_article_files +entity_type: node +type: file +settings: + display_field: true + display_default: true + uri_scheme: public + target_type: file +module: file +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_photos.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_photos.yml new file mode 100644 index 0000000..ce3af2c --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_photos.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + module: + - file + - image + - node +id: node.field_article_photos +field_name: field_article_photos +entity_type: node +type: image +settings: + uri_scheme: public + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + target_type: file + display_field: false + display_default: false +module: image +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_type.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_type.yml new file mode 100644 index 0000000..6dd81b0 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_type.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - node + - taxonomy +id: node.field_article_type +field_name: field_article_type +entity_type: node +type: entity_reference +settings: + target_type: taxonomy_term +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_videos.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_videos.yml new file mode 100644 index 0000000..c6b9847 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_article_videos.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - file + - node +id: node.field_article_videos +field_name: field_article_videos +entity_type: node +type: file +settings: + display_field: true + display_default: true + uri_scheme: public + target_type: file +module: file +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_facebook_page.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_facebook_page.yml new file mode 100644 index 0000000..2955555 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_facebook_page.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - link + - node +id: node.field_facebook_page +field_name: field_facebook_page +entity_type: node +type: link +settings: { } +module: link +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_source.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_source.yml new file mode 100644 index 0000000..6c682a1 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_source.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - link + - node +id: node.field_source +field_name: field_source +entity_type: node +type: link +settings: { } +module: link +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_summary.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_summary.yml new file mode 100644 index 0000000..45b33a8 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_summary.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - node + - text +id: node.field_summary +field_name: field_summary +entity_type: node +type: text_long +settings: { } +module: text +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_thumbnail.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_thumbnail.yml new file mode 100644 index 0000000..66fe3f4 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_thumbnail.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + module: + - file + - image + - node +id: node.field_thumbnail +field_name: field_thumbnail +entity_type: node +type: image +settings: + uri_scheme: public + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + target_type: file + display_field: false + display_default: false +module: image +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_url.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_url.yml new file mode 100644 index 0000000..66e9816 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.node.field_url.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - link + - node +id: node.field_url +field_name: field_url +entity_type: node +type: link +settings: { } +module: link +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_name.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_name.yml new file mode 100644 index 0000000..4729510 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_name.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - user +id: user.field_user_name +field_name: field_user_name +entity_type: user +type: string +settings: + max_length: 255 + is_ascii: false + case_sensitive: false +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_surname.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_surname.yml new file mode 100644 index 0000000..af7d1d1 --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/field.storage.user.field_user_surname.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - user +id: user.field_user_surname +field_name: field_user_surname +entity_type: user +type: string +settings: + max_length: 255 + is_ascii: false + case_sensitive: false +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/node.type.article.yml b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/node.type.article.yml new file mode 100644 index 0000000..58610fc --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/modules/bundle_article_test/config/install/node.type.article.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +name: 'Article' +type: article +description: '' +help: '' +new_revision: false +preview_mode: 1 +display_submitted: false diff --git a/endpoints/endpoint_get_AAAAA/tests/src/Kernel/GetNodeTypesTest.php b/endpoints/endpoint_get_AAAAA/tests/src/Kernel/GetNodeTypesTest.php new file mode 100644 index 0000000..1ed481c --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/src/Kernel/GetNodeTypesTest.php @@ -0,0 +1,41 @@ +installEntitySchema('taxonomy_term'); + } + + public function testShouldReturnListOfArticleTypesInRequestedLanguage() { + ArticleTypeBuilder::create()->build(); + ArticleTypeBuilder::create()->withLanguageCode('el')->build(); + ArticleTypeBuilder::create()->build(); + + $request = Request::create('/type/article?languageCode=en'); + $response = $this->container->get('http_kernel')->handle($request); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('{"code":200,"languageCode":"en","types":[{"id":"1","name":"Type"},{"id":"3","name":"Type"}]}', $response->getContent()); + } + + public function testShouldReturnErrorMessageWhenLanguageCodeQueryParameterIsMissing() { + $request = Request::create('/type/article'); + $response = $this->container->get('http_kernel')->handle($request); + + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals('{"code":400,"message":"Query parameter \u0022languageCode\u0022 is mandatory and is missing."}', $response->getContent()); + } + +} diff --git a/endpoints/endpoint_get_AAAAA/tests/src/Kernel/GetNodesTest.php b/endpoints/endpoint_get_AAAAA/tests/src/Kernel/GetNodesTest.php new file mode 100644 index 0000000..2e8c2cb --- /dev/null +++ b/endpoints/endpoint_get_AAAAA/tests/src/Kernel/GetNodesTest.php @@ -0,0 +1,134 @@ +installEntitySchema('taxonomy_term'); + $this->installEntitySchema('user'); + $this->installSchema('system', ['sequences']); + $this->installEntitySchema('node'); + $this->installEntitySchema('file'); + $this->installSchema('file', ['file_usage']); + $this->installConfig('bundle_article_test'); + } + + public function testShouldReturnListOfArticles() { + ArticleTypeBuilder::create()->build(); + FileBuilder::create()->build(); + UserBuilder::create()->build(); + PageBuilder::create()->build(); + ArticleBuilder::create()->build(); + ArticleBuilder::create()->build(); + + $request = Request::create('/articles?languageCode=en'); + $response = $this->container->get('http_kernel')->handle($request); + + $secondArticleAsJson = '{"id":"2","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $thirdArticleAsJson = '{"id":"3","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('{"code":200,"languageCode":"en","articles":[' . $secondArticleAsJson . ',' . $thirdArticleAsJson . ']}', $response->getContent()); + } + + + public function testShouldReturnListOfArticlesInRequestedLanguage() { + ArticleTypeBuilder::create()->build(); + $photo = FileBuilder::create()->build(); + $video = FileBuilder::create()->withFileName('filename.mp4')->build(); + $file = FileBuilder::create()->withFileName('filename.pdf')->build(); + UserBuilder::create()->build(); + ArticleBuilder::create()->withSummary('Summary') + ->withPhotos([$photo->id(), $photo->id(), $photo->id()]) + ->withVideos([$video->id(), $video->id(), $video->id()]) + ->withFiles([$file->id(), $file->id(), $file->id()]) + ->withSources(['https://example.com/source1', 'https://example.com/source2', 'https://example.com/source3']) + ->withLinks(['https://example.com/link1', 'https://example.com/link2', 'https://example.com/link3']) + ->withFacebookPage('https://facebook.com/event1') + ->build(); + ArticleBuilder::create()->withLanguageCode('el')->build(); + ArticleBuilder::create()->build(); + + $request = Request::create('/articles?languageCode=en'); + $response = $this->container->get('http_kernel')->handle($request); + + $firstArticleAsJson = '{"id":"1","typeId":"1","typeName":"Type","title":"Title","summary":"Summary","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":["https:\/\/example.com\/filename.jpg","https:\/\/example.com\/filename.jpg","https:\/\/example.com\/filename.jpg"],"videos":["https:\/\/example.com\/filename.mp4","https:\/\/example.com\/filename.mp4","https:\/\/example.com\/filename.mp4"],"files":["https:\/\/example.com\/filename.pdf","https:\/\/example.com\/filename.pdf","https:\/\/example.com\/filename.pdf"],"sources":["https:\/\/example.com\/source1","https:\/\/example.com\/source2","https:\/\/example.com\/source3"],"links":["https:\/\/example.com\/link1","https:\/\/example.com\/link2","https:\/\/example.com\/link3"],"facebookEvent":"https:\/\/facebook.com\/event1","author":"Name Surname","date":"1234567890"}'; + $thirdArticleAsJson = '{"id":"3","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('{"code":200,"languageCode":"en","articles":[' . $firstArticleAsJson . ',' . $thirdArticleAsJson . ']}', $response->getContent()); + } + + public function testShouldReturnArticleWithRequestedIdAndLanguage() { + ArticleTypeBuilder::create()->build(); + FileBuilder::create()->build(); + UserBuilder::create()->build(); + ArticleBuilder::create()->build(); + ArticleBuilder::create()->build(); + ArticleBuilder::create()->build(); + + $request = Request::create('/articles?languageCode=en&id=2'); + $response = $this->container->get('http_kernel')->handle($request); + + $secondArticleAsJson = '{"id":"2","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('{"code":200,"languageCode":"en","articles":[' . $secondArticleAsJson . ']}', $response->getContent()); + } + + public function testShouldReturnErrorWhenArticleForRequestedIdDoesNotExist() { + $request = Request::create('/articles?languageCode=en&id=1'); + $response = $this->container->get('http_kernel')->handle($request); + + $this->assertEquals(404, $response->getStatusCode()); + $this->assertEquals('{"code":404,"message":"Article with id 1 does not exist."}', $response->getContent()); + } + + public function testShouldReturnListOfArticlesForRequestedType() { + ArticleTypeBuilder::create()->build(); + ArticleTypeBuilder::create()->build(); + FileBuilder::create()->build(); + UserBuilder::create()->build(); + ArticleBuilder::create()->build(); + ArticleBuilder::create()->withType(2)->build(); + ArticleBuilder::create()->build(); + + $request = Request::create('/articles?languageCode=en&typeId=1'); + $response = $this->container->get('http_kernel')->handle($request); + + $firstArticleAsJson = '{"id":"1","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $thirdArticleAsJson = '{"id":"3","typeId":"1","typeName":"Type","title":"Title","summary":"","description":"Description","thumbnail":"https:\/\/example.com\/filename.jpg","photos":[],"videos":[],"files":[],"sources":[],"links":[],"facebookEvent":"","author":"Name Surname","date":"1234567890"}'; + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('{"code":200,"languageCode":"en","articles":[' . $firstArticleAsJson . ',' . $thirdArticleAsJson . ']}', $response->getContent()); + } + + public function testShouldReturnErrorMessageWhenLanguageCodeQueryParameterIsMissing() { + $request = Request::create('/articles'); + $response = $this->container->get('http_kernel')->handle($request); + + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals('{"code":400,"message":"Query parameter \u0022languageCode\u0022 is mandatory and is missing."}', $response->getContent()); + } + +} diff --git a/endpoints/endpoint_post_AAAAA/dotsoft.info.yml b/endpoints/endpoint_post_AAAAA/dotsoft.info.yml new file mode 100644 index 0000000..4ff29b7 --- /dev/null +++ b/endpoints/endpoint_post_AAAAA/dotsoft.info.yml @@ -0,0 +1,7 @@ +name: 'Endpoint Post for PPPPP' +description: 'Endpoint Post for PPPPP' +package: DOTSOFT + +type: module +version: 1.0 +core_version_requirement: ^10 diff --git a/endpoints/endpoint_post_AAAAA/dotsoft.routing.yml b/endpoints/endpoint_post_AAAAA/dotsoft.routing.yml new file mode 100644 index 0000000..8ae38ac --- /dev/null +++ b/endpoints/endpoint_post_AAAAA/dotsoft.routing.yml @@ -0,0 +1,7 @@ +endpoint_post.PPPPP: + path: 'api/post/PPPPP' + defaults: + _controller: 'Drupal\dotsoft\Controller\PostController::post' + methods: [POST] + requirements: + _access: 'TRUE' diff --git a/endpoints/endpoint_post_AAAAA/src/Controller/PostController.php b/endpoints/endpoint_post_AAAAA/src/Controller/PostController.php new file mode 100644 index 0000000..cfa35f8 --- /dev/null +++ b/endpoints/endpoint_post_AAAAA/src/Controller/PostController.php @@ -0,0 +1,54 @@ +getContent(), true); + + // if (array_key_exists('user_custom_field', $requestContent)) { + // $this->createUser( + // $requestContent['email'], + // $requestContent['password'], + // $requestContent['user_custom_field'] + // ); + // } else { + // $exportDirectory = Node::create([ + // 'type' => 'user_custom_field', + // 'title' => $requestContent['export_directory_title'] + // ]); + // $exportDirectory->save(); + // $user = $this->createUser( + // $requestContent['email'], + // $requestContent['password'], + // $exportDirectory->id() + // ); + // $exportDirectory->set( + // 'uid', + // $user->id() + // ); + // $exportDirectory->save(); + // } + + return new JsonResponse('Post data accepted successfully'); + } + + // private function createUser(string $email, string $password, int $exportDirectoryId): User + // { + // $user = User::create([ + // 'name' => $email, + // 'mail' => $email, + // 'pass' => $password, + // 'roles' => ['member'], + // 'field_export_directory' => $exportDirectoryId + // ]); + // $user->save(); + // return $user; + // } +} diff --git a/endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/config/install/field.field.user.user.field_export_directory.yml b/endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/config/install/field.field.user.user.field_export_directory.yml new file mode 100644 index 0000000..5c473d5 --- /dev/null +++ b/endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/config/install/field.field.user.user.field_export_directory.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_export_directory + - node.type.export_directory + module: + - user +id: user.user.field_export_directory +field_name: field_export_directory +entity_type: user +bundle: user +label: 'Export Directory' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:node' + handler_settings: + target_bundles: + export_directory: export_directory + sort: + field: _none + direction: ASC + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/config/install/field.storage.user.field_export_directory.yml b/endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/config/install/field.storage.user.field_export_directory.yml new file mode 100644 index 0000000..960d016 --- /dev/null +++ b/endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/config/install/field.storage.user.field_export_directory.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - node + - user +id: user.field_export_directory +field_name: field_export_directory +entity_type: user +type: entity_reference +settings: + target_type: node +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/dotsoft_config_user.info.yml b/endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/dotsoft_config_user.info.yml new file mode 100644 index 0000000..c34bfae --- /dev/null +++ b/endpoints/endpoint_post_AAAAA/tests/modules/dotsoft_config_user/dotsoft_config_user.info.yml @@ -0,0 +1,7 @@ +name: '' +description: '' +package: '' + +type: module +version: 1.0 +core_version_requirement: 9.x diff --git a/endpoints/endpoint_post_AAAAA/tests/src/Kernel/PostMemberRegistrationTest.php b/endpoints/endpoint_post_AAAAA/tests/src/Kernel/PostMemberRegistrationTest.php new file mode 100644 index 0000000..6511cc4 --- /dev/null +++ b/endpoints/endpoint_post_AAAAA/tests/src/Kernel/PostMemberRegistrationTest.php @@ -0,0 +1,80 @@ +installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installSchema('node', ['node_access']); + $this->installConfig([ + 'dotsoft_config_user' + ]); + } + + /** @test */ + public function member_account_is_created_when_export_directory_exists(): void + { + $exportDirectory = $this->createNode(['type' => 'export_directory']); + $uri = Url::fromRoute('dotsoft.users.register')->toString(); + $request = Request::create($uri, 'POST', [], [], [], [], '{"email":"johndoe@example.com","password":"12345","export_directory":' . $exportDirectory->id() . '}'); + + $response = $this->container->get('http_kernel')->handle($request); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertEquals('"Member account is created successfully"', $response->getContent()); + $account = current(\Drupal::entityTypeManager() + ->getStorage('user') + ->loadByProperties(['mail' => 'johndoe@example.com'])); + $this->assertNotEmpty($account); + $this->assertEquals(['member'], $account->getRoles(true)); + $this->assertNotEmpty($account->get('field_export_directory')->getValue()); + $this->assertEquals($exportDirectory->id(), $account->get('field_export_directory')->getValue()[0]['target_id']); + } + + /** @test */ + public function member_account_is_created_when_export_directory_does_not_exist(): void + { + $uri = Url::fromRoute('dotsoft.users.register')->toString(); + $request = Request::create($uri, 'POST', [], [], [], [], '{"email":"johndoe@example.com","password":"12345","export_directory_title":"DOTSOFT"}'); + + $response = $this->container->get('http_kernel')->handle($request); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode(), $response->getContent()); + $this->assertEquals('"Member account is created successfully"', $response->getContent()); + $account = current(\Drupal::entityTypeManager() + ->getStorage('user') + ->loadByProperties(['mail' => 'johndoe@example.com'])); + $this->assertNotEmpty($account); + $this->assertEquals(['member'], $account->getRoles(true)); + $exportDirectory = current(\Drupal::entityTypeManager() + ->getStorage('node') + ->loadByProperties([ + 'type' => 'export_directory', + 'title' => 'DOTSOFT' + ])); + $this->assertNotEmpty($exportDirectory); + $this->assertEquals($account->id(), $exportDirectory->get('uid')->getValue()[0]['target_id']); + $this->assertNotEmpty($account->get('field_export_directory')->getValue()); + $this->assertEquals($exportDirectory->id(), $account->get('field_export_directory')->getValue()[0]['target_id']); + } +} diff --git a/toolbox/copy.sh b/toolbox/copy.sh index 5248ccb..f36d912 100755 --- a/toolbox/copy.sh +++ b/toolbox/copy.sh @@ -1,14 +1,14 @@ #!/bin/bash -RED="\e[0;31m" -GRN="\e[0;32m" -BLU="\e[0;34m" -WHT="\e[0;97m" -BOLDRED="\e[1;31m" -BOLDGRN="\e[1;32m" -BOLDBLU="\e[1;34m" -BOLDWHT="\e[1;97m" -END="\e[00m" +R="\e[0;31m" +G="\e[0;32m" +B="\e[0;34m" +W="\e[0;97m" +BR="\e[1;31m" +BG="\e[1;32m" +BB="\e[1;34m" +BW="\e[1;97m" +E="\e[00m" DBUSER="root" DBPASS="1234" @@ -16,12 +16,9 @@ DBPASS="1234" SRC=$1 DST=$2 -function ok() { - echo -e "${BOLDGRN} [ OK ]${END}" -} - -function fail() { - echo -e "${BOLDRED} [FAIL]${END}" +function log() { + [ $1 == 0 ] && echo -e " ${G}[ OK ]${E}"; return + echo -e " ${R}[FAIL]${E}" exit } @@ -29,42 +26,45 @@ echo read -p "Note for destination $DST.log: " NOTE echo + echo -ne " 🍄 Copy files from $SRC to $DST" cp -rp $SRC $DST [ -f "$SRC.copy.log" ] && cp $SRC.copy.log $DST.copy.log echo >> $DST.copy.log echo "`date +'%d/%m/%Y %H:%M:%S'` Copy from $SRC to $DST" >> $DST.copy.log echo $NOTE >> $DST.copy.log -if [ $? = 0 ]; then ok; else fail; fi +log $? echo -ne " 🐬 Export old database $SRC" mysqldump -u${DBUSER} -p${DBPASS} $SRC > $SRC.sql -if [ $? = 0 ]; then ok; else fail; fi +log $? echo -ne " 🐬 Create new database $DST" mysql -u${DBUSER} -p${DBPASS} -e "create database $DST" -if [ $? = 0 ]; then ok; else fail; fi +log $? echo -ne " 🐬 Create database user $DST with password '1234'" mysql -uroot -p1234 -e "CREATE USER $DST@localhost IDENTIFIED BY \"1234\"" mysql -uroot -p1234 -e "GRANT ALL ON $DST.* TO $DST@localhost" mysql -uroot -p1234 -e "FLUSH PRIVILEGES" -if [ $? = 0 ]; then ok; else fail; fi +log $? echo -ne " 🐬 Import old database $SRC to new $DST" mysql -u${DBUSER} -p${DBPASS} $DST < $SRC.sql -if [ $? = 0 ]; then ok; else fail; fi +log $? echo -ne " 🍒 Drupal settings.php" cd $DST + if [ -f "sites/default/settings.php" ] then CONFIG="sites/default/settings.php" else CONFIG="web/sites/default/settings.php" fi + sed -i "s/$SRC/$DST/g" $CONFIG -if [ $? = 0 ]; then ok; else fail; fi +log $? # echo -ne " 🍒 link html to $DST" # unlink /var/www/html diff --git a/toolbox/delete.sh b/toolbox/delete.sh index c9a16b7..e9f18e1 100755 --- a/toolbox/delete.sh +++ b/toolbox/delete.sh @@ -4,6 +4,7 @@ df -h rm -rf $1 rm -r $1.sql +rm -r $1.info rm -r $1.copy.log rm -r $1.composer.log diff --git a/toolbox/site.sh b/toolbox/site.sh index eaf94d6..f2d8da2 100755 --- a/toolbox/site.sh +++ b/toolbox/site.sh @@ -52,7 +52,7 @@ log $? echo -ne " 🐬 Drush clear cache " cd $BASE_PATH/sites/$1 -scl enable php82 bash +# scl enable php82 bash if [ -f ./vendor/drush/drush/drush ] then @@ -62,7 +62,8 @@ then echo else echo "using global drush" - drush cr &> /dev/null + # drush cr &> /dev/null + drush cr log $? echo fi diff --git a/toolbox/site0.sh b/toolbox/site0.sh index 5d73da7..b16560b 100755 --- a/toolbox/site0.sh +++ b/toolbox/site0.sh @@ -1,3 +1,3 @@ #!/bin/bash -/root/Dev-Enviroment/toolbox/site.sh $1 0 +/root/dev/toolbox/site.sh $1 0 diff --git a/toolbox/site1.sh b/toolbox/site1.sh index 90b9f47..dc64011 100755 --- a/toolbox/site1.sh +++ b/toolbox/site1.sh @@ -1,3 +1,3 @@ #!/bin/bash -/root/Dev-Enviroment/toolbox/site.sh $1 1 +/root/dev/toolbox/site.sh $1 1 diff --git a/toolbox/site2.sh b/toolbox/site2.sh index f1b1719..87122ab 100755 --- a/toolbox/site2.sh +++ b/toolbox/site2.sh @@ -1,3 +1,3 @@ #!/bin/bash -/root/Dev-Enviroment/toolbox/site.sh $1 2 +/root/dev/toolbox/site.sh $1 2 diff --git a/toolbox/site3.sh b/toolbox/site3.sh index 898ecb6..9c05937 100755 --- a/toolbox/site3.sh +++ b/toolbox/site3.sh @@ -1,3 +1,3 @@ #!/bin/bash -/root/Dev-Enviroment/toolbox/site.sh $1 3 +/root/dev/toolbox/site.sh $1 3 diff --git a/toolbox/site4.sh b/toolbox/site4.sh index dfe845b..3d05abf 100755 --- a/toolbox/site4.sh +++ b/toolbox/site4.sh @@ -1,3 +1,3 @@ #!/bin/bash -/root/Dev-Enviroment/toolbox/site.sh $1 4 +/root/dev/toolbox/site.sh $1 4 diff --git a/toolbox/site6.sh b/toolbox/site6.sh index 2606ba1..6f25a34 100755 --- a/toolbox/site6.sh +++ b/toolbox/site6.sh @@ -1,3 +1,3 @@ #!/bin/bash -/root/Dev-Enviroment/toolbox/site.sh $1 6 +/root/dev/toolbox/site.sh $1 6 diff --git a/toolbox/site7.sh b/toolbox/site7.sh index db609cf..522b998 100755 --- a/toolbox/site7.sh +++ b/toolbox/site7.sh @@ -1,3 +1,3 @@ #!/bin/bash -/root/Dev-Enviroment/toolbox/site.sh $1 7 +/root/dev/toolbox/site.sh $1 7 diff --git a/toolbox/site8.sh b/toolbox/site8.sh index fff5cd6..8db4ecd 100755 --- a/toolbox/site8.sh +++ b/toolbox/site8.sh @@ -1,3 +1,3 @@ #!/bin/bash -/root/Dev-Enviroment/toolbox/site.sh $1 8 +/root/dev/toolbox/site.sh $1 8 diff --git a/toolbox/site9.sh b/toolbox/site9.sh index 5aab539..9b18acc 100755 --- a/toolbox/site9.sh +++ b/toolbox/site9.sh @@ -1,3 +1,3 @@ #!/bin/bash -/root/Dev-Enviroment/toolbox/site.sh $1 9 +/root/dev/toolbox/site.sh $1 9 From 71e1e936a57fec71a7b2d42d1cd4ede44966408d Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Sun, 31 Dec 2023 00:07:02 +0200 Subject: [PATCH 29/31] config --- vim/config/nvim | 1 + 1 file changed, 1 insertion(+) create mode 160000 vim/config/nvim diff --git a/vim/config/nvim b/vim/config/nvim new file mode 160000 index 0000000..f92846d --- /dev/null +++ b/vim/config/nvim @@ -0,0 +1 @@ +Subproject commit f92846d0e836303e8bf226f60d37d68796e10cbf From 6c8259223e983637df1aa6d553d84d7569afaee8 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Sun, 31 Dec 2023 00:09:06 +0200 Subject: [PATCH 30/31] config --- vim/config/nvim | 1 - 1 file changed, 1 deletion(-) delete mode 160000 vim/config/nvim diff --git a/vim/config/nvim b/vim/config/nvim deleted file mode 160000 index f92846d..0000000 --- a/vim/config/nvim +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f92846d0e836303e8bf226f60d37d68796e10cbf From a7db6ff2c9d7736af47a49827cec7764c7a3e168 Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Sun, 31 Dec 2023 00:10:56 +0200 Subject: [PATCH 31/31] config --- vim/config/nvim/LICENSE | 674 ++++++++++++++++++ vim/config/nvim/config.ld | 7 + vim/config/nvim/init.lua | 33 + vim/config/nvim/lua/astronvim/autocmds.lua | 295 ++++++++ vim/config/nvim/lua/astronvim/bootstrap.lua | 132 ++++ vim/config/nvim/lua/astronvim/health.lua | 75 ++ .../nvim/lua/astronvim/icons/nerd_font.lua | 60 ++ vim/config/nvim/lua/astronvim/icons/text.lua | 41 ++ vim/config/nvim/lua/astronvim/lazy.lua | 55 ++ vim/config/nvim/lua/astronvim/mappings.lua | 452 ++++++++++++ vim/config/nvim/lua/astronvim/options.lua | 76 ++ .../nvim/lua/astronvim/utils/buffer.lua | 259 +++++++ vim/config/nvim/lua/astronvim/utils/ffi.lua | 20 + vim/config/nvim/lua/astronvim/utils/git.lua | 215 ++++++ vim/config/nvim/lua/astronvim/utils/init.lua | 342 +++++++++ vim/config/nvim/lua/astronvim/utils/lsp.lua | 431 +++++++++++ vim/config/nvim/lua/astronvim/utils/mason.lua | 119 ++++ .../nvim/lua/astronvim/utils/status.lua | 10 + .../lua/astronvim/utils/status/component.lua | 435 +++++++++++ .../lua/astronvim/utils/status/condition.lua | 138 ++++ .../nvim/lua/astronvim/utils/status/env.lua | 143 ++++ .../lua/astronvim/utils/status/heirline.lua | 115 +++ .../nvim/lua/astronvim/utils/status/hl.lua | 76 ++ .../nvim/lua/astronvim/utils/status/init.lua | 155 ++++ .../lua/astronvim/utils/status/provider.lua | 521 ++++++++++++++ .../nvim/lua/astronvim/utils/status/utils.lua | 202 ++++++ vim/config/nvim/lua/astronvim/utils/ui.lua | 230 ++++++ .../nvim/lua/astronvim/utils/updater.lua | 339 +++++++++ vim/config/nvim/lua/lazy_snapshot.lua | 54 ++ vim/config/nvim/lua/plugins/alpha.lua | 38 + vim/config/nvim/lua/plugins/cmp.lua | 112 +++ vim/config/nvim/lua/plugins/configs/alpha.lua | 17 + .../nvim/lua/plugins/configs/cmp-dap.lua | 7 + .../nvim/lua/plugins/configs/guess-indent.lua | 4 + .../nvim/lua/plugins/configs/heirline.lua | 117 +++ .../nvim/lua/plugins/configs/lspconfig.lua | 56 ++ .../nvim/lua/plugins/configs/lspkind.lua | 1 + .../nvim/lua/plugins/configs/luasnip.lua | 4 + .../lua/plugins/configs/mason-lspconfig.lua | 4 + .../lua/plugins/configs/mason-null-ls.lua | 5 + .../lua/plugins/configs/mason-nvim-dap.lua | 5 + vim/config/nvim/lua/plugins/configs/mason.lua | 26 + .../nvim/lua/plugins/configs/notify.lua | 5 + .../lua/plugins/configs/nvim-autopairs.lua | 10 + .../nvim/lua/plugins/configs/nvim-dap-ui.lua | 7 + .../lua/plugins/configs/nvim-treesitter.lua | 1 + .../lua/plugins/configs/nvim-web-devicons.lua | 5 + .../nvim/lua/plugins/configs/telescope.lua | 9 + .../nvim/lua/plugins/configs/which-key.lua | 4 + vim/config/nvim/lua/plugins/core.lua | 121 ++++ vim/config/nvim/lua/plugins/dap.lua | 23 + vim/config/nvim/lua/plugins/git.lua | 17 + vim/config/nvim/lua/plugins/heirline.lua | 87 +++ vim/config/nvim/lua/plugins/lsp.lua | 97 +++ vim/config/nvim/lua/plugins/mason.lua | 25 + vim/config/nvim/lua/plugins/neo-tree.lua | 143 ++++ vim/config/nvim/lua/plugins/telescope.lua | 37 + vim/config/nvim/lua/plugins/treesitter.lua | 95 +++ vim/config/nvim/lua/plugins/ui.lua | 129 ++++ .../lua/resession/extensions/astronvim.lua | 44 ++ vim/config/nvim/lua/user/init.lua | 307 ++++++++ vim/config/nvim/lua/user/polish.lua | 7 + 62 files changed, 7273 insertions(+) create mode 100644 vim/config/nvim/LICENSE create mode 100644 vim/config/nvim/config.ld create mode 100644 vim/config/nvim/init.lua create mode 100644 vim/config/nvim/lua/astronvim/autocmds.lua create mode 100644 vim/config/nvim/lua/astronvim/bootstrap.lua create mode 100644 vim/config/nvim/lua/astronvim/health.lua create mode 100644 vim/config/nvim/lua/astronvim/icons/nerd_font.lua create mode 100644 vim/config/nvim/lua/astronvim/icons/text.lua create mode 100644 vim/config/nvim/lua/astronvim/lazy.lua create mode 100644 vim/config/nvim/lua/astronvim/mappings.lua create mode 100644 vim/config/nvim/lua/astronvim/options.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/buffer.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/ffi.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/git.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/init.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/lsp.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/mason.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/status.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/status/component.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/status/condition.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/status/env.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/status/heirline.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/status/hl.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/status/init.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/status/provider.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/status/utils.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/ui.lua create mode 100644 vim/config/nvim/lua/astronvim/utils/updater.lua create mode 100644 vim/config/nvim/lua/lazy_snapshot.lua create mode 100644 vim/config/nvim/lua/plugins/alpha.lua create mode 100644 vim/config/nvim/lua/plugins/cmp.lua create mode 100644 vim/config/nvim/lua/plugins/configs/alpha.lua create mode 100644 vim/config/nvim/lua/plugins/configs/cmp-dap.lua create mode 100644 vim/config/nvim/lua/plugins/configs/guess-indent.lua create mode 100644 vim/config/nvim/lua/plugins/configs/heirline.lua create mode 100644 vim/config/nvim/lua/plugins/configs/lspconfig.lua create mode 100644 vim/config/nvim/lua/plugins/configs/lspkind.lua create mode 100644 vim/config/nvim/lua/plugins/configs/luasnip.lua create mode 100644 vim/config/nvim/lua/plugins/configs/mason-lspconfig.lua create mode 100644 vim/config/nvim/lua/plugins/configs/mason-null-ls.lua create mode 100644 vim/config/nvim/lua/plugins/configs/mason-nvim-dap.lua create mode 100644 vim/config/nvim/lua/plugins/configs/mason.lua create mode 100644 vim/config/nvim/lua/plugins/configs/notify.lua create mode 100644 vim/config/nvim/lua/plugins/configs/nvim-autopairs.lua create mode 100644 vim/config/nvim/lua/plugins/configs/nvim-dap-ui.lua create mode 100644 vim/config/nvim/lua/plugins/configs/nvim-treesitter.lua create mode 100644 vim/config/nvim/lua/plugins/configs/nvim-web-devicons.lua create mode 100644 vim/config/nvim/lua/plugins/configs/telescope.lua create mode 100644 vim/config/nvim/lua/plugins/configs/which-key.lua create mode 100644 vim/config/nvim/lua/plugins/core.lua create mode 100644 vim/config/nvim/lua/plugins/dap.lua create mode 100644 vim/config/nvim/lua/plugins/git.lua create mode 100644 vim/config/nvim/lua/plugins/heirline.lua create mode 100644 vim/config/nvim/lua/plugins/lsp.lua create mode 100644 vim/config/nvim/lua/plugins/mason.lua create mode 100644 vim/config/nvim/lua/plugins/neo-tree.lua create mode 100644 vim/config/nvim/lua/plugins/telescope.lua create mode 100644 vim/config/nvim/lua/plugins/treesitter.lua create mode 100644 vim/config/nvim/lua/plugins/ui.lua create mode 100644 vim/config/nvim/lua/resession/extensions/astronvim.lua create mode 100644 vim/config/nvim/lua/user/init.lua create mode 100644 vim/config/nvim/lua/user/polish.lua diff --git a/vim/config/nvim/LICENSE b/vim/config/nvim/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/vim/config/nvim/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/vim/config/nvim/config.ld b/vim/config/nvim/config.ld new file mode 100644 index 0000000..61784df --- /dev/null +++ b/vim/config/nvim/config.ld @@ -0,0 +1,7 @@ +project='AstroNvim' +title='AstroNvim API' +description='Documentation of AstroNvim\'s core API' +format = 'markdown' +file='lua' +dir='docs' +no_space_before_args=true diff --git a/vim/config/nvim/init.lua b/vim/config/nvim/init.lua new file mode 100644 index 0000000..c248d7d --- /dev/null +++ b/vim/config/nvim/init.lua @@ -0,0 +1,33 @@ +if vim.loader and vim.fn.has "nvim-0.9.1" == 1 then vim.loader.enable() end + +for _, source in ipairs { + "astronvim.bootstrap", + "astronvim.options", + "astronvim.lazy", + "astronvim.autocmds", + "astronvim.mappings", +} do + local status_ok, fault = pcall(require, source) + if not status_ok then vim.api.nvim_err_writeln("Failed to load " .. source .. "\n\n" .. fault) end +end + +if astronvim.default_colorscheme then + if not pcall(vim.cmd.colorscheme, astronvim.default_colorscheme) then + require("astronvim.utils").notify( + ("Error setting up colorscheme: `%s`"):format(astronvim.default_colorscheme), + vim.log.levels.ERROR + ) + end +end + +require("astronvim.utils").conditional_func(astronvim.user_opts("polish", nil, false), true) + +require('dap').configurations.php = { + { + type = 'php', + request = 'launch', + name = 'Listen for Xdebug', + port = 9003, + }, +} + diff --git a/vim/config/nvim/lua/astronvim/autocmds.lua b/vim/config/nvim/lua/astronvim/autocmds.lua new file mode 100644 index 0000000..9866247 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/autocmds.lua @@ -0,0 +1,295 @@ +local augroup = vim.api.nvim_create_augroup +local autocmd = vim.api.nvim_create_autocmd +local cmd = vim.api.nvim_create_user_command +local namespace = vim.api.nvim_create_namespace + +local utils = require "astronvim.utils" +local is_available = utils.is_available +local astroevent = utils.event + +vim.on_key(function(char) + if vim.fn.mode() == "n" then + local new_hlsearch = vim.tbl_contains({ "", "n", "N", "*", "#", "?", "/" }, vim.fn.keytrans(char)) + if vim.opt.hlsearch:get() ~= new_hlsearch then vim.opt.hlsearch = new_hlsearch end + end +end, namespace "auto_hlsearch") + +autocmd("BufReadPre", { + desc = "Disable certain functionality on very large files", + group = augroup("large_buf", { clear = true }), + callback = function(args) + local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(args.buf)) + vim.b[args.buf].large_buf = (ok and stats and stats.size > vim.g.max_file.size) + or vim.api.nvim_buf_line_count(args.buf) > vim.g.max_file.lines + end, +}) + +local bufferline_group = augroup("bufferline", { clear = true }) +autocmd({ "BufAdd", "BufEnter", "TabNewEntered" }, { + desc = "Update buffers when adding new buffers", + group = bufferline_group, + callback = function(args) + local buf_utils = require "astronvim.utils.buffer" + if not vim.t.bufs then vim.t.bufs = {} end + if not buf_utils.is_valid(args.buf) then return end + if args.buf ~= buf_utils.current_buf then + buf_utils.last_buf = buf_utils.is_valid(buf_utils.current_buf) and buf_utils.current_buf or nil + buf_utils.current_buf = args.buf + end + local bufs = vim.t.bufs + if not vim.tbl_contains(bufs, args.buf) then + table.insert(bufs, args.buf) + vim.t.bufs = bufs + end + vim.t.bufs = vim.tbl_filter(buf_utils.is_valid, vim.t.bufs) + astroevent "BufsUpdated" + end, +}) +autocmd("BufDelete", { + desc = "Update buffers when deleting buffers", + group = bufferline_group, + callback = function(args) + local removed + for _, tab in ipairs(vim.api.nvim_list_tabpages()) do + local bufs = vim.t[tab].bufs + if bufs then + for i, bufnr in ipairs(bufs) do + if bufnr == args.buf then + removed = true + table.remove(bufs, i) + vim.t[tab].bufs = bufs + break + end + end + end + end + vim.t.bufs = vim.tbl_filter(require("astronvim.utils.buffer").is_valid, vim.t.bufs) + if removed then astroevent "BufsUpdated" end + vim.cmd.redrawtabline() + end, +}) + +autocmd({ "VimEnter", "FileType", "BufEnter", "WinEnter" }, { + desc = "URL Highlighting", + group = augroup("highlighturl", { clear = true }), + callback = function() utils.set_url_match() end, +}) + +local view_group = augroup("auto_view", { clear = true }) +autocmd({ "BufWinLeave", "BufWritePost", "WinLeave" }, { + desc = "Save view with mkview for real files", + group = view_group, + callback = function(event) + if vim.b[event.buf].view_activated then vim.cmd.mkview { mods = { emsg_silent = true } } end + end, +}) +autocmd("BufWinEnter", { + desc = "Try to load file view if available and enable view saving for real files", + group = view_group, + callback = function(event) + if not vim.b[event.buf].view_activated then + local filetype = vim.api.nvim_get_option_value("filetype", { buf = event.buf }) + local buftype = vim.api.nvim_get_option_value("buftype", { buf = event.buf }) + local ignore_filetypes = { "gitcommit", "gitrebase", "svg", "hgcommit" } + if buftype == "" and filetype and filetype ~= "" and not vim.tbl_contains(ignore_filetypes, filetype) then + vim.b[event.buf].view_activated = true + vim.cmd.loadview { mods = { emsg_silent = true } } + end + end + end, +}) + +autocmd("BufWinEnter", { + desc = "Make q close help, man, quickfix, dap floats", + group = augroup("q_close_windows", { clear = true }), + callback = function(event) + local buftype = vim.api.nvim_get_option_value("buftype", { buf = event.buf }) + if vim.tbl_contains({ "help", "nofile", "quickfix" }, buftype) then + vim.keymap.set("n", "q", "close", { + desc = "Close window", + buffer = event.buf, + silent = true, + nowait = true, + }) + end + end, +}) + +autocmd("TextYankPost", { + desc = "Highlight yanked text", + group = augroup("highlightyank", { clear = true }), + pattern = "*", + callback = function() vim.highlight.on_yank() end, +}) + +autocmd("FileType", { + desc = "Unlist quickfist buffers", + group = augroup("unlist_quickfist", { clear = true }), + pattern = "qf", + callback = function() vim.opt_local.buflisted = false end, +}) + +autocmd("BufEnter", { + desc = "Quit AstroNvim if more than one window is open and only sidebar windows are list", + group = augroup("auto_quit", { clear = true }), + callback = function() + local wins = vim.api.nvim_tabpage_list_wins(0) + -- Both neo-tree and aerial will auto-quit if there is only a single window left + if #wins <= 1 then return end + local sidebar_fts = { aerial = true, ["neo-tree"] = true } + for _, winid in ipairs(wins) do + if vim.api.nvim_win_is_valid(winid) then + local bufnr = vim.api.nvim_win_get_buf(winid) + local filetype = vim.api.nvim_get_option_value("filetype", { buf = bufnr }) + -- If any visible windows are not sidebars, early return + if not sidebar_fts[filetype] then + return + -- If the visible window is a sidebar + else + -- only count filetypes once, so remove a found sidebar from the detection + sidebar_fts[filetype] = nil + end + end + end + if #vim.api.nvim_list_tabpages() > 1 then + vim.cmd.tabclose() + else + vim.cmd.qall() + end + end, +}) + +if is_available "alpha-nvim" then + autocmd({ "User", "BufEnter" }, { + desc = "Disable status and tablines for alpha", + group = augroup("alpha_settings", { clear = true }), + callback = function(event) + if + ( + (event.event == "User" and event.file == "AlphaReady") + or (event.event == "BufEnter" and vim.api.nvim_get_option_value("filetype", { buf = event.buf }) == "alpha") + ) and not vim.g.before_alpha + then + vim.g.before_alpha = { showtabline = vim.opt.showtabline:get(), laststatus = vim.opt.laststatus:get() } + vim.opt.showtabline, vim.opt.laststatus = 0, 0 + elseif + vim.g.before_alpha + and event.event == "BufEnter" + and vim.api.nvim_get_option_value("buftype", { buf = event.buf }) ~= "nofile" + then + vim.opt.laststatus, vim.opt.showtabline = vim.g.before_alpha.laststatus, vim.g.before_alpha.showtabline + vim.g.before_alpha = nil + end + end, + }) + autocmd("VimEnter", { + desc = "Start Alpha when vim is opened with no arguments", + group = augroup("alpha_autostart", { clear = true }), + callback = function() + local should_skip = false + if vim.fn.argc() > 0 or vim.fn.line2byte(vim.fn.line "$") ~= -1 or not vim.o.modifiable then + should_skip = true + else + for _, arg in pairs(vim.v.argv) do + if arg == "-b" or arg == "-c" or vim.startswith(arg, "+") or arg == "-S" then + should_skip = true + break + end + end + end + if not should_skip then + require("alpha").start(true, require("alpha").default_config) + vim.schedule(function() vim.cmd.doautocmd "FileType" end) + end + end, + }) +end + +if is_available "resession.nvim" then + autocmd("VimLeavePre", { + desc = "Save session on close", + group = augroup("resession_auto_save", { clear = true }), + callback = function() + local buf_utils = require "astronvim.utils.buffer" + local autosave = buf_utils.sessions.autosave + if autosave and buf_utils.is_valid_session() then + local save = require("resession").save + if autosave.last then save("Last Session", { notify = false }) end + if autosave.cwd then save(vim.fn.getcwd(), { dir = "dirsession", notify = false }) end + end + end, + }) +end + +if is_available "neo-tree.nvim" then + autocmd("BufEnter", { + desc = "Open Neo-Tree on startup with directory", + group = augroup("neotree_start", { clear = true }), + callback = function() + if package.loaded["neo-tree"] then + vim.api.nvim_del_augroup_by_name "neotree_start" + else + local stats = (vim.uv or vim.loop).fs_stat(vim.api.nvim_buf_get_name(0)) -- TODO: REMOVE vim.loop WHEN DROPPING SUPPORT FOR Neovim v0.9 + if stats and stats.type == "directory" then + vim.api.nvim_del_augroup_by_name "neotree_start" + require "neo-tree" + end + end + end, + }) + autocmd("TermClose", { + pattern = "*lazygit", + desc = "Refresh Neo-Tree git when closing lazygit", + group = augroup("neotree_git_refresh", { clear = true }), + callback = function() + if package.loaded["neo-tree.sources.git_status"] then require("neo-tree.sources.git_status").refresh() end + end, + }) +end + +autocmd({ "VimEnter", "ColorScheme" }, { + desc = "Load custom highlights from user configuration", + group = augroup("astronvim_highlights", { clear = true }), + callback = function() + if vim.g.colors_name then + for _, module in ipairs { "init", vim.g.colors_name } do + for group, spec in pairs(astronvim.user_opts("highlights." .. module)) do + vim.api.nvim_set_hl(0, group, spec) + end + end + end + astroevent "ColorScheme" + end, +}) + +autocmd({ "BufReadPost", "BufNewFile", "BufWritePost" }, { + desc = "AstroNvim user events for file detection (AstroFile and AstroGitFile)", + group = augroup("file_user_events", { clear = true }), + callback = function(args) + if not (vim.fn.expand "%" == "" or vim.api.nvim_get_option_value("buftype", { buf = args.buf }) == "nofile") then + astroevent "File" + if + require("astronvim.utils.git").file_worktree() + or utils.cmd({ "git", "-C", vim.fn.expand "%:p:h", "rev-parse" }, false) + then + astroevent "GitFile" + vim.api.nvim_del_augroup_by_name "file_user_events" + end + end + end, +}) + +cmd( + "AstroChangelog", + function() require("astronvim.utils.updater").changelog() end, + { desc = "Check AstroNvim Changelog" } +) +cmd( + "AstroUpdatePackages", + function() require("astronvim.utils.updater").update_packages() end, + { desc = "Update Plugins and Mason" } +) +cmd("AstroRollback", function() require("astronvim.utils.updater").rollback() end, { desc = "Rollback AstroNvim" }) +cmd("AstroUpdate", function() require("astronvim.utils.updater").update() end, { desc = "Update AstroNvim" }) +cmd("AstroVersion", function() require("astronvim.utils.updater").version() end, { desc = "Check AstroNvim Version" }) +cmd("AstroReload", function() require("astronvim.utils").reload() end, { desc = "Reload AstroNvim (Experimental)" }) diff --git a/vim/config/nvim/lua/astronvim/bootstrap.lua b/vim/config/nvim/lua/astronvim/bootstrap.lua new file mode 100644 index 0000000..c5a9f47 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/bootstrap.lua @@ -0,0 +1,132 @@ +--- ### AstroNvim Core Bootstrap +-- +-- This module simply sets up the global `astronvim` module. +-- This is automatically loaded and should not be resourced, everything is accessible through the global `astronvim` variable. +-- +-- @module astronvim.bootstrap +-- @copyright 2022 +-- @license GNU General Public License v3.0 + +_G.astronvim = {} + +--- installation details from external installers +astronvim.install = _G["astronvim_installation"] or { home = vim.fn.stdpath "config" } +astronvim.supported_configs = { astronvim.install.home } +--- external astronvim configuration folder +astronvim.install.config = vim.fn.stdpath("config"):gsub("[^/\\]+$", "astronvim") +-- check if they are the same, protects against NVIM_APPNAME use for isolated install +if astronvim.install.home ~= astronvim.install.config then + vim.opt.rtp:append(astronvim.install.config) + --- supported astronvim user conifg folders + table.insert(astronvim.supported_configs, astronvim.install.config) +end + +--- Looks to see if a module path references a lua file in a configuration folder and tries to load it. If there is an error loading the file, write an error and continue +---@param module string The module path to try and load +---@return table|nil # The loaded module if successful or nil +local function load_module_file(module) + -- placeholder for final return value + local found_file = nil + -- search through each of the supported configuration locations + for _, config_path in ipairs(astronvim.supported_configs) do + -- convert the module path to a file path (example user.init -> user/init.lua) + local module_path = config_path .. "/lua/" .. module:gsub("%.", "/") .. ".lua" + -- check if there is a readable file, if so, set it as found + if vim.fn.filereadable(module_path) == 1 then found_file = module_path end + end + -- if we found a readable lua file, try to load it + local out = nil + if found_file then + -- try to load the file + local status_ok, loaded_module = pcall(require, module) + -- if successful at loading, set the return variable + if status_ok then + out = loaded_module + -- if unsuccessful, throw an error + else + vim.api.nvim_err_writeln("Error loading file: " .. found_file .. "\n\n" .. loaded_module) + end + end + -- return the loaded module or nil if no file found + return out +end + +--- Main configuration engine logic for extending a default configuration table with either a function override or a table to merge into the default option +-- @param overrides the override definition, either a table or a function that takes a single parameter of the original table +-- @param default the default configuration table +-- @param extend boolean value to either extend the default or simply overwrite it if an override is provided +-- @return the new configuration table +local function func_or_extend(overrides, default, extend) + -- if we want to extend the default with the provided override + if extend then + -- if the override is a table, use vim.tbl_deep_extend + if type(overrides) == "table" then + local opts = overrides or {} + default = default and vim.tbl_deep_extend("force", default, opts) or opts + -- if the override is a function, call it with the default and overwrite default with the return value + elseif type(overrides) == "function" then + default = overrides(default) + end + -- if extend is set to false and we have a provided override, simply override the default + elseif overrides ~= nil then + default = overrides + end + -- return the modified default table + return default +end + +--- user settings from the base `user/init.lua` file +local user_settings = load_module_file "user.init" + +--- Search the user settings (user/init.lua table) for a table with a module like path string +-- @param module the module path like string to look up in the user settings table +-- @return the value of the table entry if exists or nil +local function user_setting_table(module) + -- get the user settings table + local settings = user_settings or {} + -- iterate over the path string split by '.' to look up the table value + for tbl in string.gmatch(module, "([^%.]+)") do + settings = settings[tbl] + -- if key doesn't exist, keep the nil value and stop searching + if settings == nil then break end + end + -- return the found settings + return settings +end + +--- User configuration entry point to override the default options of a configuration table with a user configuration file or table in the user/init.lua user settings +---@param module string The module path of the override setting +---@param default? any The default value that will be overridden +---@param extend? boolean # Whether extend the default settings or overwrite them with the user settings entirely (default: true) +---@return any # The new configuration settings with the user overrides applied +function astronvim.user_opts(module, default, extend) + -- default to extend = true + if extend == nil then extend = true end + -- if no default table is provided set it to an empty table + if default == nil then default = {} end + -- try to load a module file if it exists + local user_module_settings = load_module_file("user." .. module) + -- if no user module file is found, try to load an override from the user settings table from user/init.lua + if user_module_settings == nil then user_module_settings = user_setting_table(module) end + -- if a user override was found call the configuration engine + if user_module_settings ~= nil then default = func_or_extend(user_module_settings, default, extend) end + -- return the final configuration table with any overrides applied + return default +end + +--- Updater settings overridden with any user provided configuration +astronvim.updater = { + options = astronvim.user_opts("updater", { remote = "origin", channel = "stable" }), + snapshot = { module = "lazy_snapshot", path = vim.fn.stdpath "config" .. "/lua/lazy_snapshot.lua" }, + rollback_file = vim.fn.stdpath "cache" .. "/astronvim_rollback.lua", +} +local options = astronvim.updater.options +if astronvim.install.is_stable ~= nil then options.channel = astronvim.install.is_stable and "stable" or "nightly" end +if options.pin_plugins == nil then options.pin_plugins = options.channel == "stable" end + +--- table of user created terminals +astronvim.user_terminals = {} +--- table of language servers to ignore the setup of, configured through lsp.skip_setup in the user configuration +astronvim.lsp = { skip_setup = astronvim.user_opts("lsp.skip_setup", {}), progress = {} } +--- the default colorscheme to apply on startup +astronvim.default_colorscheme = astronvim.user_opts("colorscheme", "astrotheme", false) diff --git a/vim/config/nvim/lua/astronvim/health.lua b/vim/config/nvim/lua/astronvim/health.lua new file mode 100644 index 0000000..307f1c9 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/health.lua @@ -0,0 +1,75 @@ +local M = {} + +-- TODO: remove deprecated method check after dropping support for neovim v0.9 +local health = { + start = vim.health.start or vim.health.report_start, + ok = vim.health.ok or vim.health.report_ok, + warn = vim.health.warn or vim.health.report_warn, + error = vim.health.error or vim.health.report_error, + info = vim.health.info or vim.health.report_info, +} + +function M.check() + health.start "AstroNvim" + + health.info("AstroNvim Version: " .. require("astronvim.utils.updater").version(true)) + health.info("Neovim Version: v" .. vim.fn.matchstr(vim.fn.execute "version", "NVIM v\\zs[^\n]*")) + + if vim.version().prerelease then + health.warn "Neovim nightly is not officially supported and may have breaking changes" + elseif vim.fn.has "nvim-0.8" == 1 then + health.ok "Using stable Neovim >= 0.8.0" + else + health.error "Neovim >= 0.8.0 is required" + end + + local programs = { + { + cmd = { "git" }, + type = "error", + msg = "Used for core functionality such as updater and plugin management", + extra_check = function(program) + local git_version = require("astronvim.utils.git").git_version() + if git_version then + if git_version.major < 2 or (git_version.major == 2 and git_version.min < 19) then + program.msg = ("Git %s installed, >= 2.19.0 is required"):format(git_version.str) + else + return true + end + else + program.msg = "Unable to validate git version" + end + end, + }, + { + cmd = { "xdg-open", "open", "explorer" }, + type = "warn", + msg = "Used for `gx` mapping for opening files with system opener (Optional)", + }, + { cmd = { "lazygit" }, type = "warn", msg = "Used for mappings to pull up git TUI (Optional)" }, + { cmd = { "node" }, type = "warn", msg = "Used for mappings to pull up node REPL (Optional)" }, + { cmd = { "gdu" }, type = "warn", msg = "Used for mappings to pull up disk usage analyzer (Optional)" }, + { cmd = { "btm" }, type = "warn", msg = "Used for mappings to pull up system monitor (Optional)" }, + { cmd = { "python", "python3" }, type = "warn", msg = "Used for mappings to pull up python REPL (Optional)" }, + } + + for _, program in ipairs(programs) do + local name = table.concat(program.cmd, "/") + local found = false + for _, cmd in ipairs(program.cmd) do + if vim.fn.executable(cmd) == 1 then + name = cmd + if not program.extra_check or program.extra_check(program) then found = true end + break + end + end + + if found then + health.ok(("`%s` is installed: %s"):format(name, program.msg)) + else + health[program.type](("`%s` is not installed: %s"):format(name, program.msg)) + end + end +end + +return M diff --git a/vim/config/nvim/lua/astronvim/icons/nerd_font.lua b/vim/config/nvim/lua/astronvim/icons/nerd_font.lua new file mode 100644 index 0000000..87aa092 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/icons/nerd_font.lua @@ -0,0 +1,60 @@ +return { + ActiveLSP = "", + ActiveTS = "", + ArrowLeft = "", + ArrowRight = "", + Bookmarks = "", + BufferClose = "󰅖", + DapBreakpoint = "", + DapBreakpointCondition = "", + DapBreakpointRejected = "", + DapLogPoint = ".>", + DapStopped = "󰁕", + Debugger = "", + DefaultFile = "󰈙", + Diagnostic = "󰒡", + DiagnosticError = "", + DiagnosticHint = "󰌵", + DiagnosticInfo = "󰋼", + DiagnosticWarn = "", + Ellipsis = "…", + FileNew = "", + FileModified = "", + FileReadOnly = "", + FoldClosed = "", + FoldOpened = "", + FoldSeparator = " ", + FolderClosed = "", + FolderEmpty = "", + FolderOpen = "", + Git = "󰊢", + GitAdd = "", + GitBranch = "", + GitChange = "", + GitConflict = "", + GitDelete = "", + GitIgnored = "◌", + GitRenamed = "➜", + GitSign = "▎", + GitStaged = "✓", + GitUnstaged = "✗", + GitUntracked = "★", + LSPLoaded = "", -- TODO: Remove unused icon in AstroNvim v4 + LSPLoading1 = "", + LSPLoading2 = "󰀚", + LSPLoading3 = "", + MacroRecording = "", + Package = "󰏖", + Paste = "󰅌", + Refresh = "", + Search = "", + Selected = "❯", + Session = "󱂬", + Sort = "󰒺", + Spellcheck = "󰓆", + Tab = "󰓩", + TabClose = "󰅙", + Terminal = "", + Window = "", + WordFile = "󰈭", +} diff --git a/vim/config/nvim/lua/astronvim/icons/text.lua b/vim/config/nvim/lua/astronvim/icons/text.lua new file mode 100644 index 0000000..ba3fe18 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/icons/text.lua @@ -0,0 +1,41 @@ +return { + ActiveLSP = "LSP:", + ArrowLeft = "<", + ArrowRight = ">", + BufferClose = "x", + DapBreakpoint = "B", + DapBreakpointCondition = "C", + DapBreakpointRejected = "R", + DapLogPoint = "L", + DapStopped = ">", + DefaultFile = "[F]", + DiagnosticError = "X", + DiagnosticHint = "?", + DiagnosticInfo = "i", + DiagnosticWarn = "!", + Ellipsis = "...", + FileModified = "*", + FileReadOnly = "[lock]", + FoldClosed = "+", + FoldOpened = "-", + FoldSeparator = " ", + FolderClosed = "[D]", + FolderEmpty = "[E]", + FolderOpen = "[O]", + GitAdd = "[+]", + GitChange = "[/]", + GitConflict = "[!]", + GitDelete = "[-]", + GitIgnored = "[I]", + GitRenamed = "[R]", + GitSign = "|", + GitStaged = "[S]", + GitUnstaged = "[U]", + GitUntracked = "[?]", + MacroRecording = "Recording:", + Paste = "[PASTE]", + Search = "?", + Selected = "*", + Spellcheck = "[SPELL]", + TabClose = "X", +} diff --git a/vim/config/nvim/lua/astronvim/lazy.lua b/vim/config/nvim/lua/astronvim/lazy.lua new file mode 100644 index 0000000..e681250 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/lazy.lua @@ -0,0 +1,55 @@ +local git_version = vim.fn.system { "git", "--version" } +if vim.api.nvim_get_vvar "shell_error" ~= 0 then + vim.api.nvim_err_writeln("Git doesn't appear to be available...\n\n" .. git_version) +end +local major, min, _ = unpack(vim.tbl_map(tonumber, vim.split(git_version:match "%d+%.%d+%.%d", "%."))) +local modern_git = major > 2 or (major == 2 and min >= 19) + +local lazypath = vim.fn.stdpath "data" .. "/lazy/lazy.nvim" +if not (vim.uv or vim.loop).fs_stat(lazypath) then -- TODO: REMOVE vim.loop WHEN DROPPING SUPPORT FOR Neovim v0.9 + local clone = { "git", "clone", modern_git and "--filter=blob:none" or nil } + local output = + vim.fn.system(vim.list_extend(clone, { "--branch=stable", "https://github.com/folke/lazy.nvim.git", lazypath })) + if vim.api.nvim_get_vvar "shell_error" ~= 0 then + vim.api.nvim_err_writeln("Error cloning lazy.nvim repository...\n\n" .. output) + end + local oldcmdheight = vim.opt.cmdheight:get() + vim.opt.cmdheight = 1 + vim.notify "Please wait while plugins are installed..." + vim.api.nvim_create_autocmd("User", { + desc = "Load Mason and Treesitter after Lazy installs plugins", + once = true, + pattern = "LazyInstall", + callback = function() + vim.cmd.bw() + vim.opt.cmdheight = oldcmdheight + vim.tbl_map(function(module) pcall(require, module) end, { "nvim-treesitter", "mason" }) + require("astronvim.utils").notify "Mason is installing packages if configured, check status with `:Mason`" + end, + }) +end +vim.opt.rtp:prepend(lazypath) + +local user_plugins = astronvim.user_opts "plugins" +for _, config_dir in ipairs(astronvim.supported_configs) do + if vim.fn.isdirectory(config_dir .. "/lua/user/plugins") == 1 then user_plugins = { import = "user.plugins" } end +end + +local spec = astronvim.updater.options.pin_plugins and { { import = astronvim.updater.snapshot.module } } or {} +vim.list_extend(spec, { { import = "plugins" }, user_plugins }) + +local colorscheme = astronvim.default_colorscheme and { astronvim.default_colorscheme } or nil + +require("lazy").setup(astronvim.user_opts("lazy", { + spec = spec, + defaults = { lazy = true }, + git = { filter = modern_git }, + install = { colorscheme = colorscheme }, + performance = { + rtp = { + paths = astronvim.supported_configs, + disabled_plugins = { "tohtml", "gzip", "zipPlugin", "netrwPlugin", "tarPlugin" }, + }, + }, + lockfile = vim.fn.stdpath "data" .. "/lazy-lock.json", +})) diff --git a/vim/config/nvim/lua/astronvim/mappings.lua b/vim/config/nvim/lua/astronvim/mappings.lua new file mode 100644 index 0000000..8e2d537 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/mappings.lua @@ -0,0 +1,452 @@ +-- TODO: replace to everywhere in AstroNvim v4 to match vimdoc +local utils = require "astronvim.utils" +local get_icon = utils.get_icon +local is_available = utils.is_available +local ui = require "astronvim.utils.ui" + +local maps = require("astronvim.utils").empty_map_table() + +local sections = { + f = { desc = get_icon("Search", 1, true) .. "Find" }, + p = { desc = get_icon("Package", 1, true) .. "Packages" }, + l = { desc = get_icon("ActiveLSP", 1, true) .. "LSP" }, + u = { desc = get_icon("Window", 1, true) .. "UI/UX" }, + b = { desc = get_icon("Tab", 1, true) .. "Buffers" }, + bs = { desc = get_icon("Sort", 1, true) .. "Sort Buffers" }, + d = { desc = get_icon("Debugger", 1, true) .. "Debugger" }, + g = { desc = get_icon("Git", 1, true) .. "Git" }, + S = { desc = get_icon("Session", 1, true) .. "Session" }, + t = { desc = get_icon("Terminal", 1, true) .. "Terminal" }, +} + +-- Normal -- +-- Standard Operations +maps.n["j"] = { "v:count == 0 ? 'gj' : 'j'", expr = true, desc = "Move cursor down" } +maps.n["k"] = { "v:count == 0 ? 'gk' : 'k'", expr = true, desc = "Move cursor up" } +maps.n["w"] = { "w", desc = "Save" } +maps.n["q"] = { "confirm q", desc = "Quit" } +maps.n["n"] = { "enew", desc = "New File" } +maps.n[""] = { "w!", desc = "Force write" } +maps.n[""] = { "q!", desc = "Force quit" } +maps.n["|"] = { "vsplit", desc = "Vertical Split" } +maps.n["\\"] = { "split", desc = "Horizontal Split" } +-- TODO: Remove when dropping support for p"] = sections.p +maps.n["pi"] = { function() require("lazy").install() end, desc = "Plugins Install" } +maps.n["ps"] = { function() require("lazy").home() end, desc = "Plugins Status" } +maps.n["pS"] = { function() require("lazy").sync() end, desc = "Plugins Sync" } +maps.n["pu"] = { function() require("lazy").check() end, desc = "Plugins Check Updates" } +maps.n["pU"] = { function() require("lazy").update() end, desc = "Plugins Update" } + +-- AstroNvim +maps.n["pa"] = { "AstroUpdatePackages", desc = "Update Plugins and Mason Packages" } +maps.n["pA"] = { "AstroUpdate", desc = "AstroNvim Update" } +maps.n["pv"] = { "AstroVersion", desc = "AstroNvim Version" } +maps.n["pl"] = { "AstroChangelog", desc = "AstroNvim Changelog" } + +-- Manage Buffers +maps.n["c"] = { function() require("astronvim.utils.buffer").close() end, desc = "Close buffer" } +maps.n["C"] = { function() require("astronvim.utils.buffer").close(0, true) end, desc = "Force close buffer" } +maps.n["]b"] = + { function() require("astronvim.utils.buffer").nav(vim.v.count > 0 and vim.v.count or 1) end, desc = "Next buffer" } +maps.n["[b"] = { + function() require("astronvim.utils.buffer").nav(-(vim.v.count > 0 and vim.v.count or 1)) end, + desc = "Previous buffer", +} +maps.n[">b"] = { + function() require("astronvim.utils.buffer").move(vim.v.count > 0 and vim.v.count or 1) end, + desc = "Move buffer tab right", +} +maps.n[" 0 and vim.v.count or 1)) end, + desc = "Move buffer tab left", +} + +maps.n["b"] = sections.b +maps.n["bc"] = + { function() require("astronvim.utils.buffer").close_all(true) end, desc = "Close all buffers except current" } +maps.n["bC"] = { function() require("astronvim.utils.buffer").close_all() end, desc = "Close all buffers" } +maps.n["bb"] = { + function() + require("astronvim.utils.status.heirline").buffer_picker(function(bufnr) vim.api.nvim_win_set_buf(0, bufnr) end) + end, + desc = "Select buffer from tabline", +} +maps.n["bd"] = { + function() + require("astronvim.utils.status.heirline").buffer_picker( + function(bufnr) require("astronvim.utils.buffer").close(bufnr) end + ) + end, + desc = "Close buffer from tabline", +} +maps.n["bl"] = + { function() require("astronvim.utils.buffer").close_left() end, desc = "Close all buffers to the left" } +maps.n["bp"] = { function() require("astronvim.utils.buffer").prev() end, desc = "Previous buffer" } +maps.n["br"] = + { function() require("astronvim.utils.buffer").close_right() end, desc = "Close all buffers to the right" } +maps.n["bs"] = sections.bs +maps.n["bse"] = { function() require("astronvim.utils.buffer").sort "extension" end, desc = "By extension" } +maps.n["bsr"] = + { function() require("astronvim.utils.buffer").sort "unique_path" end, desc = "By relative path" } +maps.n["bsp"] = { function() require("astronvim.utils.buffer").sort "full_path" end, desc = "By full path" } +maps.n["bsi"] = { function() require("astronvim.utils.buffer").sort "bufnr" end, desc = "By buffer number" } +maps.n["bsm"] = { function() require("astronvim.utils.buffer").sort "modified" end, desc = "By modification" } +maps.n["b\\"] = { + function() + require("astronvim.utils.status.heirline").buffer_picker(function(bufnr) + vim.cmd.split() + vim.api.nvim_win_set_buf(0, bufnr) + end) + end, + desc = "Horizontal split buffer from tabline", +} +maps.n["b|"] = { + function() + require("astronvim.utils.status.heirline").buffer_picker(function(bufnr) + vim.cmd.vsplit() + vim.api.nvim_win_set_buf(0, bufnr) + end) + end, + desc = "Vertical split buffer from tabline", +} + +-- Navigate tabs +maps.n["]t"] = { function() vim.cmd.tabnext() end, desc = "Next tab" } +maps.n["[t"] = { function() vim.cmd.tabprevious() end, desc = "Previous tab" } + +-- Alpha +if is_available "alpha-nvim" then + maps.n["h"] = { + function() + local wins = vim.api.nvim_tabpage_list_wins(0) + if #wins > 1 and vim.api.nvim_get_option_value("filetype", { win = wins[1] }) == "neo-tree" then + vim.fn.win_gotoid(wins[2]) -- go to non-neo-tree window to toggle alpha + end + require("alpha").start(false, require("alpha").default_config) + end, + desc = "Home Screen", + } +end + +-- Comment +if is_available "Comment.nvim" then + maps.n["/"] = { + function() require("Comment.api").toggle.linewise.count(vim.v.count > 0 and vim.v.count or 1) end, + desc = "Toggle comment line", + } + maps.v["/"] = { + "lua require('Comment.api').toggle.linewise(vim.fn.visualmode())", + desc = "Toggle comment for selection", + } +end + +-- GitSigns +if is_available "gitsigns.nvim" then + maps.n["g"] = sections.g + maps.n["]g"] = { function() require("gitsigns").next_hunk() end, desc = "Next Git hunk" } + maps.n["[g"] = { function() require("gitsigns").prev_hunk() end, desc = "Previous Git hunk" } + maps.n["gl"] = { function() require("gitsigns").blame_line() end, desc = "View Git blame" } + maps.n["gL"] = { function() require("gitsigns").blame_line { full = true } end, desc = "View full Git blame" } + maps.n["gp"] = { function() require("gitsigns").preview_hunk() end, desc = "Preview Git hunk" } + maps.n["gh"] = { function() require("gitsigns").reset_hunk() end, desc = "Reset Git hunk" } + maps.n["gr"] = { function() require("gitsigns").reset_buffer() end, desc = "Reset Git buffer" } + maps.n["gs"] = { function() require("gitsigns").stage_hunk() end, desc = "Stage Git hunk" } + maps.n["gS"] = { function() require("gitsigns").stage_buffer() end, desc = "Stage Git buffer" } + maps.n["gu"] = { function() require("gitsigns").undo_stage_hunk() end, desc = "Unstage Git hunk" } + maps.n["gd"] = { function() require("gitsigns").diffthis() end, desc = "View Git diff" } +end + +-- NeoTree +if is_available "neo-tree.nvim" then + maps.n["e"] = { "Neotree toggle", desc = "Toggle Explorer" } + maps.n["o"] = { + function() + if vim.bo.filetype == "neo-tree" then + vim.cmd.wincmd "p" + else + vim.cmd.Neotree "focus" + end + end, + desc = "Toggle Explorer Focus", + } +end + +-- Session Manager +if is_available "neovim-session-manager" then + maps.n["S"] = sections.S + maps.n["Sl"] = { "SessionManager! load_last_session", desc = "Load last session" } + maps.n["Ss"] = { "SessionManager! save_current_session", desc = "Save this session" } + maps.n["Sd"] = { "SessionManager! delete_session", desc = "Delete session" } + maps.n["Sf"] = { "SessionManager! load_session", desc = "Search sessions" } + maps.n["S."] = + { "SessionManager! load_current_dir_session", desc = "Load current directory session" } +end +if is_available "resession.nvim" then + maps.n["S"] = sections.S + maps.n["Sl"] = { function() require("resession").load "Last Session" end, desc = "Load last session" } + maps.n["Ss"] = { function() require("resession").save() end, desc = "Save this session" } + maps.n["St"] = { function() require("resession").save_tab() end, desc = "Save this tab's session" } + maps.n["Sd"] = { function() require("resession").delete() end, desc = "Delete a session" } + maps.n["Sf"] = { function() require("resession").load() end, desc = "Load a session" } + maps.n["S."] = { + function() require("resession").load(vim.fn.getcwd(), { dir = "dirsession" }) end, + desc = "Load current directory session", + } +end + +-- Package Manager +if is_available "mason.nvim" then + maps.n["pm"] = { "Mason", desc = "Mason Installer" } + maps.n["pM"] = { "MasonUpdateAll", desc = "Mason Update" } +end + +-- Smart Splits +if is_available "smart-splits.nvim" then + maps.n[""] = { function() require("smart-splits").move_cursor_left() end, desc = "Move to left split" } + maps.n[""] = { function() require("smart-splits").move_cursor_down() end, desc = "Move to below split" } + maps.n[""] = { function() require("smart-splits").move_cursor_up() end, desc = "Move to above split" } + maps.n[""] = { function() require("smart-splits").move_cursor_right() end, desc = "Move to right split" } + maps.n[""] = { function() require("smart-splits").resize_up() end, desc = "Resize split up" } + maps.n[""] = { function() require("smart-splits").resize_down() end, desc = "Resize split down" } + maps.n[""] = { function() require("smart-splits").resize_left() end, desc = "Resize split left" } + maps.n[""] = { function() require("smart-splits").resize_right() end, desc = "Resize split right" } +else + maps.n[""] = { "h", desc = "Move to left split" } + maps.n[""] = { "j", desc = "Move to below split" } + maps.n[""] = { "k", desc = "Move to above split" } + maps.n[""] = { "l", desc = "Move to right split" } + maps.n[""] = { "resize -2", desc = "Resize split up" } + maps.n[""] = { "resize +2", desc = "Resize split down" } + maps.n[""] = { "vertical resize -2", desc = "Resize split left" } + maps.n[""] = { "vertical resize +2", desc = "Resize split right" } +end + +-- SymbolsOutline +if is_available "aerial.nvim" then + maps.n["l"] = sections.l + maps.n["lS"] = { function() require("aerial").toggle() end, desc = "Symbols outline" } +end + +-- Telescope +if is_available "telescope.nvim" then + maps.n["f"] = sections.f + maps.n["g"] = sections.g + maps.n["gb"] = + { function() require("telescope.builtin").git_branches { use_file_path = true } end, desc = "Git branches" } + maps.n["gc"] = { + function() require("telescope.builtin").git_commits { use_file_path = true } end, + desc = "Git commits (repository)", + } + maps.n["gC"] = { + function() require("telescope.builtin").git_bcommits { use_file_path = true } end, + desc = "Git commits (current file)", + } + maps.n["gt"] = + { function() require("telescope.builtin").git_status { use_file_path = true } end, desc = "Git status" } + maps.n["f"] = { function() require("telescope.builtin").resume() end, desc = "Resume previous search" } + maps.n["f'"] = { function() require("telescope.builtin").marks() end, desc = "Find marks" } + maps.n["f/"] = + { function() require("telescope.builtin").current_buffer_fuzzy_find() end, desc = "Find words in current buffer" } + maps.n["fa"] = { + function() + local cwd = vim.fn.stdpath "config" .. "/.." + local search_dirs = {} + for _, dir in ipairs(astronvim.supported_configs) do -- search all supported config locations + if dir == astronvim.install.home then dir = dir .. "/lua/user" end -- don't search the astronvim core files + if vim.fn.isdirectory(dir) == 1 then table.insert(search_dirs, dir) end -- add directory to search if exists + end + if vim.tbl_isempty(search_dirs) then -- if no config folders found, show warning + utils.notify("No user configuration files found", vim.log.levels.WARN) + else + if #search_dirs == 1 then cwd = search_dirs[1] end -- if only one directory, focus cwd + require("telescope.builtin").find_files { + prompt_title = "Config Files", + search_dirs = search_dirs, + cwd = cwd, + } -- call telescope + end + end, + desc = "Find AstroNvim config files", + } + maps.n["fb"] = { function() require("telescope.builtin").buffers() end, desc = "Find buffers" } + maps.n["fc"] = { function() require("telescope.builtin").grep_string() end, desc = "Find word under cursor" } + maps.n["fC"] = { function() require("telescope.builtin").commands() end, desc = "Find commands" } + maps.n["ff"] = { function() require("telescope.builtin").find_files() end, desc = "Find files" } + maps.n["fF"] = { + function() require("telescope.builtin").find_files { hidden = true, no_ignore = true } end, + desc = "Find all files", + } + maps.n["fh"] = { function() require("telescope.builtin").help_tags() end, desc = "Find help" } + maps.n["fk"] = { function() require("telescope.builtin").keymaps() end, desc = "Find keymaps" } + maps.n["fm"] = { function() require("telescope.builtin").man_pages() end, desc = "Find man" } + if is_available "nvim-notify" then + maps.n["fn"] = + { function() require("telescope").extensions.notify.notify() end, desc = "Find notifications" } + end + maps.n["fo"] = { function() require("telescope.builtin").oldfiles() end, desc = "Find history" } + maps.n["fr"] = { function() require("telescope.builtin").registers() end, desc = "Find registers" } + maps.n["ft"] = + { function() require("telescope.builtin").colorscheme { enable_preview = true } end, desc = "Find themes" } + maps.n["fw"] = { function() require("telescope.builtin").live_grep() end, desc = "Find words" } + maps.n["fW"] = { + function() + require("telescope.builtin").live_grep { + additional_args = function(args) return vim.list_extend(args, { "--hidden", "--no-ignore" }) end, + } + end, + desc = "Find words in all files", + } + maps.n["l"] = sections.l + maps.n["ls"] = { + function() + local aerial_avail, _ = pcall(require, "aerial") + if aerial_avail then + require("telescope").extensions.aerial.aerial() + else + require("telescope.builtin").lsp_document_symbols() + end + end, + desc = "Search symbols", + } +end + +-- Terminal +if is_available "toggleterm.nvim" then + maps.n["t"] = sections.t + if vim.fn.executable "lazygit" == 1 then + maps.n["g"] = sections.g + maps.n["gg"] = { + function() + local worktree = require("astronvim.utils.git").file_worktree() + local flags = worktree and (" --work-tree=%s --git-dir=%s"):format(worktree.toplevel, worktree.gitdir) or "" + utils.toggle_term_cmd("lazygit " .. flags) + end, + desc = "ToggleTerm lazygit", + } + maps.n["tl"] = maps.n["gg"] + end + if vim.fn.executable "node" == 1 then + maps.n["tn"] = { function() utils.toggle_term_cmd "node" end, desc = "ToggleTerm node" } + end + if vim.fn.executable "gdu" == 1 then + maps.n["tu"] = { function() utils.toggle_term_cmd "gdu" end, desc = "ToggleTerm gdu" } + end + if vim.fn.executable "btm" == 1 then + maps.n["tt"] = { function() utils.toggle_term_cmd "btm" end, desc = "ToggleTerm btm" } + end + local python = vim.fn.executable "python" == 1 and "python" or vim.fn.executable "python3" == 1 and "python3" + if python then maps.n["tp"] = { function() utils.toggle_term_cmd(python) end, desc = "ToggleTerm python" } end + maps.n["tf"] = { "ToggleTerm direction=float", desc = "ToggleTerm float" } + maps.n["th"] = { "ToggleTerm size=10 direction=horizontal", desc = "ToggleTerm horizontal split" } + maps.n["tv"] = { "ToggleTerm size=80 direction=vertical", desc = "ToggleTerm vertical split" } + maps.n[""] = { "ToggleTerm", desc = "Toggle terminal" } + maps.t[""] = maps.n[""] + maps.n[""] = maps.n[""] -- requires terminal that supports binding + maps.t[""] = maps.n[""] -- requires terminal that supports binding +end + +if is_available "nvim-dap" then + maps.n["d"] = sections.d + maps.v["d"] = sections.d + -- modified function keys found with `showkey -a` in the terminal to get key code + -- run `nvim -V3log +quit` and search through the "Terminal info" in the `log` file for the correct keyname + maps.n[""] = { function() require("dap").continue() end, desc = "Debugger: Start" } + maps.n[""] = { function() require("dap").terminate() end, desc = "Debugger: Stop" } -- Shift+F5 + maps.n[""] = { -- Shift+F9 + function() + vim.ui.input({ prompt = "Condition: " }, function(condition) + if condition then require("dap").set_breakpoint(condition) end + end) + end, + desc = "Debugger: Conditional Breakpoint", + } + maps.n[""] = { function() require("dap").restart_frame() end, desc = "Debugger: Restart" } -- Control+F5 + maps.n[""] = { function() require("dap").pause() end, desc = "Debugger: Pause" } + maps.n[""] = { function() require("dap").toggle_breakpoint() end, desc = "Debugger: Toggle Breakpoint" } + maps.n[""] = { function() require("dap").step_over() end, desc = "Debugger: Step Over" } + maps.n[""] = { function() require("dap").step_into() end, desc = "Debugger: Step Into" } + maps.n[""] = { function() require("dap").step_out() end, desc = "Debugger: Step Out" } -- Shift+F11 + maps.n["db"] = { function() require("dap").toggle_breakpoint() end, desc = "Toggle Breakpoint (F9)" } + maps.n["dB"] = { function() require("dap").clear_breakpoints() end, desc = "Clear Breakpoints" } + maps.n["dc"] = { function() require("dap").continue() end, desc = "Start/Continue (F5)" } + maps.n["dC"] = { + function() + vim.ui.input({ prompt = "Condition: " }, function(condition) + if condition then require("dap").set_breakpoint(condition) end + end) + end, + desc = "Conditional Breakpoint (S-F9)", + } + maps.n["di"] = { function() require("dap").step_into() end, desc = "Step Into (F11)" } + maps.n["do"] = { function() require("dap").step_over() end, desc = "Step Over (F10)" } + maps.n["dO"] = { function() require("dap").step_out() end, desc = "Step Out (S-F11)" } + maps.n["dq"] = { function() require("dap").close() end, desc = "Close Session" } + maps.n["dQ"] = { function() require("dap").terminate() end, desc = "Terminate Session (S-F5)" } + maps.n["dp"] = { function() require("dap").pause() end, desc = "Pause (F6)" } + maps.n["dr"] = { function() require("dap").restart_frame() end, desc = "Restart (C-F5)" } + maps.n["dR"] = { function() require("dap").repl.toggle() end, desc = "Toggle REPL" } + maps.n["ds"] = { function() require("dap").run_to_cursor() end, desc = "Run To Cursor" } + + if is_available "nvim-dap-ui" then + maps.n["dE"] = { + function() + vim.ui.input({ prompt = "Expression: " }, function(expr) + if expr then require("dapui").eval(expr) end + end) + end, + desc = "Evaluate Input", + } + maps.v["dE"] = { function() require("dapui").eval() end, desc = "Evaluate Input" } + maps.n["du"] = { function() require("dapui").toggle() end, desc = "Toggle Debugger UI" } + maps.n["dh"] = { function() require("dap.ui.widgets").hover() end, desc = "Debugger Hover" } + end +end + +-- Improved Code Folding +if is_available "nvim-ufo" then + maps.n["zR"] = { function() require("ufo").openAllFolds() end, desc = "Open all folds" } + maps.n["zM"] = { function() require("ufo").closeAllFolds() end, desc = "Close all folds" } + maps.n["zr"] = { function() require("ufo").openFoldsExceptKinds() end, desc = "Fold less" } + maps.n["zm"] = { function() require("ufo").closeFoldsWith() end, desc = "Fold more" } + maps.n["zp"] = { function() require("ufo").peekFoldedLinesUnderCursor() end, desc = "Peek fold" } +end + +-- Stay in indent mode +maps.v[""] = { ""] = { ">gv", desc = "Indent line" } + +-- Improved Terminal Navigation +maps.t[""] = { "wincmd h", desc = "Terminal left window navigation" } +maps.t[""] = { "wincmd j", desc = "Terminal down window navigation" } +maps.t[""] = { "wincmd k", desc = "Terminal up window navigation" } +maps.t[""] = { "wincmd l", desc = "Terminal right window navigation" } + +maps.n["u"] = sections.u +-- Custom menu for modification of the user experience +if is_available "nvim-autopairs" then maps.n["ua"] = { ui.toggle_autopairs, desc = "Toggle autopairs" } end +maps.n["ub"] = { ui.toggle_background, desc = "Toggle background" } +if is_available "nvim-cmp" then maps.n["uc"] = { ui.toggle_cmp, desc = "Toggle autocompletion" } end +if is_available "nvim-colorizer.lua" then + maps.n["uC"] = { "ColorizerToggle", desc = "Toggle color highlight" } +end +maps.n["ud"] = { ui.toggle_diagnostics, desc = "Toggle diagnostics" } +maps.n["ug"] = { ui.toggle_signcolumn, desc = "Toggle signcolumn" } +maps.n["ui"] = { ui.set_indent, desc = "Change indent setting" } +maps.n["ul"] = { ui.toggle_statusline, desc = "Toggle statusline" } +maps.n["uL"] = { ui.toggle_codelens, desc = "Toggle CodeLens" } +maps.n["un"] = { ui.change_number, desc = "Change line numbering" } +maps.n["uN"] = { ui.toggle_ui_notifications, desc = "Toggle Notifications" } +maps.n["up"] = { ui.toggle_paste, desc = "Toggle paste mode" } +maps.n["us"] = { ui.toggle_spell, desc = "Toggle spellcheck" } +maps.n["uS"] = { ui.toggle_conceal, desc = "Toggle conceal" } +maps.n["ut"] = { ui.toggle_tabline, desc = "Toggle tabline" } +maps.n["uu"] = { ui.toggle_url_match, desc = "Toggle URL highlight" } +maps.n["uw"] = { ui.toggle_wrap, desc = "Toggle wrap" } +maps.n["uy"] = { ui.toggle_syntax, desc = "Toggle syntax highlight" } +maps.n["uh"] = { ui.toggle_foldcolumn, desc = "Toggle foldcolumn" } + +utils.set_mappings(astronvim.user_opts("mappings", maps)) diff --git a/vim/config/nvim/lua/astronvim/options.lua b/vim/config/nvim/lua/astronvim/options.lua new file mode 100644 index 0000000..6ea58f1 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/options.lua @@ -0,0 +1,76 @@ +vim.opt.viewoptions:remove "curdir" -- disable saving current directory with views +vim.opt.shortmess:append { s = true, I = true } -- disable startup message +vim.opt.backspace:append { "nostop" } -- Don't stop backspace at insert +if vim.fn.has "nvim-0.9" == 1 then + vim.opt.diffopt:append "linematch:60" -- enable linematch diff algorithm +end +local options = astronvim.user_opts("options", { + opt = { + breakindent = true, -- Wrap indent to match line start + clipboard = "unnamedplus", -- Connection to the system clipboard + cmdheight = 0, -- hide command line unless needed + completeopt = { "menu", "menuone", "noselect" }, -- Options for insert mode completion + copyindent = true, -- Copy the previous indentation on autoindenting + cursorline = true, -- Highlight the text line of the cursor + expandtab = true, -- Enable the use of space in tab + fileencoding = "utf-8", -- File content encoding for the buffer + fillchars = { eob = " " }, -- Disable `~` on nonexistent lines + foldenable = true, -- enable fold for nvim-ufo + foldlevel = 99, -- set high foldlevel for nvim-ufo + foldlevelstart = 99, -- start with all code unfolded + foldcolumn = vim.fn.has "nvim-0.9" == 1 and "1" or nil, -- show foldcolumn in nvim 0.9 + history = 100, -- Number of commands to remember in a history table + ignorecase = true, -- Case insensitive searching + infercase = true, -- Infer cases in keyword completion + laststatus = 3, -- globalstatus + linebreak = true, -- Wrap lines at 'breakat' + mouse = "a", -- Enable mouse support + number = true, -- Show numberline + preserveindent = true, -- Preserve indent structure as much as possible + pumheight = 10, -- Height of the pop up menu + relativenumber = true, -- Show relative numberline + scrolloff = 8, -- Number of lines to keep above and below the cursor + shiftwidth = 2, -- Number of space inserted for indentation + showmode = false, -- Disable showing modes in command line + showtabline = 2, -- always display tabline + sidescrolloff = 8, -- Number of columns to keep at the sides of the cursor + signcolumn = "yes", -- Always show the sign column + smartcase = true, -- Case sensitivie searching + smartindent = true, -- Smarter autoindentation + splitbelow = true, -- Splitting a new window below the current one + splitright = true, -- Splitting a new window at the right of the current one + tabstop = 2, -- Number of space in a tab + termguicolors = true, -- Enable 24-bit RGB color in the TUI + timeoutlen = 500, -- Shorten key timeout length a little bit for which-key + undofile = true, -- Enable persistent undo + updatetime = 300, -- Length of time to wait before triggering the plugin + virtualedit = "block", -- allow going past end of line in visual block mode + wrap = false, -- Disable wrapping of lines longer than the width of window + writebackup = false, -- Disable making a backup before overwriting a file + }, + g = { + mapleader = " ", -- set leader key + maplocalleader = ",", -- set default local leader key + -- AstroNvim specific global options + max_file = { size = 1024 * 100, lines = 10000 }, -- set global limits for large files + autoformat_enabled = true, -- enable or disable auto formatting at start (lsp.formatting.format_on_save must be enabled) + autopairs_enabled = true, -- enable autopairs at start + cmp_enabled = true, -- enable completion at start + codelens_enabled = true, -- enable or disable automatic codelens refreshing for lsp that support it + diagnostics_mode = 3, -- set the visibility of diagnostics in the UI (0=off, 1=only show in status line, 2=virtual text off, 3=all on) + highlighturl_enabled = true, -- highlight URLs by default + icons_enabled = true, -- disable icons in the UI (disable if no nerd font is available) + inlay_hints_enabled = false, -- enable or disable LSP inlay hints on startup (Neovim v0.10 only) + lsp_handlers_enabled = true, -- enable or disable default vim.lsp.handlers (hover and signatureHelp) + semantic_tokens_enabled = true, -- enable or disable LSP semantic tokens on startup + ui_notifications_enabled = true, -- disable notifications (TODO: rename to notifications_enabled in AstroNvim v4) + git_worktrees = nil, -- enable git integration for detached worktrees (specify a table where each entry is of the form { toplevel = vim.env.HOME, gitdir=vim.env.HOME .. "/.dotfiles" }) + }, + t = vim.t.bufs and vim.t.bufs or { bufs = vim.api.nvim_list_bufs() }, -- initialize buffers for the current tab +}) + +for scope, table in pairs(options) do + for setting, value in pairs(table) do + vim[scope][setting] = value + end +end diff --git a/vim/config/nvim/lua/astronvim/utils/buffer.lua b/vim/config/nvim/lua/astronvim/utils/buffer.lua new file mode 100644 index 0000000..11f1c49 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/buffer.lua @@ -0,0 +1,259 @@ +--- ### AstroNvim Buffer Utilities +-- +-- Buffer management related utility functions +-- +-- This module can be loaded with `local buffer_utils = require "astronvim.utils.buffer"` +-- +-- @module astronvim.utils.buffer +-- @copyright 2022 +-- @license GNU General Public License v3.0 + +local M = {} + +local utils = require "astronvim.utils" + +--- Placeholders for keeping track of most recent and previous buffer +M.current_buf, M.last_buf = nil, nil + +-- TODO: Add user configuration table for this once resession is default +--- Configuration table for controlling session options +M.sessions = { + autosave = { + last = true, -- auto save last session + cwd = true, -- auto save session for each working directory + }, + ignore = { + dirs = {}, -- working directories to ignore sessions in + filetypes = { "gitcommit", "gitrebase" }, -- filetypes to ignore sessions + buftypes = {}, -- buffer types to ignore sessions + }, +} + +--- Check if a buffer is valid +---@param bufnr number? The buffer to check, default to current buffer +---@return boolean # Whether the buffer is valid or not +function M.is_valid(bufnr) + if not bufnr then bufnr = 0 end + return vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].buflisted +end + +--- Check if a buffer can be restored +---@param bufnr number The buffer to check +---@return boolean # Whether the buffer is restorable or not +function M.is_restorable(bufnr) + if not M.is_valid(bufnr) or vim.api.nvim_get_option_value("bufhidden", { buf = bufnr }) ~= "" then return false end + + local buftype = vim.api.nvim_get_option_value("buftype", { buf = bufnr }) + if buftype == "" then + -- Normal buffer, check if it listed. + if not vim.api.nvim_get_option_value("buflisted", { buf = bufnr }) then return false end + -- Check if it has a filename. + if vim.api.nvim_buf_get_name(bufnr) == "" then return false end + end + + if + vim.tbl_contains(M.sessions.ignore.filetypes, vim.api.nvim_get_option_value("filetype", { buf = bufnr })) + or vim.tbl_contains(M.sessions.ignore.buftypes, vim.api.nvim_get_option_value("buftype", { buf = bufnr })) + then + return false + end + return true +end + +--- Check if the current buffers form a valid session +---@return boolean # Whether the current session of buffers is a valid session +function M.is_valid_session() + local cwd = vim.fn.getcwd() + for _, dir in ipairs(M.sessions.ignore.dirs) do + if vim.fn.expand(dir) == cwd then return false end + end + for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do + if M.is_restorable(bufnr) then return true end + end + return false +end + +--- Move the current buffer tab n places in the bufferline +---@param n number The number of tabs to move the current buffer over by (positive = right, negative = left) +function M.move(n) + if n == 0 then return end -- if n = 0 then no shifts are needed + local bufs = vim.t.bufs -- make temp variable + for i, bufnr in ipairs(bufs) do -- loop to find current buffer + if bufnr == vim.api.nvim_get_current_buf() then -- found index of current buffer + for _ = 0, (n % #bufs) - 1 do -- calculate number of right shifts + local new_i = i + 1 -- get next i + if i == #bufs then -- if at end, cycle to beginning + new_i = 1 -- next i is actually 1 if at the end + local val = bufs[i] -- save value + table.remove(bufs, i) -- remove from end + table.insert(bufs, new_i, val) -- insert at beginning + else -- if not at the end,then just do an in place swap + bufs[i], bufs[new_i] = bufs[new_i], bufs[i] + end + i = new_i -- iterate i to next value + end + break + end + end + vim.t.bufs = bufs -- set buffers + utils.event "BufsUpdated" + vim.cmd.redrawtabline() -- redraw tabline +end + +--- Navigate left and right by n places in the bufferline +-- @param n number The number of tabs to navigate to (positive = right, negative = left) +function M.nav(n) + local current = vim.api.nvim_get_current_buf() + for i, v in ipairs(vim.t.bufs) do + if current == v then + vim.cmd.b(vim.t.bufs[(i + n - 1) % #vim.t.bufs + 1]) + break + end + end +end + +--- Navigate to a specific buffer by its position in the bufferline +---@param tabnr number The position of the buffer to navigate to +function M.nav_to(tabnr) vim.cmd.b(vim.t.bufs[tabnr]) end + +--- Navigate to the previously used buffer +function M.prev() + if vim.fn.bufnr() == M.current_buf then + if M.last_buf then + vim.cmd.b(M.last_buf) + else + utils.notify "No previous buffer found" + end + else + utils.notify "Must be in a main editor window to switch the window buffer" + end +end + +--- Close a given buffer +---@param bufnr? number The buffer to close or the current buffer if not provided +---@param force? boolean Whether or not to foce close the buffers or confirm changes (default: false) +function M.close(bufnr, force) + if utils.is_available "mini.bufremove" and M.is_valid(bufnr) and #vim.t.bufs > 1 then + if not force and vim.api.nvim_get_option_value("modified", { buf = bufnr }) then + local bufname = vim.fn.expand "%" + local empty = bufname == "" + if empty then bufname = "Untitled" end + local confirm = vim.fn.confirm(('Save changes to "%s"?'):format(bufname), "&Yes\n&No\n&Cancel", 1, "Question") + if confirm == 1 then + if empty then return end + vim.cmd.write() + elseif confirm == 2 then + force = true + else + return + end + end + require("mini.bufremove").delete(bufnr, force) + else + vim.cmd((force and "bd!" or "confirm bd") .. (bufnr == nil and "" or bufnr)) + end +end + +--- Close all buffers +---@param keep_current? boolean Whether or not to keep the current buffer (default: false) +---@param force? boolean Whether or not to foce close the buffers or confirm changes (default: false) +function M.close_all(keep_current, force) + if keep_current == nil then keep_current = false end + local current = vim.api.nvim_get_current_buf() + for _, bufnr in ipairs(vim.t.bufs) do + if not keep_current or bufnr ~= current then M.close(bufnr, force) end + end +end + +--- Close buffers to the left of the current buffer +---@param force? boolean Whether or not to foce close the buffers or confirm changes (default: false) +function M.close_left(force) + local current = vim.api.nvim_get_current_buf() + for _, bufnr in ipairs(vim.t.bufs) do + if bufnr == current then break end + M.close(bufnr, force) + end +end + +--- Close buffers to the right of the current buffer +---@param force? boolean Whether or not to foce close the buffers or confirm changes (default: false) +function M.close_right(force) + local current = vim.api.nvim_get_current_buf() + local after_current = false + for _, bufnr in ipairs(vim.t.bufs) do + if after_current then M.close(bufnr, force) end + if bufnr == current then after_current = true end + end +end + +--- Sort a the buffers in the current tab based on some comparator +---@param compare_func string|function a string of a comparator defined in require("astronvim.utils.buffer").comparator or a custom comparator function +---@param skip_autocmd boolean|nil whether or not to skip triggering AstroBufsUpdated autocmd event +---@return boolean # Whether or not the buffers were sorted +function M.sort(compare_func, skip_autocmd) + if type(compare_func) == "string" then compare_func = M.comparator[compare_func] end + if type(compare_func) == "function" then + local bufs = vim.t.bufs + table.sort(bufs, compare_func) + vim.t.bufs = bufs + if not skip_autocmd then utils.event "BufsUpdated" end + vim.cmd.redrawtabline() + return true + end + return false +end + +--- Close the current tab +function M.close_tab() + if #vim.api.nvim_list_tabpages() > 1 then + vim.t.bufs = nil + utils.event "BufsUpdated" + vim.cmd.tabclose() + end +end + +--- A table of buffer comparator functions +M.comparator = {} + +local fnamemodify = vim.fn.fnamemodify +local function bufinfo(bufnr) return vim.fn.getbufinfo(bufnr)[1] end +local function unique_path(bufnr) + return require("astronvim.utils.status.provider").unique_path() { bufnr = bufnr } + .. fnamemodify(bufinfo(bufnr).name, ":t") +end + +--- Comparator of two buffer numbers +---@param bufnr_a integer buffer number A +---@param bufnr_b integer buffer number B +---@return boolean comparison true if A is sorted before B, false if B should be sorted before A +function M.comparator.bufnr(bufnr_a, bufnr_b) return bufnr_a < bufnr_b end + +--- Comparator of two buffer numbers based on the file extensions +---@param bufnr_a integer buffer number A +---@param bufnr_b integer buffer number B +---@return boolean comparison true if A is sorted before B, false if B should be sorted before A +function M.comparator.extension(bufnr_a, bufnr_b) + return fnamemodify(bufinfo(bufnr_a).name, ":e") < fnamemodify(bufinfo(bufnr_b).name, ":e") +end + +--- Comparator of two buffer numbers based on the full path +---@param bufnr_a integer buffer number A +---@param bufnr_b integer buffer number B +---@return boolean comparison true if A is sorted before B, false if B should be sorted before A +function M.comparator.full_path(bufnr_a, bufnr_b) + return fnamemodify(bufinfo(bufnr_a).name, ":p") < fnamemodify(bufinfo(bufnr_b).name, ":p") +end + +--- Comparator of two buffers based on their unique path +---@param bufnr_a integer buffer number A +---@param bufnr_b integer buffer number B +---@return boolean comparison true if A is sorted before B, false if B should be sorted before A +function M.comparator.unique_path(bufnr_a, bufnr_b) return unique_path(bufnr_a) < unique_path(bufnr_b) end + +--- Comparator of two buffers based on modification date +---@param bufnr_a integer buffer number A +---@param bufnr_b integer buffer number B +---@return boolean comparison true if A is sorted before B, false if B should be sorted before A +function M.comparator.modified(bufnr_a, bufnr_b) return bufinfo(bufnr_a).lastused > bufinfo(bufnr_b).lastused end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/ffi.lua b/vim/config/nvim/lua/astronvim/utils/ffi.lua new file mode 100644 index 0000000..3b1487d --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/ffi.lua @@ -0,0 +1,20 @@ +-- ### AstroNvim C Extensions + +local ffi = require "ffi" + +-- Custom C extension to get direct fold information from Neovim +ffi.cdef [[ + typedef struct {} Error; + typedef struct {} win_T; + typedef struct { + int start; // line number where deepest fold starts + int level; // fold level, when zero other fields are N/A + int llevel; // lowest level that starts in v:lnum + int lines; // number of lines from v:lnum to end of closed fold + } foldinfo_T; + foldinfo_T fold_info(win_T* wp, int lnum); + win_T *find_window_by_handle(int Window, Error *err); + int compute_foldcolumn(win_T *wp, int col); +]] + +return ffi diff --git a/vim/config/nvim/lua/astronvim/utils/git.lua b/vim/config/nvim/lua/astronvim/utils/git.lua new file mode 100644 index 0000000..3f2a205 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/git.lua @@ -0,0 +1,215 @@ +--- ### Git LUA API +-- +-- This module can be loaded with `local git = require "astronvim.utils.git"` +-- +-- @module astronvim.utils.git +-- @copyright 2022 +-- @license GNU General Public License v3.0 + +local git = { url = "https://github.com/" } + +local function trim_or_nil(str) return type(str) == "string" and vim.trim(str) or nil end + +--- Run a git command from the AstroNvim installation directory +---@param args string|string[] the git arguments +---@return string|nil # The result of the command or nil if unsuccessful +function git.cmd(args, ...) + if type(args) == "string" then args = { args } end + return require("astronvim.utils").cmd(vim.list_extend({ "git", "-C", astronvim.install.home }, args), ...) +end + +--- Get the first worktree that a file belongs to +---@param file string? the file to check, defaults to the current file +---@param worktrees table[]? an array like table of worktrees with entries `toplevel` and `gitdir`, default retrieves from `vim.g.git_worktrees` +---@return table|nil # a table specifying the `toplevel` and `gitdir` of a worktree or nil if not found +function git.file_worktree(file, worktrees) + worktrees = worktrees or vim.g.git_worktrees + if not worktrees then return end + file = file or vim.fn.expand "%" + for _, worktree in ipairs(worktrees) do + if + require("astronvim.utils").cmd({ + "git", + "--work-tree", + worktree.toplevel, + "--git-dir", + worktree.gitdir, + "ls-files", + "--error-unmatch", + file, + }, false) + then + return worktree + end + end +end + +--- Check if the AstroNvim is able to reach the `git` command +---@return boolean # The result of running `git --help` +function git.available() return vim.fn.executable "git" == 1 end + +--- Check the git client version number +---@return table|nil # A table with version information or nil if there is an error +function git.git_version() + local output = git.cmd({ "--version" }, false) + if output then + local version_str = output:match "%d+%.%d+%.%d" + local major, min, patch = unpack(vim.tbl_map(tonumber, vim.split(version_str, "%."))) + return { major = major, min = min, patch = patch, str = version_str } + end +end + +--- Check if the AstroNvim home is a git repo +---@return string|nil # The result of the command +function git.is_repo() return git.cmd({ "rev-parse", "--is-inside-work-tree" }, false) end + +--- Fetch git remote +---@param remote string the remote to fetch +---@return string|nil # The result of the command +function git.fetch(remote, ...) return git.cmd({ "fetch", remote }, ...) end + +--- Pull the git repo +---@return string|nil # The result of the command +function git.pull(...) return git.cmd({ "pull", "--rebase" }, ...) end + +--- Checkout git target +---@param dest string the target to checkout +---@return string|nil # The result of the command +function git.checkout(dest, ...) return git.cmd({ "checkout", dest }, ...) end + +--- Hard reset to a git target +-- @param dest the target to hard reset to +---@return string|nil # The result of the command +function git.hard_reset(dest, ...) return git.cmd({ "reset", "--hard", dest }, ...) end + +--- Check if a branch contains a commit +---@param remote string the git remote to check +---@param branch string the git branch to check +---@param commit string the git commit to check for +---@return boolean # The result of the command +function git.branch_contains(remote, branch, commit, ...) + return git.cmd({ "merge-base", "--is-ancestor", commit, remote .. "/" .. branch }, ...) ~= nil +end + +--- Get the remote name for a given branch +---@param branch string the git branch to check +---@return string|nil # The name of the remote for the given branch +function git.branch_remote(branch, ...) return trim_or_nil(git.cmd({ "config", "branch." .. branch .. ".remote" }, ...)) end + +--- Add a git remote +---@param remote string the remote to add +---@param url string the url of the remote +---@return string|nil # The result of the command +function git.remote_add(remote, url, ...) return git.cmd({ "remote", "add", remote, url }, ...) end + +--- Update a git remote URL +---@param remote string the remote to update +---@param url string the new URL of the remote +---@return string|nil # The result of the command +function git.remote_update(remote, url, ...) return git.cmd({ "remote", "set-url", remote, url }, ...) end + +--- Get the URL of a given git remote +---@param remote string the remote to get the URL of +---@return string|nil # The url of the remote +function git.remote_url(remote, ...) return trim_or_nil(git.cmd({ "remote", "get-url", remote }, ...)) end + +--- Get branches from a git remote +---@param remote string the remote to setup branches for +---@param branch string the branch to setup +---@return string|nil # The result of the command +function git.remote_set_branches(remote, branch, ...) return git.cmd({ "remote", "set-branches", remote, branch }, ...) end + +--- Get the current version with git describe including tags +---@return string|nil # The current git describe string +function git.current_version(...) return trim_or_nil(git.cmd({ "describe", "--tags" }, ...)) end + +--- Get the current branch +---@return string|nil # The branch of the AstroNvim installation +function git.current_branch(...) return trim_or_nil(git.cmd({ "rev-parse", "--abbrev-ref", "HEAD" }, ...)) end + +--- Verify a reference +---@return string|nil # The referenced commit +function git.ref_verify(ref, ...) return trim_or_nil(git.cmd({ "rev-parse", "--verify", ref }, ...)) end + +--- Get the current head of the git repo +---@return string|nil # the head string +function git.local_head(...) return trim_or_nil(git.cmd({ "rev-parse", "HEAD" }, ...)) end + +--- Get the current head of a git remote +---@param remote string the remote to check +---@param branch string the branch to check +---@return string|nil # The head string of the remote branch +function git.remote_head(remote, branch, ...) + return trim_or_nil(git.cmd({ "rev-list", "-n", "1", remote .. "/" .. branch }, ...)) +end + +--- Get the commit hash of a given tag +---@param tag string the tag to resolve +---@return string|nil # The commit hash of a git tag +function git.tag_commit(tag, ...) return trim_or_nil(git.cmd({ "rev-list", "-n", "1", tag }, ...)) end + +--- Get the commit log between two commit hashes +---@param start_hash? string the start commit hash +---@param end_hash? string the end commit hash +---@return string[] # An array like table of commit messages +function git.get_commit_range(start_hash, end_hash, ...) + local range = start_hash and end_hash and start_hash .. ".." .. end_hash or nil + local log = git.cmd({ "log", "--no-merges", '--pretty="format:[%h] %s"', range }, ...) + return log and vim.fn.split(log, "\n") or {} +end + +--- Get a list of all tags with a regex filter +---@param search? string a regex to search the tags with (defaults to "v*" for version tags) +---@return string[] # An array like table of tags that match the search +function git.get_versions(search, ...) + local tags = git.cmd({ "tag", "-l", "--sort=version:refname", search == "latest" and "v*" or search }, ...) + return tags and vim.fn.split(tags, "\n") or {} +end + +--- Get the latest version of a list of versions +---@param versions? table a list of versions to search (defaults to all versions available) +---@return string|nil # The latest version from the array +function git.latest_version(versions, ...) + if not versions then versions = git.get_versions(...) end + return versions[#versions] +end + +--- Parse a remote url +---@param str string the remote to parse to a full git url +---@return string # The full git url for the given remote string +function git.parse_remote_url(str) + return vim.fn.match(str, require("astronvim.utils").url_matcher) == -1 + and git.url .. str .. (vim.fn.match(str, "/") == -1 and "/AstroNvim.git" or ".git") + or str +end + +--- Check if a Conventional Commit commit message is breaking or not +---@param commit string a commit message +---@return boolean true if the message is breaking, false if the commit message is not breaking +function git.is_breaking(commit) return vim.fn.match(commit, "\\[.*\\]\\s\\+\\w\\+\\((\\w\\+)\\)\\?!:") ~= -1 end + +--- Get a list of breaking commits from commit messages using Conventional Commit standard +---@param commits string[] an array like table of commit messages +---@return string[] # An array like table of commits that are breaking +function git.breaking_changes(commits) return vim.tbl_filter(git.is_breaking, commits) end + +--- Generate a table of commit messages for neovim's echo API with highlighting +---@param commits string[] an array like table of commit messages +---@return string[][] # An array like table of echo messages to provide to nvim_echo or astronvim.echo +function git.pretty_changelog(commits) + local changelog = {} + for _, commit in ipairs(commits) do + local hash, type, msg = commit:match "(%[.*%])(.*:)(.*)" + if hash and type and msg then + vim.list_extend(changelog, { + { hash, "DiffText" }, + { type, git.is_breaking(commit) and "DiffDelete" or "DiffChange" }, + { msg }, + { "\n" }, + }) + end + end + return changelog +end + +return git diff --git a/vim/config/nvim/lua/astronvim/utils/init.lua b/vim/config/nvim/lua/astronvim/utils/init.lua new file mode 100644 index 0000000..b1c405a --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/init.lua @@ -0,0 +1,342 @@ +--- ### AstroNvim Utilities +-- +-- Various utility functions to use within AstroNvim and user configurations. +-- +-- This module can be loaded with `local utils = require "astronvim.utils"` +-- +-- @module astronvim.utils +-- @copyright 2022 +-- @license GNU General Public License v3.0 + +local M = {} + +--- Merge extended options with a default table of options +---@param default? table The default table that you want to merge into +---@param opts? table The new options that should be merged with the default table +---@return table # The merged table +function M.extend_tbl(default, opts) + opts = opts or {} + return default and vim.tbl_deep_extend("force", default, opts) or opts +end + +--- Partially reload AstroNvim user settings. Includes core vim options, mappings, and highlights. This is an experimental feature and may lead to instabilities until restart. +---@param quiet? boolean Whether or not to notify on completion of reloading +---@return boolean # True if the reload was successful, False otherwise +function M.reload(quiet) + local was_modifiable = vim.opt.modifiable:get() + if not was_modifiable then vim.opt.modifiable = true end + local core_modules = { "astronvim.bootstrap", "astronvim.options", "astronvim.mappings" } + local modules = vim.tbl_filter(function(module) return module:find "^user%." end, vim.tbl_keys(package.loaded)) + + vim.tbl_map(require("plenary.reload").reload_module, vim.list_extend(modules, core_modules)) + + local success = true + for _, module in ipairs(core_modules) do + local status_ok, fault = pcall(require, module) + if not status_ok then + vim.api.nvim_err_writeln("Failed to load " .. module .. "\n\n" .. fault) + success = false + end + end + if not was_modifiable then vim.opt.modifiable = false end + if not quiet then -- if not quiet, then notify of result + if success then + M.notify("AstroNvim successfully reloaded", vim.log.levels.INFO) + else + M.notify("Error reloading AstroNvim...", vim.log.levels.ERROR) + end + end + vim.cmd.doautocmd "ColorScheme" + return success +end + +--- Insert one or more values into a list like table and maintain that you do not insert non-unique values (THIS MODIFIES `lst`) +---@param lst any[]|nil The list like table that you want to insert into +---@param vals any|any[] Either a list like table of values to be inserted or a single value to be inserted +---@return any[] # The modified list like table +function M.list_insert_unique(lst, vals) + if not lst then lst = {} end + assert(vim.tbl_islist(lst), "Provided table is not a list like table") + if not vim.tbl_islist(vals) then vals = { vals } end + local added = {} + vim.tbl_map(function(v) added[v] = true end, lst) + for _, val in ipairs(vals) do + if not added[val] then + table.insert(lst, val) + added[val] = true + end + end + return lst +end + +--- Call function if a condition is met +---@param func function The function to run +---@param condition boolean # Whether to run the function or not +---@return any|nil result # the result of the function running or nil +function M.conditional_func(func, condition, ...) + -- if the condition is true or no condition is provided, evaluate the function with the rest of the parameters and return the result + if condition and type(func) == "function" then return func(...) end +end + +--- Get an icon from the AstroNvim internal icons if it is available and return it +---@param kind string The kind of icon in astronvim.icons to retrieve +---@param padding? integer Padding to add to the end of the icon +---@param no_fallback? boolean Whether or not to disable fallback to text icon +---@return string icon +function M.get_icon(kind, padding, no_fallback) + if not vim.g.icons_enabled and no_fallback then return "" end + local icon_pack = vim.g.icons_enabled and "icons" or "text_icons" + if not M[icon_pack] then + M.icons = astronvim.user_opts("icons", require "astronvim.icons.nerd_font") + M.text_icons = astronvim.user_opts("text_icons", require "astronvim.icons.text") + end + local icon = M[icon_pack] and M[icon_pack][kind] + return icon and icon .. string.rep(" ", padding or 0) or "" +end + +--- Get a icon spinner table if it is available in the AstroNvim icons. Icons in format `kind1`,`kind2`, `kind3`, ... +---@param kind string The kind of icon to check for sequential entries of +---@return string[]|nil spinners # A collected table of spinning icons in sequential order or nil if none exist +function M.get_spinner(kind, ...) + local spinner = {} + repeat + local icon = M.get_icon(("%s%d"):format(kind, #spinner + 1), ...) + if icon ~= "" then table.insert(spinner, icon) end + until not icon or icon == "" + if #spinner > 0 then return spinner end +end + +--- Get highlight properties for a given highlight name +---@param name string The highlight group name +---@param fallback? table The fallback highlight properties +---@return table properties # the highlight group properties +function M.get_hlgroup(name, fallback) + if vim.fn.hlexists(name) == 1 then + local hl + if vim.api.nvim_get_hl then -- check for new neovim 0.9 API + hl = vim.api.nvim_get_hl(0, { name = name, link = false }) + if not hl.fg then hl.fg = "NONE" end + if not hl.bg then hl.bg = "NONE" end + else + hl = vim.api.nvim_get_hl_by_name(name, vim.o.termguicolors) + if not hl.foreground then hl.foreground = "NONE" end + if not hl.background then hl.background = "NONE" end + hl.fg, hl.bg = hl.foreground, hl.background + hl.ctermfg, hl.ctermbg = hl.fg, hl.bg + hl.sp = hl.special + end + return hl + end + return fallback or {} +end + +--- Serve a notification with a title of AstroNvim +---@param msg string The notification body +---@param type number|nil The type of the notification (:help vim.log.levels) +---@param opts? table The nvim-notify options to use (:help notify-options) +function M.notify(msg, type, opts) + vim.schedule(function() vim.notify(msg, type, M.extend_tbl({ title = "AstroNvim" }, opts)) end) +end + +--- Trigger an AstroNvim user event +---@param event string The event name to be appended to Astro +function M.event(event) + vim.schedule(function() vim.api.nvim_exec_autocmds("User", { pattern = "Astro" .. event, modeline = false }) end) +end + +--- Open a URL under the cursor with the current operating system +---@param path string The path of the file to open with the system opener +function M.system_open(path) + -- TODO: REMOVE WHEN DROPPING NEOVIM <0.10 + if vim.ui.open then return vim.ui.open(path) end + local cmd + if vim.fn.has "win32" == 1 and vim.fn.executable "explorer" == 1 then + cmd = { "cmd.exe", "/K", "explorer" } + elseif vim.fn.has "unix" == 1 and vim.fn.executable "xdg-open" == 1 then + cmd = { "xdg-open" } + elseif (vim.fn.has "mac" == 1 or vim.fn.has "unix" == 1) and vim.fn.executable "open" == 1 then + cmd = { "open" } + end + if not cmd then M.notify("Available system opening tool not found!", vim.log.levels.ERROR) end + vim.fn.jobstart(vim.fn.extend(cmd, { path or vim.fn.expand "" }), { detach = true }) +end + +--- Toggle a user terminal if it exists, if not then create a new one and save it +---@param opts string|table A terminal command string or a table of options for Terminal:new() (Check toggleterm.nvim documentation for table format) +function M.toggle_term_cmd(opts) + local terms = astronvim.user_terminals + -- if a command string is provided, create a basic table for Terminal:new() options + if type(opts) == "string" then opts = { cmd = opts, hidden = true } end + local num = vim.v.count > 0 and vim.v.count or 1 + -- if terminal doesn't exist yet, create it + if not terms[opts.cmd] then terms[opts.cmd] = {} end + if not terms[opts.cmd][num] then + if not opts.count then opts.count = vim.tbl_count(terms) * 100 + num end + if not opts.on_exit then opts.on_exit = function() terms[opts.cmd][num] = nil end end + terms[opts.cmd][num] = require("toggleterm.terminal").Terminal:new(opts) + end + -- toggle the terminal + terms[opts.cmd][num]:toggle() +end + +--- Create a button entity to use with the alpha dashboard +---@param sc string The keybinding string to convert to a button +---@param txt string The explanation text of what the keybinding does +---@return table # A button entity table for an alpha configuration +function M.alpha_button(sc, txt) + -- replace in shortcut text with LDR for nicer printing + local sc_ = sc:gsub("%s", ""):gsub("LDR", "") + -- if the leader is set, replace the text with the actual leader key for nicer printing + if vim.g.mapleader then sc = sc:gsub("LDR", vim.g.mapleader == " " and "SPC" or vim.g.mapleader) end + -- return the button entity to display the correct text and send the correct keybinding on press + return { + type = "button", + val = txt, + on_press = function() + local key = vim.api.nvim_replace_termcodes(sc_, true, false, true) + vim.api.nvim_feedkeys(key, "normal", false) + end, + opts = { + position = "center", + text = txt, + shortcut = sc, + cursor = -2, + width = 36, + align_shortcut = "right", + hl = "DashboardCenter", + hl_shortcut = "DashboardShortcut", + }, + } +end + +--- Check if a plugin is defined in lazy. Useful with lazy loading when a plugin is not necessarily loaded yet +---@param plugin string The plugin to search for +---@return boolean available # Whether the plugin is available +function M.is_available(plugin) + local lazy_config_avail, lazy_config = pcall(require, "lazy.core.config") + return lazy_config_avail and lazy_config.spec.plugins[plugin] ~= nil +end + +--- Resolve the options table for a given plugin with lazy +---@param plugin string The plugin to search for +---@return table opts # The plugin options +function M.plugin_opts(plugin) + local lazy_config_avail, lazy_config = pcall(require, "lazy.core.config") + local lazy_plugin_avail, lazy_plugin = pcall(require, "lazy.core.plugin") + local opts = {} + if lazy_config_avail and lazy_plugin_avail then + local spec = lazy_config.spec.plugins[plugin] + if spec then opts = lazy_plugin.values(spec, "opts") end + end + return opts +end + +--- A helper function to wrap a module function to require a plugin before running +---@param plugin string The plugin to call `require("lazy").load` with +---@param module table The system module where the functions live (e.g. `vim.ui`) +---@param func_names string|string[] The functions to wrap in the given module (e.g. `{ "ui", "select }`) +function M.load_plugin_with_func(plugin, module, func_names) + if type(func_names) == "string" then func_names = { func_names } end + for _, func in ipairs(func_names) do + local old_func = module[func] + module[func] = function(...) + module[func] = old_func + require("lazy").load { plugins = { plugin } } + module[func](...) + end + end +end + +--- Register queued which-key mappings +function M.which_key_register() + if M.which_key_queue then + local wk_avail, wk = pcall(require, "which-key") + if wk_avail then + for mode, registration in pairs(M.which_key_queue) do + wk.register(registration, { mode = mode }) + end + M.which_key_queue = nil + end + end +end + +--- Get an empty table of mappings with a key for each map mode +---@return table # a table with entries for each map mode +function M.empty_map_table() + local maps = {} + for _, mode in ipairs { "", "n", "v", "x", "s", "o", "!", "i", "l", "c", "t" } do + maps[mode] = {} + end + if vim.fn.has "nvim-0.10.0" == 1 then + for _, abbr_mode in ipairs { "ia", "ca", "!a" } do + maps[abbr_mode] = {} + end + end + return maps +end + +--- Table based API for setting keybindings +---@param map_table table A nested table where the first key is the vim mode, the second key is the key to map, and the value is the function to set the mapping to +---@param base? table A base set of options to set on every keybinding +function M.set_mappings(map_table, base) + -- iterate over the first keys for each mode + base = base or {} + for mode, maps in pairs(map_table) do + -- iterate over each keybinding set in the current mode + for keymap, options in pairs(maps) do + -- build the options for the command accordingly + if options then + local cmd = options + local keymap_opts = base + if type(options) == "table" then + cmd = options[1] + keymap_opts = vim.tbl_deep_extend("force", keymap_opts, options) + keymap_opts[1] = nil + end + if not cmd or keymap_opts.name then -- if which-key mapping, queue it + if not keymap_opts.name then keymap_opts.name = keymap_opts.desc end + if not M.which_key_queue then M.which_key_queue = {} end + if not M.which_key_queue[mode] then M.which_key_queue[mode] = {} end + M.which_key_queue[mode][keymap] = keymap_opts + else -- if not which-key mapping, set it + vim.keymap.set(mode, keymap, cmd, keymap_opts) + end + end + end + end + if package.loaded["which-key"] then M.which_key_register() end -- if which-key is loaded already, register +end + +--- regex used for matching a valid URL/URI string +M.url_matcher = + "\\v\\c%(%(h?ttps?|ftp|file|ssh|git)://|[a-z]+[@][a-z]+[.][a-z]+:)%([&:#*@~%_\\-=?!+;/0-9a-z]+%(%([.;/?]|[.][.]+)[&:#*@~%_\\-=?!+/0-9a-z]+|:\\d+|,%(%(%(h?ttps?|ftp|file|ssh|git)://|[a-z]+[@][a-z]+[.][a-z]+:)@![0-9a-z]+))*|\\([&:#*@~%_\\-=?!+;/.0-9a-z]*\\)|\\[[&:#*@~%_\\-=?!+;/.0-9a-z]*\\]|\\{%([&:#*@~%_\\-=?!+;/.0-9a-z]*|\\{[&:#*@~%_\\-=?!+;/.0-9a-z]*})\\})+" + +--- Delete the syntax matching rules for URLs/URIs if set +function M.delete_url_match() + for _, match in ipairs(vim.fn.getmatches()) do + if match.group == "HighlightURL" then vim.fn.matchdelete(match.id) end + end +end + +--- Add syntax matching rules for highlighting URLs/URIs +function M.set_url_match() + M.delete_url_match() + if vim.g.highlighturl_enabled then vim.fn.matchadd("HighlightURL", M.url_matcher, 15) end +end + +--- Run a shell command and capture the output and if the command succeeded or failed +---@param cmd string|string[] The terminal command to execute +---@param show_error? boolean Whether or not to show an unsuccessful command as an error to the user +---@return string|nil # The result of a successfully executed command or nil +function M.cmd(cmd, show_error) + if type(cmd) == "string" then cmd = { cmd } end + if vim.fn.has "win32" == 1 then cmd = vim.list_extend({ "cmd.exe", "/C" }, cmd) end + local result = vim.fn.system(cmd) + local success = vim.api.nvim_get_vvar "shell_error" == 0 + if not success and (show_error == nil or show_error) then + vim.api.nvim_err_writeln(("Error running command %s\nError message:\n%s"):format(table.concat(cmd, " "), result)) + end + return success and result:gsub("[\27\155][][()#;?%d]*[A-PRZcf-ntqry=><~]", "") or nil +end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/lsp.lua b/vim/config/nvim/lua/astronvim/utils/lsp.lua new file mode 100644 index 0000000..822e45f --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/lsp.lua @@ -0,0 +1,431 @@ +--- ### AstroNvim LSP Utils +-- +-- LSP related utility functions to use within AstroNvim and user configurations. +-- +-- This module can be loaded with `local lsp_utils = require("astronvim.utils.lsp")` +-- +-- @module astronvim.utils.lsp +-- @see astronvim.utils +-- @copyright 2022 +-- @license GNU General Public License v3.0 + +local M = {} +local tbl_contains = vim.tbl_contains +local tbl_isempty = vim.tbl_isempty +local user_opts = astronvim.user_opts + +local utils = require "astronvim.utils" +local conditional_func = utils.conditional_func +local is_available = utils.is_available +local extend_tbl = utils.extend_tbl + +local server_config = "lsp.config." +local setup_handlers = user_opts("lsp.setup_handlers", { + function(server, opts) require("lspconfig")[server].setup(opts) end, +}) + +M.diagnostics = { [0] = {}, {}, {}, {} } + +M.setup_diagnostics = function(signs) + local default_diagnostics = astronvim.user_opts("diagnostics", { + virtual_text = true, + signs = { active = signs }, + update_in_insert = true, + underline = true, + severity_sort = true, + float = { + focused = false, + style = "minimal", + border = "rounded", + source = "always", + header = "", + prefix = "", + }, + }) + M.diagnostics = { + -- diagnostics off + [0] = extend_tbl( + default_diagnostics, + { underline = false, virtual_text = false, signs = false, update_in_insert = false } + ), + -- status only + extend_tbl(default_diagnostics, { virtual_text = false, signs = false }), + -- virtual text off, signs on + extend_tbl(default_diagnostics, { virtual_text = false }), + -- all diagnostics on + default_diagnostics, + } + + vim.diagnostic.config(M.diagnostics[vim.g.diagnostics_mode]) +end + +M.formatting = user_opts("lsp.formatting", { format_on_save = { enabled = true }, disabled = {} }) +if type(M.formatting.format_on_save) == "boolean" then + M.formatting.format_on_save = { enabled = M.formatting.format_on_save } +end + +M.format_opts = vim.deepcopy(M.formatting) +M.format_opts.disabled = nil +M.format_opts.format_on_save = nil +M.format_opts.filter = function(client) + local filter = M.formatting.filter + local disabled = M.formatting.disabled or {} + -- check if client is fully disabled or filtered by function + return not (vim.tbl_contains(disabled, client.name) or (type(filter) == "function" and not filter(client))) +end + +--- Helper function to set up a given server with the Neovim LSP client +---@param server string The name of the server to be setup +M.setup = function(server) + -- if server doesn't exist, set it up from user server definition + local config_avail, config = pcall(require, "lspconfig.server_configurations." .. server) + if not config_avail or not config.default_config then + local server_definition = user_opts(server_config .. server) + if server_definition.cmd then require("lspconfig.configs")[server] = { default_config = server_definition } end + end + local opts = M.config(server) + local setup_handler = setup_handlers[server] or setup_handlers[1] + if not vim.tbl_contains(astronvim.lsp.skip_setup, server) and setup_handler then setup_handler(server, opts) end +end + +--- Helper function to check if any active LSP clients given a filter provide a specific capability +---@param capability string The server capability to check for (example: "documentFormattingProvider") +---@param filter vim.lsp.get_active_clients.filter|nil (table|nil) A table with +--- key-value pairs used to filter the returned clients. +--- The available keys are: +--- - id (number): Only return clients with the given id +--- - bufnr (number): Only return clients attached to this buffer +--- - name (string): Only return clients with the given name +---@return boolean # Whether or not any of the clients provide the capability +function M.has_capability(capability, filter) + for _, client in ipairs(vim.lsp.get_active_clients(filter)) do + if client.supports_method(capability) then return true end + end + return false +end + +local function add_buffer_autocmd(augroup, bufnr, autocmds) + if not vim.tbl_islist(autocmds) then autocmds = { autocmds } end + local cmds_found, cmds = pcall(vim.api.nvim_get_autocmds, { group = augroup, buffer = bufnr }) + if not cmds_found or vim.tbl_isempty(cmds) then + vim.api.nvim_create_augroup(augroup, { clear = false }) + for _, autocmd in ipairs(autocmds) do + local events = autocmd.events + autocmd.events = nil + autocmd.group = augroup + autocmd.buffer = bufnr + vim.api.nvim_create_autocmd(events, autocmd) + end + end +end + +local function del_buffer_autocmd(augroup, bufnr) + local cmds_found, cmds = pcall(vim.api.nvim_get_autocmds, { group = augroup, buffer = bufnr }) + if cmds_found then vim.tbl_map(function(cmd) vim.api.nvim_del_autocmd(cmd.id) end, cmds) end +end + +--- The `on_attach` function used by AstroNvim +---@param client table The LSP client details when attaching +---@param bufnr number The buffer that the LSP client is attaching to +M.on_attach = function(client, bufnr) + local lsp_mappings = require("astronvim.utils").empty_map_table() + + lsp_mappings.n["ld"] = { function() vim.diagnostic.open_float() end, desc = "Hover diagnostics" } + lsp_mappings.n["[d"] = { function() vim.diagnostic.goto_prev() end, desc = "Previous diagnostic" } + lsp_mappings.n["]d"] = { function() vim.diagnostic.goto_next() end, desc = "Next diagnostic" } + lsp_mappings.n["gl"] = { function() vim.diagnostic.open_float() end, desc = "Hover diagnostics" } + + if is_available "telescope.nvim" then + lsp_mappings.n["lD"] = + { function() require("telescope.builtin").diagnostics() end, desc = "Search diagnostics" } + end + + if is_available "mason-lspconfig.nvim" then + lsp_mappings.n["li"] = { "LspInfo", desc = "LSP information" } + end + + if is_available "null-ls.nvim" then + lsp_mappings.n["lI"] = { "NullLsInfo", desc = "Null-ls information" } + end + + if client.supports_method "textDocument/codeAction" then + lsp_mappings.n["la"] = { + function() vim.lsp.buf.code_action() end, + desc = "LSP code action", + } + lsp_mappings.v["la"] = lsp_mappings.n["la"] + end + + if client.supports_method "textDocument/codeLens" then + add_buffer_autocmd("lsp_codelens_refresh", bufnr, { + events = { "InsertLeave", "BufEnter" }, + desc = "Refresh codelens", + callback = function() + if not M.has_capability("textDocument/codeLens", { bufnr = bufnr }) then + del_buffer_autocmd("lsp_codelens_refresh", bufnr) + return + end + if vim.g.codelens_enabled then vim.lsp.codelens.refresh() end + end, + }) + if vim.g.codelens_enabled then vim.lsp.codelens.refresh() end + lsp_mappings.n["ll"] = { + function() vim.lsp.codelens.refresh() end, + desc = "LSP CodeLens refresh", + } + lsp_mappings.n["lL"] = { + function() vim.lsp.codelens.run() end, + desc = "LSP CodeLens run", + } + end + + if client.supports_method "textDocument/declaration" then + lsp_mappings.n["gD"] = { + function() vim.lsp.buf.declaration() end, + desc = "Declaration of current symbol", + } + end + + if client.supports_method "textDocument/definition" then + lsp_mappings.n["gd"] = { + function() vim.lsp.buf.definition() end, + desc = "Show the definition of current symbol", + } + end + + if client.supports_method "textDocument/formatting" and not tbl_contains(M.formatting.disabled, client.name) then + lsp_mappings.n["lf"] = { + function() vim.lsp.buf.format(M.format_opts) end, + desc = "Format buffer", + } + lsp_mappings.v["lf"] = lsp_mappings.n["lf"] + + vim.api.nvim_buf_create_user_command( + bufnr, + "Format", + function() vim.lsp.buf.format(M.format_opts) end, + { desc = "Format file with LSP" } + ) + local autoformat = M.formatting.format_on_save + local filetype = vim.api.nvim_get_option_value("filetype", { buf = bufnr }) + if + autoformat.enabled + and (tbl_isempty(autoformat.allow_filetypes or {}) or tbl_contains(autoformat.allow_filetypes, filetype)) + and (tbl_isempty(autoformat.ignore_filetypes or {}) or not tbl_contains(autoformat.ignore_filetypes, filetype)) + then + add_buffer_autocmd("lsp_auto_format", bufnr, { + events = "BufWritePre", + desc = "autoformat on save", + callback = function() + if not M.has_capability("textDocument/formatting", { bufnr = bufnr }) then + del_buffer_autocmd("lsp_auto_format", bufnr) + return + end + local autoformat_enabled = vim.b.autoformat_enabled + if autoformat_enabled == nil then autoformat_enabled = vim.g.autoformat_enabled end + if autoformat_enabled and ((not autoformat.filter) or autoformat.filter(bufnr)) then + vim.lsp.buf.format(extend_tbl(M.format_opts, { bufnr = bufnr })) + end + end, + }) + lsp_mappings.n["uf"] = { + function() require("astronvim.utils.ui").toggle_buffer_autoformat() end, + desc = "Toggle autoformatting (buffer)", + } + lsp_mappings.n["uF"] = { + function() require("astronvim.utils.ui").toggle_autoformat() end, + desc = "Toggle autoformatting (global)", + } + end + end + + if client.supports_method "textDocument/documentHighlight" then + add_buffer_autocmd("lsp_document_highlight", bufnr, { + { + events = { "CursorHold", "CursorHoldI" }, + desc = "highlight references when cursor holds", + callback = function() + if not M.has_capability("textDocument/documentHighlight", { bufnr = bufnr }) then + del_buffer_autocmd("lsp_document_highlight", bufnr) + return + end + vim.lsp.buf.document_highlight() + end, + }, + { + events = { "CursorMoved", "CursorMovedI" }, + desc = "clear references when cursor moves", + callback = function() vim.lsp.buf.clear_references() end, + }, + }) + end + + if client.supports_method "textDocument/hover" then + -- TODO: Remove mapping after dropping support for Neovim v0.9, it's automatic + if vim.fn.has "nvim-0.10" == 0 then + lsp_mappings.n["K"] = { + function() vim.lsp.buf.hover() end, + desc = "Hover symbol details", + } + end + end + + if client.supports_method "textDocument/implementation" then + lsp_mappings.n["gI"] = { + function() vim.lsp.buf.implementation() end, + desc = "Implementation of current symbol", + } + end + + if client.supports_method "textDocument/inlayHint" then + if vim.b.inlay_hints_enabled == nil then vim.b.inlay_hints_enabled = vim.g.inlay_hints_enabled end + -- TODO: remove check after dropping support for Neovim v0.9 + if vim.lsp.inlay_hint then + if vim.b.inlay_hints_enabled then vim.lsp.inlay_hint(bufnr, true) end + lsp_mappings.n["uH"] = { + function() require("astronvim.utils.ui").toggle_buffer_inlay_hints(bufnr) end, + desc = "Toggle LSP inlay hints (buffer)", + } + end + end + + if client.supports_method "textDocument/references" then + lsp_mappings.n["gr"] = { + function() vim.lsp.buf.references() end, + desc = "References of current symbol", + } + lsp_mappings.n["lR"] = { + function() vim.lsp.buf.references() end, + desc = "Search references", + } + end + + if client.supports_method "textDocument/rename" then + lsp_mappings.n["lr"] = { + function() vim.lsp.buf.rename() end, + desc = "Rename current symbol", + } + end + + if client.supports_method "textDocument/signatureHelp" then + lsp_mappings.n["lh"] = { + function() vim.lsp.buf.signature_help() end, + desc = "Signature help", + } + end + + if client.supports_method "textDocument/typeDefinition" then + lsp_mappings.n["gy"] = { + function() vim.lsp.buf.type_definition() end, + desc = "Definition of current type", + } + end + + if client.supports_method "workspace/symbol" then + lsp_mappings.n["lG"] = { function() vim.lsp.buf.workspace_symbol() end, desc = "Search workspace symbols" } + end + + if client.supports_method "textDocument/semanticTokens/full" and vim.lsp.semantic_tokens then + if vim.b.semantic_tokens_enabled == nil then vim.b.semantic_tokens_enabled = vim.g.semantic_tokens_enabled end + if not vim.g.semantic_tokens_enabled then vim.lsp.semantic_tokens["stop"](bufnr, client.id) end + lsp_mappings.n["uY"] = { + function() require("astronvim.utils.ui").toggle_buffer_semantic_tokens(bufnr) end, + desc = "Toggle LSP semantic highlight (buffer)", + } + end + + if is_available "telescope.nvim" then -- setup telescope mappings if available + if lsp_mappings.n.gd then lsp_mappings.n.gd[1] = function() require("telescope.builtin").lsp_definitions() end end + if lsp_mappings.n.gI then + lsp_mappings.n.gI[1] = function() require("telescope.builtin").lsp_implementations() end + end + if lsp_mappings.n.gr then lsp_mappings.n.gr[1] = function() require("telescope.builtin").lsp_references() end end + if lsp_mappings.n["lR"] then + lsp_mappings.n["lR"][1] = function() require("telescope.builtin").lsp_references() end + end + if lsp_mappings.n.gy then + lsp_mappings.n.gy[1] = function() require("telescope.builtin").lsp_type_definitions() end + end + if lsp_mappings.n["lG"] then + lsp_mappings.n["lG"][1] = function() + vim.ui.input({ prompt = "Symbol Query: " }, function(query) + if query then require("telescope.builtin").lsp_workspace_symbols { query = query } end + end) + end + end + end + + if not vim.tbl_isempty(lsp_mappings.v) then + lsp_mappings.v["l"] = { desc = utils.get_icon("ActiveLSP", 1, true) .. "LSP" } + end + utils.set_mappings(user_opts("lsp.mappings", lsp_mappings), { buffer = bufnr }) + + for id, _ in pairs(astronvim.lsp.progress) do -- clear lingering progress messages + if not next(vim.lsp.get_active_clients { id = tonumber(id:match "^%d+") }) then astronvim.lsp.progress[id] = nil end + end + + local on_attach_override = user_opts("lsp.on_attach", nil, false) + conditional_func(on_attach_override, true, client, bufnr) +end + +--- The default AstroNvim LSP capabilities +M.capabilities = vim.lsp.protocol.make_client_capabilities() +M.capabilities.textDocument.completion.completionItem.documentationFormat = { "markdown", "plaintext" } +M.capabilities.textDocument.completion.completionItem.snippetSupport = true +M.capabilities.textDocument.completion.completionItem.preselectSupport = true +M.capabilities.textDocument.completion.completionItem.insertReplaceSupport = true +M.capabilities.textDocument.completion.completionItem.labelDetailsSupport = true +M.capabilities.textDocument.completion.completionItem.deprecatedSupport = true +M.capabilities.textDocument.completion.completionItem.commitCharactersSupport = true +M.capabilities.textDocument.completion.completionItem.tagSupport = { valueSet = { 1 } } +M.capabilities.textDocument.completion.completionItem.resolveSupport = + { properties = { "documentation", "detail", "additionalTextEdits" } } +M.capabilities.textDocument.foldingRange = { dynamicRegistration = false, lineFoldingOnly = true } +M.capabilities = user_opts("lsp.capabilities", M.capabilities) +M.flags = user_opts "lsp.flags" + +--- Get the server configuration for a given language server to be provided to the server's `setup()` call +---@param server_name string The name of the server +---@return table # The table of LSP options used when setting up the given language server +function M.config(server_name) + local server = require("lspconfig")[server_name] + local lsp_opts = extend_tbl( + extend_tbl(server.document_config.default_config, server), + { capabilities = M.capabilities, flags = M.flags } + ) + if server_name == "jsonls" then -- by default add json schemas + local schemastore_avail, schemastore = pcall(require, "schemastore") + if schemastore_avail then + lsp_opts.settings = { json = { schemas = schemastore.json.schemas(), validate = { enable = true } } } + end + end + if server_name == "yamlls" then -- by default add yaml schemas + local schemastore_avail, schemastore = pcall(require, "schemastore") + if schemastore_avail then lsp_opts.settings = { yaml = { schemas = schemastore.yaml.schemas() } } end + end + if server_name == "lua_ls" then -- by default initialize neodev and disable third party checking + pcall(require, "neodev") + lsp_opts.before_init = function(param, config) + if vim.b.neodev_enabled then + for _, astronvim_config in ipairs(astronvim.supported_configs) do + if param.rootPath:match(astronvim_config) then + table.insert(config.settings.Lua.workspace.library, astronvim.install.home .. "/lua") + break + end + end + end + end + lsp_opts.settings = { Lua = { workspace = { checkThirdParty = false } } } + end + local opts = user_opts(server_config .. server_name, lsp_opts) + local old_on_attach = server.on_attach + local user_on_attach = opts.on_attach + opts.on_attach = function(client, bufnr) + conditional_func(old_on_attach, true, client, bufnr) + M.on_attach(client, bufnr) + conditional_func(user_on_attach, true, client, bufnr) + end + return opts +end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/mason.lua b/vim/config/nvim/lua/astronvim/utils/mason.lua new file mode 100644 index 0000000..5cbd0e6 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/mason.lua @@ -0,0 +1,119 @@ +--- ### Mason Utils +-- +-- Mason related utility functions to use within AstroNvim and user configurations. +-- +-- This module can be loaded with `local mason_utils = require("astronvim.utils.mason")` +-- +-- @module astronvim.utils.mason +-- @see astronvim.utils +-- @copyright 2022 +-- @license GNU General Public License v3.0 + +local M = {} + +local utils = require "astronvim.utils" +local astroevent = utils.event +local function mason_notify(msg, type) utils.notify(msg, type, { title = "Mason" }) end + +--- Update specified mason packages, or just update the registries if no packages are listed +---@param pkg_names? string|string[] The package names as defined in Mason (Not mason-lspconfig or mason-null-ls) if the value is nil then it will just update the registries +---@param auto_install? boolean whether or not to install a package that is not currently installed (default: True) +function M.update(pkg_names, auto_install) + pkg_names = pkg_names or {} + if type(pkg_names) == "string" then pkg_names = { pkg_names } end + if auto_install == nil then auto_install = true end + local registry_avail, registry = pcall(require, "mason-registry") + if not registry_avail then + vim.api.nvim_err_writeln "Unable to access mason registry" + return + end + + registry.update(vim.schedule_wrap(function(success, updated_registries) + if success then + local count = #updated_registries + if vim.tbl_count(pkg_names) == 0 then + mason_notify(("Successfully updated %d %s."):format(count, count == 1 and "registry" or "registries")) + end + for _, pkg_name in ipairs(pkg_names) do + local pkg_avail, pkg = pcall(registry.get_package, pkg_name) + if not pkg_avail then + mason_notify(("`%s` is not available"):format(pkg_name), vim.log.levels.ERROR) + else + if not pkg:is_installed() then + if auto_install then + mason_notify(("Installing `%s`"):format(pkg.name)) + pkg:install() + else + mason_notify(("`%s` not installed"):format(pkg.name), vim.log.levels.WARN) + end + else + pkg:check_new_version(function(update_available, version) + if update_available then + mason_notify(("Updating `%s` to %s"):format(pkg.name, version.latest_version)) + pkg:install():on("closed", function() mason_notify(("Updated %s"):format(pkg.name)) end) + else + mason_notify(("No updates available for `%s`"):format(pkg.name)) + end + end) + end + end + end + else + mason_notify(("Failed to update registries: %s"):format(updated_registries), vim.log.levels.ERROR) + end + end)) +end + +--- Update all packages in Mason +function M.update_all() + local registry_avail, registry = pcall(require, "mason-registry") + if not registry_avail then + vim.api.nvim_err_writeln "Unable to access mason registry" + return + end + + mason_notify "Checking for package updates..." + registry.update(vim.schedule_wrap(function(success, updated_registries) + if success then + local installed_pkgs = registry.get_installed_packages() + local running = #installed_pkgs + local no_pkgs = running == 0 + + if no_pkgs then + mason_notify "No updates available" + astroevent "MasonUpdateCompleted" + else + local updated = false + for _, pkg in ipairs(installed_pkgs) do + pkg:check_new_version(function(update_available, version) + if update_available then + updated = true + mason_notify(("Updating `%s` to %s"):format(pkg.name, version.latest_version)) + pkg:install():on("closed", function() + running = running - 1 + if running == 0 then + mason_notify "Update Complete" + astroevent "MasonUpdateCompleted" + end + end) + else + running = running - 1 + if running == 0 then + if updated then + mason_notify "Update Complete" + else + mason_notify "No updates available" + end + astroevent "MasonUpdateCompleted" + end + end + end) + end + end + else + mason_notify(("Failed to update registries: %s"):format(updated_registries), vim.log.levels.ERROR) + end + end)) +end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/status.lua b/vim/config/nvim/lua/astronvim/utils/status.lua new file mode 100644 index 0000000..1e44fd3 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/status.lua @@ -0,0 +1,10 @@ +return { + component = require "astronvim.utils.status.component", + condition = require "astronvim.utils.status.condition", + env = require "astronvim.utils.status.env", + heirline = require "astronvim.utils.status.heirline", + hl = require "astronvim.utils.status.hl", + init = require "astronvim.utils.status.init", + provider = require "astronvim.utils.status.provider", + utils = require "astronvim.utils.status.utils", +} diff --git a/vim/config/nvim/lua/astronvim/utils/status/component.lua b/vim/config/nvim/lua/astronvim/utils/status/component.lua new file mode 100644 index 0000000..769e077 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/status/component.lua @@ -0,0 +1,435 @@ +--- ### AstroNvim Status Components +-- +-- Statusline related component functions to use with Heirline +-- +-- This module can be loaded with `local component = require "astronvim.utils.status.component"` +-- +-- @module astronvim.utils.status.component +-- @copyright 2023 +-- @license GNU General Public License v3.0 + +local M = {} + +local condition = require "astronvim.utils.status.condition" +local env = require "astronvim.utils.status.env" +local hl = require "astronvim.utils.status.hl" +local init = require "astronvim.utils.status.init" +local provider = require "astronvim.utils.status.provider" +local status_utils = require "astronvim.utils.status.utils" + +local utils = require "astronvim.utils" +local buffer_utils = require "astronvim.utils.buffer" +local extend_tbl = utils.extend_tbl +local get_icon = utils.get_icon +local is_available = utils.is_available + +--- A Heirline component for filling in the empty space of the bar +---@param opts? table options for configuring the other fields of the heirline component +---@return table # The heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.fill() +function M.fill(opts) return extend_tbl({ provider = provider.fill() }, opts) end + +--- A function to build a set of children components for an entire file information section +---@param opts? table options for configuring file_icon, filename, filetype, file_modified, file_read_only, and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.file_info() +function M.file_info(opts) + opts = extend_tbl({ + file_icon = { hl = hl.file_icon "statusline", padding = { left = 1, right = 1 } }, + filename = {}, + file_modified = { padding = { left = 1 } }, + file_read_only = { padding = { left = 1 } }, + surround = { separator = "left", color = "file_info_bg", condition = condition.has_filetype }, + hl = hl.get_attributes "file_info", + }, opts) + return M.builder(status_utils.setup_providers(opts, { + "file_icon", + "unique_path", + "filename", + "filetype", + "file_modified", + "file_read_only", + "close_button", + })) +end + +--- A function with different file_info defaults specifically for use in the tabline +---@param opts? table options for configuring file_icon, filename, filetype, file_modified, file_read_only, and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.tabline_file_info() +function M.tabline_file_info(opts) + return M.file_info(extend_tbl({ + file_icon = { + condition = function(self) return not self._show_picker end, + hl = hl.file_icon "tabline", + }, + unique_path = { + hl = function(self) return hl.get_attributes(self.tab_type .. "_path") end, + }, + close_button = { + hl = function(self) return hl.get_attributes(self.tab_type .. "_close") end, + padding = { left = 1, right = 1 }, + on_click = { + callback = function(_, minwid) buffer_utils.close(minwid) end, + minwid = function(self) return self.bufnr end, + name = "heirline_tabline_close_buffer_callback", + }, + }, + padding = { left = 1, right = 1 }, + hl = function(self) + local tab_type = self.tab_type + if self._show_picker and self.tab_type ~= "buffer_active" then tab_type = "buffer_visible" end + return hl.get_attributes(tab_type) + end, + surround = false, + }, opts)) +end + +--- A function to build a set of children components for an entire navigation section +---@param opts? table options for configuring ruler, percentage, scrollbar, and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.nav() +function M.nav(opts) + opts = extend_tbl({ + ruler = {}, + percentage = { padding = { left = 1 } }, + scrollbar = { padding = { left = 1 }, hl = { fg = "scrollbar" } }, + surround = { separator = "right", color = "nav_bg" }, + hl = hl.get_attributes "nav", + update = { "CursorMoved", "CursorMovedI", "BufEnter" }, + }, opts) + return M.builder(status_utils.setup_providers(opts, { "ruler", "percentage", "scrollbar" })) +end + +--- A function to build a set of children components for information shown in the cmdline +---@param opts? table options for configuring macro recording, search count, and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.cmd_info() +function M.cmd_info(opts) + opts = extend_tbl({ + macro_recording = { + icon = { kind = "MacroRecording", padding = { right = 1 } }, + condition = condition.is_macro_recording, + update = { + "RecordingEnter", + "RecordingLeave", + callback = vim.schedule_wrap(function() vim.cmd.redrawstatus() end), + }, + }, + search_count = { + icon = { kind = "Search", padding = { right = 1 } }, + padding = { left = 1 }, + condition = condition.is_hlsearch, + }, + showcmd = { + padding = { left = 1 }, + condition = condition.is_statusline_showcmd, + }, + surround = { + separator = "center", + color = "cmd_info_bg", + condition = function() + return condition.is_hlsearch() or condition.is_macro_recording() or condition.is_statusline_showcmd() + end, + }, + condition = function() return vim.opt.cmdheight:get() == 0 end, + hl = hl.get_attributes "cmd_info", + }, opts) + return M.builder(status_utils.setup_providers(opts, { "macro_recording", "search_count", "showcmd" })) +end + +--- A function to build a set of children components for a mode section +---@param opts? table options for configuring mode_text, paste, spell, and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.mode { mode_text = true } +function M.mode(opts) + opts = extend_tbl({ + mode_text = false, + paste = false, + spell = false, + surround = { separator = "left", color = hl.mode_bg }, + hl = hl.get_attributes "mode", + update = { + "ModeChanged", + pattern = "*:*", + callback = vim.schedule_wrap(function() vim.cmd.redrawstatus() end), + }, + }, opts) + if not opts["mode_text"] then opts.str = { str = " " } end + return M.builder(status_utils.setup_providers(opts, { "mode_text", "str", "paste", "spell" })) +end + +--- A function to build a set of children components for an LSP breadcrumbs section +---@param opts? table options for configuring breadcrumbs and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.breadcumbs() +function M.breadcrumbs(opts) + opts = extend_tbl({ padding = { left = 1 }, condition = condition.aerial_available, update = "CursorMoved" }, opts) + opts.init = init.breadcrumbs(opts) + return opts +end + +--- A function to build a set of children components for the current file path +---@param opts? table options for configuring path and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.separated_path() +function M.separated_path(opts) + opts = extend_tbl({ padding = { left = 1 }, update = { "BufEnter", "DirChanged" } }, opts) + opts.init = init.separated_path(opts) + return opts +end + +--- A function to build a set of children components for a git branch section +---@param opts? table options for configuring git branch and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.git_branch() +function M.git_branch(opts) + opts = extend_tbl({ + git_branch = { icon = { kind = "GitBranch", padding = { right = 1 } } }, + surround = { separator = "left", color = "git_branch_bg", condition = condition.is_git_repo }, + hl = hl.get_attributes "git_branch", + on_click = { + name = "heirline_branch", + callback = function() + if is_available "telescope.nvim" then + vim.defer_fn(function() require("telescope.builtin").git_branches() end, 100) + end + end, + }, + update = { "User", pattern = "GitSignsUpdate" }, + init = init.update_events { "BufEnter" }, + }, opts) + return M.builder(status_utils.setup_providers(opts, { "git_branch" })) +end + +--- A function to build a set of children components for a git difference section +---@param opts? table options for configuring git changes and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.git_diff() +function M.git_diff(opts) + opts = extend_tbl({ + added = { icon = { kind = "GitAdd", padding = { left = 1, right = 1 } } }, + changed = { icon = { kind = "GitChange", padding = { left = 1, right = 1 } } }, + removed = { icon = { kind = "GitDelete", padding = { left = 1, right = 1 } } }, + hl = hl.get_attributes "git_diff", + on_click = { + name = "heirline_git", + callback = function() + if is_available "telescope.nvim" then + vim.defer_fn(function() require("telescope.builtin").git_status() end, 100) + end + end, + }, + surround = { separator = "left", color = "git_diff_bg", condition = condition.git_changed }, + update = { "User", pattern = "GitSignsUpdate" }, + init = init.update_events { "BufEnter" }, + }, opts) + return M.builder(status_utils.setup_providers(opts, { "added", "changed", "removed" }, function(p_opts, p) + local out = status_utils.build_provider(p_opts, p) + if out then + out.provider = "git_diff" + out.opts.type = p + if out.hl == nil then out.hl = { fg = "git_" .. p } end + end + return out + end)) +end + +--- A function to build a set of children components for a diagnostics section +---@param opts? table options for configuring diagnostic providers and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.diagnostics() +function M.diagnostics(opts) + opts = extend_tbl({ + ERROR = { icon = { kind = "DiagnosticError", padding = { left = 1, right = 1 } } }, + WARN = { icon = { kind = "DiagnosticWarn", padding = { left = 1, right = 1 } } }, + INFO = { icon = { kind = "DiagnosticInfo", padding = { left = 1, right = 1 } } }, + HINT = { icon = { kind = "DiagnosticHint", padding = { left = 1, right = 1 } } }, + surround = { separator = "left", color = "diagnostics_bg", condition = condition.has_diagnostics }, + hl = hl.get_attributes "diagnostics", + on_click = { + name = "heirline_diagnostic", + callback = function() + if is_available "telescope.nvim" then + vim.defer_fn(function() require("telescope.builtin").diagnostics() end, 100) + end + end, + }, + update = { "DiagnosticChanged", "BufEnter" }, + }, opts) + return M.builder(status_utils.setup_providers(opts, { "ERROR", "WARN", "INFO", "HINT" }, function(p_opts, p) + local out = status_utils.build_provider(p_opts, p) + if out then + out.provider = "diagnostics" + out.opts.severity = p + if out.hl == nil then out.hl = { fg = "diag_" .. p } end + end + return out + end)) +end + +--- A function to build a set of children components for a Treesitter section +---@param opts? table options for configuring diagnostic providers and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.treesitter() +function M.treesitter(opts) + opts = extend_tbl({ + str = { str = "TS", icon = { kind = "ActiveTS", padding = { right = 1 } } }, + surround = { + separator = "right", + color = "treesitter_bg", + condition = condition.treesitter_available, + }, + hl = hl.get_attributes "treesitter", + update = { "OptionSet", pattern = "syntax" }, + init = init.update_events { "BufEnter" }, + }, opts) + return M.builder(status_utils.setup_providers(opts, { "str" })) +end + +--- A function to build a set of children components for an LSP section +---@param opts? table options for configuring lsp progress and client_name providers and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.lsp() +function M.lsp(opts) + opts = extend_tbl({ + lsp_progress = { + str = "", + padding = { right = 1 }, + update = { + "User", + pattern = "AstroLspProgress", + callback = vim.schedule_wrap(function() vim.cmd.redrawstatus() end), + }, + }, + lsp_client_names = { + str = "LSP", + update = { + "LspAttach", + "LspDetach", + "BufEnter", + callback = vim.schedule_wrap(function() vim.cmd.redrawstatus() end), + }, + icon = { kind = "ActiveLSP", padding = { right = 2 } }, + }, + hl = hl.get_attributes "lsp", + surround = { separator = "right", color = "lsp_bg", condition = condition.lsp_attached }, + on_click = { + name = "heirline_lsp", + callback = function() + vim.defer_fn(function() vim.cmd.LspInfo() end, 100) + end, + }, + }, opts) + return M.builder(status_utils.setup_providers( + opts, + { "lsp_progress", "lsp_client_names" }, + function(p_opts, p, i) + return p_opts + and { + flexible = i, + status_utils.build_provider(p_opts, provider[p](p_opts)), + status_utils.build_provider(p_opts, provider.str(p_opts)), + } + or false + end + )) +end + +--- A function to build a set of components for a foldcolumn section in a statuscolumn +---@param opts? table options for configuring foldcolumn and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.foldcolumn() +function M.foldcolumn(opts) + opts = extend_tbl({ + foldcolumn = { padding = { right = 1 } }, + condition = condition.foldcolumn_enabled, + on_click = { + name = "fold_click", + callback = function(...) + local char = status_utils.statuscolumn_clickargs(...).char + local fillchars = vim.opt_local.fillchars:get() + if char == (fillchars.foldopen or get_icon "FoldOpened") then + vim.cmd "norm! zc" + elseif char == (fillchars.foldcolse or get_icon "FoldClosed") then + vim.cmd "norm! zo" + end + end, + }, + }, opts) + return M.builder(status_utils.setup_providers(opts, { "foldcolumn" })) +end + +--- A function to build a set of components for a numbercolumn section in statuscolumn +---@param opts? table options for configuring numbercolumn and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.numbercolumn() +function M.numbercolumn(opts) + opts = extend_tbl({ + numbercolumn = { padding = { right = 1 } }, + condition = condition.numbercolumn_enabled, + on_click = { + name = "line_click", + callback = function(...) + local args = status_utils.statuscolumn_clickargs(...) + if args.mods:find "c" then + local dap_avail, dap = pcall(require, "dap") + if dap_avail then vim.schedule(dap.toggle_breakpoint) end + end + end, + }, + }, opts) + return M.builder(status_utils.setup_providers(opts, { "numbercolumn" })) +end + +--- A function to build a set of components for a signcolumn section in statuscolumn +---@param opts? table options for configuring signcolumn and the overall padding +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").component.signcolumn() +function M.signcolumn(opts) + opts = extend_tbl({ + signcolumn = {}, + condition = condition.signcolumn_enabled, + on_click = { + name = "sign_click", + callback = function(...) + local args = status_utils.statuscolumn_clickargs(...) + if args.sign and args.sign.name and env.sign_handlers[args.sign.name] then + env.sign_handlers[args.sign.name](args) + end + end, + }, + }, opts) + return M.builder(status_utils.setup_providers(opts, { "signcolumn" })) +end + +--- A general function to build a section of astronvim status providers with highlights, conditions, and section surrounding +---@param opts? table a list of components to build into a section +---@return table # The Heirline component table +-- @usage local heirline_component = require("astronvim.utils.status").components.builder({ { provider = "file_icon", opts = { padding = { right = 1 } } }, { provider = "filename" } }) +function M.builder(opts) + opts = extend_tbl({ padding = { left = 0, right = 0 } }, opts) + local children = {} + if opts.padding.left > 0 then -- add left padding + table.insert(children, { provider = status_utils.pad_string(" ", { left = opts.padding.left - 1 }) }) + end + for key, entry in pairs(opts) do + if + type(key) == "number" + and type(entry) == "table" + and provider[entry.provider] + and (entry.opts == nil or type(entry.opts) == "table") + then + entry.provider = provider[entry.provider](entry.opts) + end + children[key] = entry + end + if opts.padding.right > 0 then -- add right padding + table.insert(children, { provider = status_utils.pad_string(" ", { right = opts.padding.right - 1 }) }) + end + return opts.surround + and status_utils.surround(opts.surround.separator, opts.surround.color, children, opts.surround.condition) + or children +end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/status/condition.lua b/vim/config/nvim/lua/astronvim/utils/status/condition.lua new file mode 100644 index 0000000..ae1598c --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/status/condition.lua @@ -0,0 +1,138 @@ +--- ### AstroNvim Status Conditions +-- +-- Statusline related condition functions to use with Heirline +-- +-- This module can be loaded with `local condition = require "astronvim.utils.status.condition"` +-- +-- @module astronvim.utils.status.condition +-- @copyright 2023 +-- @license GNU General Public License v3.0 + +local M = {} + +local env = require "astronvim.utils.status.env" + +--- A condition function if the window is currently active +---@return boolean # whether or not the window is currently actie +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.is_active } +function M.is_active() return vim.api.nvim_get_current_win() == tonumber(vim.g.actual_curwin) end + +--- A condition function if the buffer filetype,buftype,bufname match a pattern +---@param patterns table the table of patterns to match +---@param bufnr number of the buffer to match (Default: 0 [current]) +---@return boolean # whether or not LSP is attached +-- @usage local heirline_component = { provider = "Example Provider", condition = function() return require("astronvim.utils.status").condition.buffer_matches { buftype = { "terminal" } } end } +function M.buffer_matches(patterns, bufnr) + for kind, pattern_list in pairs(patterns) do + if env.buf_matchers[kind](pattern_list, bufnr) then return true end + end + return false +end + +--- A condition function if a macro is being recorded +---@return boolean # whether or not a macro is currently being recorded +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.is_macro_recording } +function M.is_macro_recording() return vim.fn.reg_recording() ~= "" end + +--- A condition function if search is visible +---@return boolean # whether or not searching is currently visible +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.is_hlsearch } +function M.is_hlsearch() return vim.v.hlsearch ~= 0 end + +--- A condition function if showcmdloc is set to statusline +---@return boolean # whether or not statusline showcmd is enabled +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.is_statusline_showcmd } +function M.is_statusline_showcmd() return vim.fn.has "nvim-0.9" == 1 and vim.opt.showcmdloc:get() == "statusline" end + +--- A condition function if the current file is in a git repo +---@param bufnr table|integer a buffer number to check the condition for, a table with bufnr property, or nil to get the current buffer +---@return boolean # whether or not the current file is in a git repo +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.is_git_repo } +function M.is_git_repo(bufnr) + if type(bufnr) == "table" then bufnr = bufnr.bufnr end + return vim.b[bufnr or 0].gitsigns_head or vim.b[bufnr or 0].gitsigns_status_dict +end + +--- A condition function if there are any git changes +---@param bufnr table|integer a buffer number to check the condition for, a table with bufnr property, or nil to get the current buffer +---@return boolean # whether or not there are any git changes +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.git_changed } +function M.git_changed(bufnr) + if type(bufnr) == "table" then bufnr = bufnr.bufnr end + local git_status = vim.b[bufnr or 0].gitsigns_status_dict + return git_status and (git_status.added or 0) + (git_status.removed or 0) + (git_status.changed or 0) > 0 +end + +--- A condition function if the current buffer is modified +---@param bufnr table|integer a buffer number to check the condition for, a table with bufnr property, or nil to get the current buffer +---@return boolean # whether or not the current buffer is modified +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.file_modified } +function M.file_modified(bufnr) + if type(bufnr) == "table" then bufnr = bufnr.bufnr end + return vim.bo[bufnr or 0].modified +end + +--- A condition function if the current buffer is read only +---@param bufnr table|integer a buffer number to check the condition for, a table with bufnr property, or nil to get the current buffer +---@return boolean # whether or not the current buffer is read only or not modifiable +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.file_read_only } +function M.file_read_only(bufnr) + if type(bufnr) == "table" then bufnr = bufnr.bufnr end + local buffer = vim.bo[bufnr or 0] + return not buffer.modifiable or buffer.readonly +end + +--- A condition function if the current file has any diagnostics +---@param bufnr table|integer a buffer number to check the condition for, a table with bufnr property, or nil to get the current buffer +---@return boolean # whether or not the current file has any diagnostics +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.has_diagnostics } +function M.has_diagnostics(bufnr) + if type(bufnr) == "table" then bufnr = bufnr.bufnr end + return vim.g.diagnostics_mode > 0 and #vim.diagnostic.get(bufnr or 0) > 0 +end + +--- A condition function if there is a defined filetype +---@param bufnr table|integer a buffer number to check the condition for, a table with bufnr property, or nil to get the current buffer +---@return boolean # whether or not there is a filetype +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.has_filetype } +function M.has_filetype(bufnr) + if type(bufnr) == "table" then bufnr = bufnr.bufnr end + return vim.bo[bufnr or 0].filetype and vim.bo[bufnr or 0].filetype ~= "" +end + +--- A condition function if Aerial is available +---@return boolean # whether or not aerial plugin is installed +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.aerial_available } +-- function M.aerial_available() return is_available "aerial.nvim" end +function M.aerial_available() return package.loaded["aerial"] end + +--- A condition function if LSP is attached +---@param bufnr table|integer a buffer number to check the condition for, a table with bufnr property, or nil to get the current buffer +---@return boolean # whether or not LSP is attached +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.lsp_attached } +function M.lsp_attached(bufnr) + if type(bufnr) == "table" then bufnr = bufnr.bufnr end + -- HACK: Check for lsp utilities loaded first, get_active_clients seems to have a bug if called too early (tokyonight colorscheme seems to be a good way to expose this for some reason) + return package.loaded["astronvim.utils.lsp"] and next(vim.lsp.get_active_clients { bufnr = bufnr or 0 }) ~= nil +end + +--- A condition function if treesitter is in use +---@param bufnr table|integer a buffer number to check the condition for, a table with bufnr property, or nil to get the current buffer +---@return boolean # whether or not treesitter is active +-- @usage local heirline_component = { provider = "Example Provider", condition = require("astronvim.utils.status").condition.treesitter_available } +function M.treesitter_available(bufnr) + if not package.loaded["nvim-treesitter"] then return false end + if type(bufnr) == "table" then bufnr = bufnr.bufnr end + local parsers = require "nvim-treesitter.parsers" + return parsers.has_parser(parsers.get_buf_lang(bufnr or vim.api.nvim_get_current_buf())) +end + +--- A condition function if the foldcolumn is enabled +---@return boolean # true if vim.opt.foldcolumn > 0, false if vim.opt.foldcolumn == 0 +function M.foldcolumn_enabled() return vim.opt.foldcolumn:get() ~= "0" end + +--- A condition function if the number column is enabled +---@return boolean # true if vim.opt.number or vim.opt.relativenumber, false if neither +function M.numbercolumn_enabled() return vim.opt.number:get() or vim.opt.relativenumber:get() end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/status/env.lua b/vim/config/nvim/lua/astronvim/utils/status/env.lua new file mode 100644 index 0000000..257abae --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/status/env.lua @@ -0,0 +1,143 @@ +--- ### AstroNvim Status Environment +-- +-- Statusline related environment variables shared between components/providers/etc. +-- +-- This module can be loaded with `local env = require "astronvim.utils.status.env"` +-- +-- @module astronvim.utils.status.env +-- @copyright 2023 +-- @license GNU General Public License v3.0 + +local M = {} + +M.fallback_colors = { + none = "NONE", + fg = "#abb2bf", + bg = "#1e222a", + dark_bg = "#2c323c", + blue = "#61afef", + green = "#98c379", + grey = "#5c6370", + bright_grey = "#777d86", + dark_grey = "#5c5c5c", + orange = "#ff9640", + purple = "#c678dd", + bright_purple = "#a9a1e1", + red = "#e06c75", + bright_red = "#ec5f67", + white = "#c9c9c9", + yellow = "#e5c07b", + bright_yellow = "#ebae34", +} + +M.modes = { + ["n"] = { "NORMAL", "normal" }, + ["no"] = { "OP", "normal" }, + ["nov"] = { "OP", "normal" }, + ["noV"] = { "OP", "normal" }, + ["no"] = { "OP", "normal" }, + ["niI"] = { "NORMAL", "normal" }, + ["niR"] = { "NORMAL", "normal" }, + ["niV"] = { "NORMAL", "normal" }, + ["i"] = { "INSERT", "insert" }, + ["ic"] = { "INSERT", "insert" }, + ["ix"] = { "INSERT", "insert" }, + ["t"] = { "TERM", "terminal" }, + ["nt"] = { "TERM", "terminal" }, + ["v"] = { "VISUAL", "visual" }, + ["vs"] = { "VISUAL", "visual" }, + ["V"] = { "LINES", "visual" }, + ["Vs"] = { "LINES", "visual" }, + [""] = { "BLOCK", "visual" }, + ["s"] = { "BLOCK", "visual" }, + ["R"] = { "REPLACE", "replace" }, + ["Rc"] = { "REPLACE", "replace" }, + ["Rx"] = { "REPLACE", "replace" }, + ["Rv"] = { "V-REPLACE", "replace" }, + ["s"] = { "SELECT", "visual" }, + ["S"] = { "SELECT", "visual" }, + [""] = { "BLOCK", "visual" }, + ["c"] = { "COMMAND", "command" }, + ["cv"] = { "COMMAND", "command" }, + ["ce"] = { "COMMAND", "command" }, + ["r"] = { "PROMPT", "inactive" }, + ["rm"] = { "MORE", "inactive" }, + ["r?"] = { "CONFIRM", "inactive" }, + ["!"] = { "SHELL", "inactive" }, + ["null"] = { "null", "inactive" }, +} + +M.separators = astronvim.user_opts("heirline.separators", { + none = { "", "" }, + left = { "", " " }, + right = { " ", "" }, + center = { " ", " " }, + tab = { "", " " }, + breadcrumbs = "  ", + path = "  ", +}) + +M.attributes = astronvim.user_opts("heirline.attributes", { + buffer_active = { bold = true, italic = true }, + buffer_picker = { bold = true }, + macro_recording = { bold = true }, + git_branch = { bold = true }, + git_diff = { bold = true }, +}) + +M.icon_highlights = astronvim.user_opts("heirline.icon_highlights", { + file_icon = { + tabline = function(self) return self.is_active or self.is_visible end, + statusline = true, + }, +}) + +local function pattern_match(str, pattern_list) + for _, pattern in ipairs(pattern_list) do + if str:find(pattern) then return true end + end + return false +end + +M.buf_matchers = { + filetype = function(pattern_list, bufnr) return pattern_match(vim.bo[bufnr or 0].filetype, pattern_list) end, + buftype = function(pattern_list, bufnr) return pattern_match(vim.bo[bufnr or 0].buftype, pattern_list) end, + bufname = function(pattern_list, bufnr) + return pattern_match(vim.fn.fnamemodify(vim.api.nvim_buf_get_name(bufnr or 0), ":t"), pattern_list) + end, +} + +M.sign_handlers = {} +-- gitsigns handlers +local gitsigns = function(_) + local gitsigns_avail, gitsigns = pcall(require, "gitsigns") + if gitsigns_avail then vim.schedule(gitsigns.preview_hunk) end +end +for _, sign in ipairs { "Topdelete", "Untracked", "Add", "Changedelete", "Delete" } do + local name = "GitSigns" .. sign + if not M.sign_handlers[name] then M.sign_handlers[name] = gitsigns end +end +-- diagnostic handlers +local diagnostics = function(args) + if args.mods:find "c" then + vim.schedule(vim.lsp.buf.code_action) + else + vim.schedule(vim.diagnostic.open_float) + end +end +for _, sign in ipairs { "Error", "Hint", "Info", "Warn" } do + local name = "DiagnosticSign" .. sign + if not M.sign_handlers[name] then M.sign_handlers[name] = diagnostics end +end +-- DAP handlers +local dap_breakpoint = function(_) + local dap_avail, dap = pcall(require, "dap") + if dap_avail then vim.schedule(dap.toggle_breakpoint) end +end +for _, sign in ipairs { "", "Rejected", "Condition" } do + local name = "DapBreakpoint" .. sign + if not M.sign_handlers[name] then M.sign_handlers[name] = dap_breakpoint end +end +M.sign_handlers = astronvim.user_opts("heirline.sign_handlers", M.sign_handlers) + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/status/heirline.lua b/vim/config/nvim/lua/astronvim/utils/status/heirline.lua new file mode 100644 index 0000000..494c178 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/status/heirline.lua @@ -0,0 +1,115 @@ +--- ### AstroNvim Status Heirline Extensions +-- +-- Statusline related heirline specific extensions +-- +-- This module can be loaded with `local astro_heirline = require "astronvim.utils.status.heirline"` +-- +-- @module astronvim.utils.status.heirline +-- @copyright 2023 +-- @license GNU General Public License v3.0 + +local M = {} + +local hl = require "astronvim.utils.status.hl" +local provider = require "astronvim.utils.status.provider" +local status_utils = require "astronvim.utils.status.utils" + +local utils = require "astronvim.utils" +local buffer_utils = require "astronvim.utils.buffer" +local get_icon = utils.get_icon + +--- A helper function to get the type a tab or buffer is +---@param self table the self table from a heirline component function +---@param prefix? string the prefix of the type, either "tab" or "buffer" (Default: "buffer") +---@return string # the string of prefix with the type (i.e. "_active" or "_visible") +function M.tab_type(self, prefix) + local tab_type = "" + if self.is_active then + tab_type = "_active" + elseif self.is_visible then + tab_type = "_visible" + end + return (prefix or "buffer") .. tab_type +end + +--- Make a list of buffers, rendering each buffer with the provided component +---@param component table +---@return table +M.make_buflist = function(component) + local overflow_hl = hl.get_attributes("buffer_overflow", true) + return require("heirline.utils").make_buflist( + status_utils.surround( + "tab", + function(self) + return { + main = M.tab_type(self) .. "_bg", + left = "tabline_bg", + right = "tabline_bg", + } + end, + { -- bufferlist + init = function(self) self.tab_type = M.tab_type(self) end, + on_click = { -- add clickable component to each buffer + callback = function(_, minwid) vim.api.nvim_win_set_buf(0, minwid) end, + minwid = function(self) return self.bufnr end, + name = "heirline_tabline_buffer_callback", + }, + { -- add buffer picker functionality to each buffer + condition = function(self) return self._show_picker end, + update = false, + init = function(self) + if not (self.label and self._picker_labels[self.label]) then + local bufname = provider.filename()(self) + local label = bufname:sub(1, 1) + local i = 2 + while label ~= " " and self._picker_labels[label] do + if i > #bufname then break end + label = bufname:sub(i, i) + i = i + 1 + end + self._picker_labels[label] = self.bufnr + self.label = label + end + end, + provider = function(self) return provider.str { str = self.label, padding = { left = 1, right = 1 } } end, + hl = hl.get_attributes "buffer_picker", + }, + component, -- create buffer component + }, + function(self) return buffer_utils.is_valid(self.bufnr) end -- disable surrounding + ), + { provider = get_icon "ArrowLeft" .. " ", hl = overflow_hl }, + { provider = get_icon "ArrowRight" .. " ", hl = overflow_hl }, + function() return vim.t.bufs end, -- use astronvim bufs variable + false -- disable internal caching + ) +end + +--- Alias to require("heirline.utils").make_tablist +function M.make_tablist(...) return require("heirline.utils").make_tablist(...) end + +--- Run the buffer picker and execute the callback function on the selected buffer +---@param callback function with a single parameter of the buffer number +function M.buffer_picker(callback) + local tabline = require("heirline").tabline + -- if buflist then + local prev_showtabline = vim.opt.showtabline:get() + if prev_showtabline ~= 2 then vim.opt.showtabline = 2 end + vim.cmd.redrawtabline() + ---@diagnostic disable-next-line: undefined-field + local buflist = tabline and tabline._buflist and tabline._buflist[1] + if buflist then + buflist._picker_labels = {} + buflist._show_picker = true + vim.cmd.redrawtabline() + local char = vim.fn.getcharstr() + local bufnr = buflist._picker_labels[char] + if bufnr then callback(bufnr) end + buflist._show_picker = false + end + if prev_showtabline ~= 2 then vim.opt.showtabline = prev_showtabline end + vim.cmd.redrawtabline() + -- end +end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/status/hl.lua b/vim/config/nvim/lua/astronvim/utils/status/hl.lua new file mode 100644 index 0000000..91dab18 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/status/hl.lua @@ -0,0 +1,76 @@ +--- ### AstroNvim Status Highlighting +-- +-- Statusline related highlighting utilities +-- +-- This module can be loaded with `local hl = require "astronvim.utils.status.hl"` +-- +-- @module astronvim.utils.status.hl +-- @copyright 2023 +-- @license GNU General Public License v3.0 + +local M = {} + +local env = require "astronvim.utils.status.env" + +--- Get the highlight background color of the lualine theme for the current colorscheme +---@param mode string the neovim mode to get the color of +---@param fallback string the color to fallback on if a lualine theme is not present +---@return string # The background color of the lualine theme or the fallback parameter if one doesn't exist +function M.lualine_mode(mode, fallback) + if not vim.g.colors_name then return fallback end + local lualine_avail, lualine = pcall(require, "lualine.themes." .. vim.g.colors_name) + local lualine_opts = lualine_avail and lualine[mode] + return lualine_opts and type(lualine_opts.a) == "table" and lualine_opts.a.bg or fallback +end + +--- Get the highlight for the current mode +---@return table # the highlight group for the current mode +-- @usage local heirline_component = { provider = "Example Provider", hl = require("astronvim.utils.status").hl.mode }, +function M.mode() return { bg = M.mode_bg() } end + +--- Get the foreground color group for the current mode, good for usage with Heirline surround utility +---@return string # the highlight group for the current mode foreground +-- @usage local heirline_component = require("heirline.utils").surround({ "|", "|" }, require("astronvim.utils.status").hl.mode_bg, heirline_component), + +function M.mode_bg() return env.modes[vim.fn.mode()][2] end + +--- Get the foreground color group for the current filetype +---@return table # the highlight group for the current filetype foreground +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.fileicon(), hl = require("astronvim.utils.status").hl.filetype_color }, +function M.filetype_color(self) + local devicons_avail, devicons = pcall(require, "nvim-web-devicons") + if not devicons_avail then return {} end + local _, color = devicons.get_icon_color( + vim.fn.fnamemodify(vim.api.nvim_buf_get_name(self and self.bufnr or 0), ":t"), + nil, + { default = true } + ) + return { fg = color } +end + +--- Merge the color and attributes from user settings for a given name +---@param name string, the name of the element to get the attributes and colors for +---@param include_bg? boolean whether or not to include background color (Default: false) +---@return table # a table of highlight information +-- @usage local heirline_component = { provider = "Example Provider", hl = require("astronvim.utils.status").hl.get_attributes("treesitter") }, +function M.get_attributes(name, include_bg) + local hl = env.attributes[name] or {} + hl.fg = name .. "_fg" + if include_bg then hl.bg = name .. "_bg" end + return hl +end + +--- Enable filetype color highlight if enabled in icon_highlights.file_icon options +---@param name string the icon_highlights.file_icon table element +---@return function # for setting hl property in a component +-- @usage local heirline_component = { provider = "Example Provider", hl = require("astronvim.utils.status").hl.file_icon("winbar") }, +function M.file_icon(name) + local hl_enabled = env.icon_highlights.file_icon[name] + return function(self) + if hl_enabled == true or (type(hl_enabled) == "function" and hl_enabled(self)) then + return M.filetype_color(self) + end + end +end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/status/init.lua b/vim/config/nvim/lua/astronvim/utils/status/init.lua new file mode 100644 index 0000000..6133d00 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/status/init.lua @@ -0,0 +1,155 @@ +--- ### AstroNvim Status Initializers +-- +-- Statusline related init functions for building dynamic statusline components +-- +-- This module can be loaded with `local init = require "astronvim.utils.status.init"` +-- +-- @module astronvim.utils.status.init +-- @copyright 2023 +-- @license GNU General Public License v3.0 + +local M = {} + +local env = require "astronvim.utils.status.env" +local provider = require "astronvim.utils.status.provider" +local status_utils = require "astronvim.utils.status.utils" + +local utils = require "astronvim.utils" +local extend_tbl = utils.extend_tbl + +--- An `init` function to build a set of children components for LSP breadcrumbs +---@param opts? table # options for configuring the breadcrumbs (default: `{ max_depth = 5, separator = "  ", icon = { enabled = true, hl = false }, padding = { left = 0, right = 0 } }`) +---@return function # The Heirline init function +-- @usage local heirline_component = { init = require("astronvim.utils.status").init.breadcrumbs { padding = { left = 1 } } } +function M.breadcrumbs(opts) + opts = extend_tbl({ + max_depth = 5, + separator = env.separators.breadcrumbs or "  ", + icon = { enabled = true, hl = env.icon_highlights.breadcrumbs }, + padding = { left = 0, right = 0 }, + }, opts) + return function(self) + local data = require("aerial").get_location(true) or {} + local children = {} + -- add prefix if needed, use the separator if true, or use the provided character + if opts.prefix and not vim.tbl_isempty(data) then + table.insert(children, { provider = opts.prefix == true and opts.separator or opts.prefix }) + end + local start_idx = 0 + if opts.max_depth and opts.max_depth > 0 then + start_idx = #data - opts.max_depth + if start_idx > 0 then + table.insert(children, { provider = require("astronvim.utils").get_icon "Ellipsis" .. opts.separator }) + end + end + -- create a child for each level + for i, d in ipairs(data) do + if i > start_idx then + local child = { + { provider = string.gsub(d.name, "%%", "%%%%"):gsub("%s*->%s*", "") }, -- add symbol name + on_click = { -- add on click function + minwid = status_utils.encode_pos(d.lnum, d.col, self.winnr), + callback = function(_, minwid) + local lnum, col, winnr = status_utils.decode_pos(minwid) + vim.api.nvim_win_set_cursor(vim.fn.win_getid(winnr), { lnum, col }) + end, + name = "heirline_breadcrumbs", + }, + } + if opts.icon.enabled then -- add icon and highlight if enabled + local hl = opts.icon.hl + if type(hl) == "function" then hl = hl(self) end + local hlgroup = string.format("Aerial%sIcon", d.kind) + table.insert(child, 1, { + provider = string.format("%s ", d.icon), + hl = (hl and vim.fn.hlexists(hlgroup) == 1) and hlgroup or nil, + }) + end + if #data > 1 and i < #data then table.insert(child, { provider = opts.separator }) end -- add a separator only if needed + table.insert(children, child) + end + end + if opts.padding.left > 0 then -- add left padding + table.insert(children, 1, { provider = status_utils.pad_string(" ", { left = opts.padding.left - 1 }) }) + end + if opts.padding.right > 0 then -- add right padding + table.insert(children, { provider = status_utils.pad_string(" ", { right = opts.padding.right - 1 }) }) + end + -- instantiate the new child + self[1] = self:new(children, 1) + end +end + +--- An `init` function to build a set of children components for a separated path to file +---@param opts? table options for configuring the breadcrumbs (default: `{ max_depth = 3, path_func = provider.unique_path(), separator = "  ", suffix = true, padding = { left = 0, right = 0 } }`) +---@return function # The Heirline init function +-- @usage local heirline_component = { init = require("astronvim.utils.status").init.separated_path { padding = { left = 1 } } } +function M.separated_path(opts) + opts = extend_tbl({ + max_depth = 3, + path_func = provider.unique_path(), + separator = env.separators.path or "  ", + suffix = true, + padding = { left = 0, right = 0 }, + }, opts) + if opts.suffix == true then opts.suffix = opts.separator end + return function(self) + local path = opts.path_func(self) + if path == "." then path = "" end -- if there is no path, just replace with empty string + local data = vim.fn.split(path, "/") + local children = {} + -- add prefix if needed, use the separator if true, or use the provided character + if opts.prefix and not vim.tbl_isempty(data) then + table.insert(children, { provider = opts.prefix == true and opts.separator or opts.prefix }) + end + local start_idx = 0 + if opts.max_depth and opts.max_depth > 0 then + start_idx = #data - opts.max_depth + if start_idx > 0 then + table.insert(children, { provider = require("astronvim.utils").get_icon "Ellipsis" .. opts.separator }) + end + end + -- create a child for each level + for i, d in ipairs(data) do + if i > start_idx then + local child = { { provider = d } } + local separator = i < #data and opts.separtor or opts.suffix + if separator then table.insert(child, { provider = separator }) end + table.insert(children, child) + end + end + if opts.padding.left > 0 then -- add left padding + table.insert(children, 1, { provider = status_utils.pad_string(" ", { left = opts.padding.left - 1 }) }) + end + if opts.padding.right > 0 then -- add right padding + table.insert(children, { provider = status_utils.pad_string(" ", { right = opts.padding.right - 1 }) }) + end + -- instantiate the new child + self[1] = self:new(children, 1) + end +end + +--- An `init` function to build multiple update events which is not supported yet by Heirline's update field +---@param opts any[] an array like table of autocmd events as either just a string or a table with custom patterns and callbacks. +---@return function # The Heirline init function +-- @usage local heirline_component = { init = require("astronvim.utils.status").init.update_events { "BufEnter", { "User", pattern = "LspProgressUpdate" } } } +function M.update_events(opts) + return function(self) + if not rawget(self, "once") then + local clear_cache = function() self._win_cache = nil end + for _, event in ipairs(opts) do + local event_opts = { callback = clear_cache } + if type(event) == "table" then + event_opts.pattern = event.pattern + event_opts.callback = event.callback or clear_cache + event.pattern = nil + event.callback = nil + end + vim.api.nvim_create_autocmd(event, event_opts) + end + self.once = true + end + end +end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/status/provider.lua b/vim/config/nvim/lua/astronvim/utils/status/provider.lua new file mode 100644 index 0000000..3f1c64d --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/status/provider.lua @@ -0,0 +1,521 @@ +--- ### AstroNvim Status Providers +-- +-- Statusline related provider functions for building statusline components +-- +-- This module can be loaded with `local provider = require "astronvim.utils.status.provider"` +-- +-- @module astronvim.utils.status.provider +-- @copyright 2023 +-- @license GNU General Public License v3.0 + +local M = {} + +local condition = require "astronvim.utils.status.condition" +local env = require "astronvim.utils.status.env" +local status_utils = require "astronvim.utils.status.utils" + +local utils = require "astronvim.utils" +local extend_tbl = utils.extend_tbl +local get_icon = utils.get_icon +local luv = vim.uv or vim.loop -- TODO: REMOVE WHEN DROPPING SUPPORT FOR Neovim v0.9 + +--- A provider function for the fill string +---@return string # the statusline string for filling the empty space +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.fill } +function M.fill() return "%=" end + +--- A provider function for the signcolumn string +---@param opts? table options passed to the stylize function +---@return string # the statuscolumn string for adding the signcolumn +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.signcolumn } +-- @see astronvim.utils.status.utils.stylize +function M.signcolumn(opts) + opts = extend_tbl({ escape = false }, opts) + return status_utils.stylize("%s", opts) +end + +--- A provider function for the numbercolumn string +---@param opts? table options passed to the stylize function +---@return function # the statuscolumn string for adding the numbercolumn +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.numbercolumn } +-- @see astronvim.utils.status.utils.stylize +function M.numbercolumn(opts) + opts = extend_tbl({ thousands = false, culright = true, escape = false }, opts) + return function() + local lnum, rnum, virtnum = vim.v.lnum, vim.v.relnum, vim.v.virtnum + local num, relnum = vim.opt.number:get(), vim.opt.relativenumber:get() + local str + if not num and not relnum then + str = "" + elseif virtnum ~= 0 then + str = "%=" + else + local cur = relnum and (rnum > 0 and rnum or (num and lnum or 0)) or lnum + if opts.thousands and cur > 999 then + cur = string.reverse(cur):gsub("%d%d%d", "%1" .. opts.thousands):reverse():gsub("^%" .. opts.thousands, "") + end + str = (rnum == 0 and not opts.culright and relnum) and cur .. "%=" or "%=" .. cur + end + return status_utils.stylize(str, opts) + end +end + +--- A provider function for building a foldcolumn +---@param opts? table options passed to the stylize function +---@return function # a custom foldcolumn function for the statuscolumn that doesn't show the nest levels +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.foldcolumn } +-- @see astronvim.utils.status.utils.stylize +function M.foldcolumn(opts) + opts = extend_tbl({ escape = false }, opts) + local ffi = require "astronvim.utils.ffi" -- get AstroNvim C extensions + local fillchars = vim.opt.fillchars:get() + local foldopen = fillchars.foldopen or get_icon "FoldOpened" + local foldclosed = fillchars.foldclose or get_icon "FoldClosed" + local foldsep = fillchars.foldsep or get_icon "FoldSeparator" + return function() -- move to M.fold_indicator + local wp = ffi.C.find_window_by_handle(0, ffi.new "Error") -- get window handler + local width = ffi.C.compute_foldcolumn(wp, 0) -- get foldcolumn width + -- get fold info of current line + local foldinfo = width > 0 and ffi.C.fold_info(wp, vim.v.lnum) or { start = 0, level = 0, llevel = 0, lines = 0 } + + local str = "" + if width ~= 0 then + str = vim.v.relnum > 0 and "%#FoldColumn#" or "%#CursorLineFold#" + if foldinfo.level == 0 then + str = str .. (" "):rep(width) + else + local closed = foldinfo.lines > 0 + local first_level = foldinfo.level - width - (closed and 1 or 0) + 1 + if first_level < 1 then first_level = 1 end + + for col = 1, width do + str = str + .. ( + (vim.v.virtnum ~= 0 and foldsep) + or ((closed and (col == foldinfo.level or col == width)) and foldclosed) + or ((foldinfo.start == vim.v.lnum and first_level + col > foldinfo.llevel) and foldopen) + or foldsep + ) + if col == foldinfo.level then + str = str .. (" "):rep(width - col) + break + end + end + end + end + return status_utils.stylize(str .. "%*", opts) + end +end + +--- A provider function for the current tab numbre +---@return function # the statusline function to return a string for a tab number +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.tabnr() } +function M.tabnr() + return function(self) return (self and self.tabnr) and "%" .. self.tabnr .. "T " .. self.tabnr .. " %T" or "" end +end + +--- A provider function for showing if spellcheck is on +---@param opts? table options passed to the stylize function +---@return function # the function for outputting if spell is enabled +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.spell() } +-- @see astronvim.utils.status.utils.stylize +function M.spell(opts) + opts = extend_tbl({ str = "", icon = { kind = "Spellcheck" }, show_empty = true }, opts) + return function() return status_utils.stylize(vim.wo.spell and opts.str or nil, opts) end +end + +--- A provider function for showing if paste is enabled +---@param opts? table options passed to the stylize function +---@return function # the function for outputting if paste is enabled +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.paste() } +-- @see astronvim.utils.status.utils.stylize +function M.paste(opts) + opts = extend_tbl({ str = "", icon = { kind = "Paste" }, show_empty = true }, opts) + local paste = vim.opt.paste + if type(paste) ~= "boolean" then paste = paste:get() end + return function() return status_utils.stylize(paste and opts.str or nil, opts) end +end + +--- A provider function for displaying if a macro is currently being recorded +---@param opts? table a prefix before the recording register and options passed to the stylize function +---@return function # a function that returns a string of the current recording status +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.macro_recording() } +-- @see astronvim.utils.status.utils.stylize +function M.macro_recording(opts) + opts = extend_tbl({ prefix = "@" }, opts) + return function() + local register = vim.fn.reg_recording() + if register ~= "" then register = opts.prefix .. register end + return status_utils.stylize(register, opts) + end +end + +--- A provider function for displaying the current command +---@param opts? table of options passed to the stylize function +---@return string # the statusline string for showing the current command +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.showcmd() } +-- @see astronvim.utils.status.utils.stylize +function M.showcmd(opts) + opts = extend_tbl({ minwid = 0, maxwid = 5, escape = false }, opts) + return status_utils.stylize(("%%%d.%d(%%S%%)"):format(opts.minwid, opts.maxwid), opts) +end + +--- A provider function for displaying the current search count +---@param opts? table options for `vim.fn.searchcount` and options passed to the stylize function +---@return function # a function that returns a string of the current search location +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.search_count() } +-- @see astronvim.utils.status.utils.stylize +function M.search_count(opts) + local search_func = vim.tbl_isempty(opts or {}) and function() return vim.fn.searchcount() end + or function() return vim.fn.searchcount(opts) end + return function() + local search_ok, search = pcall(search_func) + if search_ok and type(search) == "table" and search.total then + return status_utils.stylize( + string.format( + "%s%d/%s%d", + search.current > search.maxcount and ">" or "", + math.min(search.current, search.maxcount), + search.incomplete == 2 and ">" or "", + math.min(search.total, search.maxcount) + ), + opts + ) + end + end +end + +--- A provider function for showing the text of the current vim mode +---@param opts? table options for padding the text and options passed to the stylize function +---@return function # the function for displaying the text of the current vim mode +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.mode_text() } +-- @see astronvim.utils.status.utils.stylize +function M.mode_text(opts) + local max_length = math.max(unpack(vim.tbl_map(function(str) return #str[1] end, vim.tbl_values(env.modes)))) + return function() + local text = env.modes[vim.fn.mode()][1] + if opts and opts.pad_text then + local padding = max_length - #text + if opts.pad_text == "right" then + text = string.rep(" ", padding) .. text + elseif opts.pad_text == "left" then + text = text .. string.rep(" ", padding) + elseif opts.pad_text == "center" then + text = string.rep(" ", math.floor(padding / 2)) .. text .. string.rep(" ", math.ceil(padding / 2)) + end + end + return status_utils.stylize(text, opts) + end +end + +--- A provider function for showing the percentage of the current location in a document +---@param opts? table options for Top/Bot text, fixed width, and options passed to the stylize function +---@return function # the statusline string for displaying the percentage of current document location +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.percentage() } +-- @see astronvim.utils.status.utils.stylize +function M.percentage(opts) + opts = extend_tbl({ escape = false, fixed_width = true, edge_text = true }, opts) + return function() + local text = "%" .. (opts.fixed_width and (opts.edge_text and "2" or "3") or "") .. "p%%" + if opts.edge_text then + local current_line = vim.fn.line "." + if current_line == 1 then + text = "Top" + elseif current_line == vim.fn.line "$" then + text = "Bot" + end + end + return status_utils.stylize(text, opts) + end +end + +--- A provider function for showing the current line and character in a document +---@param opts? table options for padding the line and character locations and options passed to the stylize function +---@return function # the statusline string for showing location in document line_num:char_num +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.ruler({ pad_ruler = { line = 3, char = 2 } }) } +-- @see astronvim.utils.status.utils.stylize +function M.ruler(opts) + opts = extend_tbl({ pad_ruler = { line = 3, char = 2 } }, opts) + local padding_str = string.format("%%%dd:%%-%dd", opts.pad_ruler.line, opts.pad_ruler.char) + return function() + local line = vim.fn.line "." + local char = vim.fn.virtcol "." + return status_utils.stylize(string.format(padding_str, line, char), opts) + end +end + +--- A provider function for showing the current location as a scrollbar +---@param opts? table options passed to the stylize function +---@return function # the function for outputting the scrollbar +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.scrollbar() } +-- @see astronvim.utils.status.utils.stylize +function M.scrollbar(opts) + local sbar = { "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" } + return function() + local curr_line = vim.api.nvim_win_get_cursor(0)[1] + local lines = vim.api.nvim_buf_line_count(0) + local i = math.floor((curr_line - 1) / lines * #sbar) + 1 + if sbar[i] then return status_utils.stylize(string.rep(sbar[i], 2), opts) end + end +end + +--- A provider to simply show a close button icon +---@param opts? table options passed to the stylize function and the kind of icon to use +---@return string # the stylized icon +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.close_button() } +-- @see astronvim.utils.status.utils.stylize +function M.close_button(opts) + opts = extend_tbl({ kind = "BufferClose" }, opts) + return status_utils.stylize(get_icon(opts.kind), opts) +end + +--- A provider function for showing the current filetype +---@param opts? table options passed to the stylize function +---@return function # the function for outputting the filetype +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.filetype() } +-- @see astronvim.utils.status.utils.stylize +function M.filetype(opts) + return function(self) + local buffer = vim.bo[self and self.bufnr or 0] + return status_utils.stylize(string.lower(buffer.filetype), opts) + end +end + +--- A provider function for showing the current filename +---@param opts? table options for argument to fnamemodify to format filename and options passed to the stylize function +---@return function # the function for outputting the filename +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.filename() } +-- @see astronvim.utils.status.utils.stylize +function M.filename(opts) + opts = extend_tbl({ + fallback = "Empty", + fname = function(nr) return vim.api.nvim_buf_get_name(nr) end, + modify = ":t", + }, opts) + return function(self) + local filename = vim.fn.fnamemodify(opts.fname(self and self.bufnr or 0), opts.modify) + return status_utils.stylize((filename == "" and opts.fallback or filename), opts) + end +end + +--- A provider function for showing the current file encoding +---@param opts? table options passed to the stylize function +---@return function # the function for outputting the file encoding +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.file_encoding() } +-- @see astronvim.utils.status.utils.stylize +function M.file_encoding(opts) + return function(self) + local buf_enc = vim.bo[self and self.bufnr or 0].fenc + return status_utils.stylize(string.upper(buf_enc ~= "" and buf_enc or vim.o.enc), opts) + end +end + +--- A provider function for showing the current file format +---@param opts? table options passed to the stylize function +---@return function # the function for outputting the file format +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.file_format() } +-- @see astronvim.utils.status.utils.stylize +function M.file_format(opts) + return function(self) + local buf_format = vim.bo[self and self.bufnr or 0].fileformat + return status_utils.stylize(string.upper(buf_format ~= "" and buf_format or vim.o.fileformat), opts) + end +end + +--- Get a unique filepath between all buffers +---@param opts? table options for function to get the buffer name, a buffer number, max length, and options passed to the stylize function +---@return function # path to file that uniquely identifies each buffer +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.unique_path() } +-- @see astronvim.utils.status.utils.stylize +function M.unique_path(opts) + opts = extend_tbl({ + buf_name = function(bufnr) return vim.fn.fnamemodify(vim.api.nvim_buf_get_name(bufnr), ":t") end, + bufnr = 0, + max_length = 16, + }, opts) + local function path_parts(bufnr) + local parts = {} + for match in (vim.api.nvim_buf_get_name(bufnr) .. "/"):gmatch("(.-)" .. "/") do + table.insert(parts, match) + end + return parts + end + return function(self) + opts.bufnr = self and self.bufnr or opts.bufnr + local name = opts.buf_name(opts.bufnr) + local unique_path = "" + -- check for same buffer names under different dirs + local current + for _, value in ipairs(vim.t.bufs) do + if name == opts.buf_name(value) and value ~= opts.bufnr then + if not current then current = path_parts(opts.bufnr) end + local other = path_parts(value) + + for i = #current - 1, 1, -1 do + if current[i] ~= other[i] then + unique_path = current[i] .. "/" + break + end + end + end + end + return status_utils.stylize( + ( + opts.max_length > 0 + and #unique_path > opts.max_length + and string.sub(unique_path, 1, opts.max_length - 2) .. get_icon "Ellipsis" .. "/" + ) or unique_path, + opts + ) + end +end + +--- A provider function for showing if the current file is modifiable +---@param opts? table options passed to the stylize function +---@return function # the function for outputting the indicator if the file is modified +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.file_modified() } +-- @see astronvim.utils.status.utils.stylize +function M.file_modified(opts) + opts = extend_tbl({ str = "", icon = { kind = "FileModified" }, show_empty = true }, opts) + return function(self) + return status_utils.stylize(condition.file_modified((self or {}).bufnr) and opts.str or nil, opts) + end +end + +--- A provider function for showing if the current file is read-only +---@param opts? table options passed to the stylize function +---@return function # the function for outputting the indicator if the file is read-only +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.file_read_only() } +-- @see astronvim.utils.status.utils.stylize +function M.file_read_only(opts) + opts = extend_tbl({ str = "", icon = { kind = "FileReadOnly" }, show_empty = true }, opts) + return function(self) + return status_utils.stylize(condition.file_read_only((self or {}).bufnr) and opts.str or nil, opts) + end +end + +--- A provider function for showing the current filetype icon +---@param opts? table options passed to the stylize function +---@return function # the function for outputting the filetype icon +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.file_icon() } +-- @see astronvim.utils.status.utils.stylize +function M.file_icon(opts) + return function(self) + local devicons_avail, devicons = pcall(require, "nvim-web-devicons") + if not devicons_avail then return "" end + local ft_icon, _ = devicons.get_icon( + vim.fn.fnamemodify(vim.api.nvim_buf_get_name(self and self.bufnr or 0), ":t"), + nil, + { default = true } + ) + return status_utils.stylize(ft_icon, opts) + end +end + +--- A provider function for showing the current git branch +---@param opts table options passed to the stylize function +---@return function # the function for outputting the git branch +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.git_branch() } +-- @see astronvim.utils.status.utils.stylize +function M.git_branch(opts) + return function(self) return status_utils.stylize(vim.b[self and self.bufnr or 0].gitsigns_head or "", opts) end +end + +--- A provider function for showing the current git diff count of a specific type +---@param opts? table options for type of git diff and options passed to the stylize function +---@return function|nil # the function for outputting the git diff +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.git_diff({ type = "added" }) } +-- @see astronvim.utils.status.utils.stylize +function M.git_diff(opts) + if not opts or not opts.type then return end + return function(self) + local status = vim.b[self and self.bufnr or 0].gitsigns_status_dict + return status_utils.stylize( + status and status[opts.type] and status[opts.type] > 0 and tostring(status[opts.type]) or "", + opts + ) + end +end + +--- A provider function for showing the current diagnostic count of a specific severity +---@param opts table options for severity of diagnostic and options passed to the stylize function +---@return function|nil # the function for outputting the diagnostic count +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.diagnostics({ severity = "ERROR" }) } +-- @see astronvim.utils.status.utils.stylize +function M.diagnostics(opts) + if not opts or not opts.severity then return end + return function(self) + local bufnr = self and self.bufnr or 0 + local count = #vim.diagnostic.get(bufnr, opts.severity and { severity = vim.diagnostic.severity[opts.severity] }) + return status_utils.stylize(count ~= 0 and tostring(count) or "", opts) + end +end + +--- A provider function for showing the current progress of loading language servers +---@param opts? table options passed to the stylize function +---@return function # the function for outputting the LSP progress +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.lsp_progress() } +-- @see astronvim.utils.status.utils.stylize +function M.lsp_progress(opts) + local spinner = utils.get_spinner("LSPLoading", 1) or { "" } + return function() + local _, Lsp = next(astronvim.lsp.progress) + return status_utils.stylize(Lsp and (spinner[math.floor(luv.hrtime() / 12e7) % #spinner + 1] .. table.concat({ + Lsp.title or "", + Lsp.message or "", + Lsp.percentage and "(" .. Lsp.percentage .. "%)" or "", + }, " ")), opts) + end +end + +--- A provider function for showing the connected LSP client names +---@param opts? table options for explanding null_ls clients, max width percentage, and options passed to the stylize function +---@return function # the function for outputting the LSP client names +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.lsp_client_names({ expand_null_ls = true, truncate = 0.25 }) } +-- @see astronvim.utils.status.utils.stylize +function M.lsp_client_names(opts) + opts = extend_tbl({ expand_null_ls = true, truncate = 0.25 }, opts) + return function(self) + local buf_client_names = {} + for _, client in pairs(vim.lsp.get_active_clients { bufnr = self and self.bufnr or 0 }) do + if client.name == "null-ls" and opts.expand_null_ls then + local null_ls_sources = {} + for _, type in ipairs { "FORMATTING", "DIAGNOSTICS" } do + for _, source in ipairs(status_utils.null_ls_sources(vim.bo.filetype, type)) do + null_ls_sources[source] = true + end + end + vim.list_extend(buf_client_names, vim.tbl_keys(null_ls_sources)) + else + table.insert(buf_client_names, client.name) + end + end + local str = table.concat(buf_client_names, ", ") + if type(opts.truncate) == "number" then + local max_width = math.floor(status_utils.width() * opts.truncate) + if #str > max_width then str = string.sub(str, 0, max_width) .. "…" end + end + return status_utils.stylize(str, opts) + end +end + +--- A provider function for showing if treesitter is connected +---@param opts? table options passed to the stylize function +---@return function # function for outputting TS if treesitter is connected +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.treesitter_status() } +-- @see astronvim.utils.status.utils.stylize +function M.treesitter_status(opts) + return function() return status_utils.stylize(require("nvim-treesitter.parser").has_parser() and "TS" or "", opts) end +end + +--- A provider function for displaying a single string +---@param opts? table options passed to the stylize function +---@return string # the stylized statusline string +-- @usage local heirline_component = { provider = require("astronvim.utils.status").provider.str({ str = "Hello" }) } +-- @see astronvim.utils.status.utils.stylize +function M.str(opts) + opts = extend_tbl({ str = " " }, opts) + return status_utils.stylize(opts.str, opts) +end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/status/utils.lua b/vim/config/nvim/lua/astronvim/utils/status/utils.lua new file mode 100644 index 0000000..6af81ca --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/status/utils.lua @@ -0,0 +1,202 @@ +--- ### AstroNvim Status Utilities +-- +-- Statusline related uitility functions +-- +-- This module can be loaded with `local status_utils = require "astronvim.utils.status.utils"` +-- +-- @module astronvim.utils.status.utils +-- @copyright 2023 +-- @license GNU General Public License v3.0 + +local M = {} + +local env = require "astronvim.utils.status.env" + +local utils = require "astronvim.utils" +local extend_tbl = utils.extend_tbl +local get_icon = utils.get_icon + +--- Convert a component parameter table to a table that can be used with the component builder +---@param opts? table a table of provider options +---@param provider? function|string a provider in `M.providers` +---@return table|false # the provider table that can be used in `M.component.builder` +function M.build_provider(opts, provider, _) + return opts + and { + provider = provider, + opts = opts, + condition = opts.condition, + on_click = opts.on_click, + update = opts.update, + hl = opts.hl, + } + or false +end + +--- Convert key/value table of options to an array of providers for the component builder +---@param opts table the table of options for the components +---@param providers string[] an ordered list like array of providers that are configured in the options table +---@param setup? function a function that takes provider options table, provider name, provider index and returns the setup provider table, optional, default is `M.build_provider` +---@return table # the fully setup options table with the appropriately ordered providers +function M.setup_providers(opts, providers, setup) + setup = setup or M.build_provider + for i, provider in ipairs(providers) do + opts[i] = setup(opts[provider], provider, i) + end + return opts +end + +--- A utility function to get the width of the bar +---@param is_winbar? boolean true if you want the width of the winbar, false if you want the statusline width +---@return integer # the width of the specified bar +function M.width(is_winbar) + return vim.o.laststatus == 3 and not is_winbar and vim.o.columns or vim.api.nvim_win_get_width(0) +end + +--- Add left and/or right padding to a string +---@param str string the string to add padding to +---@param padding table a table of the format `{ left = 0, right = 0}` that defines the number of spaces to include to the left and the right of the string +---@return string # the padded string +function M.pad_string(str, padding) + padding = padding or {} + return str and str ~= "" and string.rep(" ", padding.left or 0) .. str .. string.rep(" ", padding.right or 0) or "" +end + +local function escape(str) return str:gsub("%%", "%%%%") end + +--- A utility function to stylize a string with an icon from lspkind, separators, and left/right padding +---@param str? string the string to stylize +---@param opts? table options of `{ padding = { left = 0, right = 0 }, separator = { left = "|", right = "|" }, escape = true, show_empty = false, icon = { kind = "NONE", padding = { left = 0, right = 0 } } }` +---@return string # the stylized string +-- @usage local string = require("astronvim.utils.status").utils.stylize("Hello", { padding = { left = 1, right = 1 }, icon = { kind = "String" } }) +function M.stylize(str, opts) + opts = extend_tbl({ + padding = { left = 0, right = 0 }, + separator = { left = "", right = "" }, + show_empty = false, + escape = true, + icon = { kind = "NONE", padding = { left = 0, right = 0 } }, + }, opts) + local icon = M.pad_string(get_icon(opts.icon.kind), opts.icon.padding) + return str + and (str ~= "" or opts.show_empty) + and opts.separator.left .. M.pad_string(icon .. (opts.escape and escape(str) or str), opts.padding) .. opts.separator.right + or "" +end + +--- Surround component with separator and color adjustment +---@param separator string|string[] the separator index to use in `env.separators` +---@param color function|string|table the color to use as the separator foreground/component background +---@param component table the component to surround +---@param condition boolean|function the condition for displaying the surrounded component +---@return table # the new surrounded component +function M.surround(separator, color, component, condition) + local function surround_color(self) + local colors = type(color) == "function" and color(self) or color + return type(colors) == "string" and { main = colors } or colors + end + + separator = type(separator) == "string" and env.separators[separator] or separator + local surrounded = { condition = condition } + if separator[1] ~= "" then + table.insert(surrounded, { + provider = separator[1], + hl = function(self) + local s_color = surround_color(self) + if s_color then return { fg = s_color.main, bg = s_color.left } end + end, + }) + end + table.insert(surrounded, { + hl = function(self) + local s_color = surround_color(self) + if s_color then return { bg = s_color.main } end + end, + extend_tbl(component, {}), + }) + if separator[2] ~= "" then + table.insert(surrounded, { + provider = separator[2], + hl = function(self) + local s_color = surround_color(self) + if s_color then return { fg = s_color.main, bg = s_color.right } end + end, + }) + end + return surrounded +end + +--- Encode a position to a single value that can be decoded later +---@param line integer line number of position +---@param col integer column number of position +---@param winnr integer a window number +---@return integer the encoded position +function M.encode_pos(line, col, winnr) return bit.bor(bit.lshift(line, 16), bit.lshift(col, 6), winnr) end + +--- Decode a previously encoded position to it's sub parts +---@param c integer the encoded position +---@return integer line, integer column, integer window +function M.decode_pos(c) return bit.rshift(c, 16), bit.band(bit.rshift(c, 6), 1023), bit.band(c, 63) end + +--- Get a list of registered null-ls providers for a given filetype +---@param filetype string the filetype to search null-ls for +---@return table # a table of null-ls sources +function M.null_ls_providers(filetype) + local registered = {} + -- try to load null-ls + local sources_avail, sources = pcall(require, "null-ls.sources") + if sources_avail then + -- get the available sources of a given filetype + for _, source in ipairs(sources.get_available(filetype)) do + -- get each source name + for method in pairs(source.methods) do + registered[method] = registered[method] or {} + table.insert(registered[method], source.name) + end + end + end + -- return the found null-ls sources + return registered +end + +--- Get the null-ls sources for a given null-ls method +---@param filetype string the filetype to search null-ls for +---@param method string the null-ls method (check null-ls documentation for available methods) +---@return string[] # the available sources for the given filetype and method +function M.null_ls_sources(filetype, method) + local methods_avail, methods = pcall(require, "null-ls.methods") + return methods_avail and M.null_ls_providers(filetype)[methods.internal[method]] or {} +end + +--- A helper function for decoding statuscolumn click events with mouse click pressed, modifier keys, as well as which signcolumn sign was clicked if any +---@param self any the self parameter from Heirline component on_click.callback function call +---@param minwid any the minwid parameter from Heirline component on_click.callback function call +---@param clicks any the clicks parameter from Heirline component on_click.callback function call +---@param button any the button parameter from Heirline component on_click.callback function call +---@param mods any the button parameter from Heirline component on_click.callback function call +---@return table # the argument table with the decoded mouse information and signcolumn signs information +-- @usage local heirline_component = { on_click = { callback = function(...) local args = require("astronvim.utils.status").utils.statuscolumn_clickargs(...) end } } +function M.statuscolumn_clickargs(self, minwid, clicks, button, mods) + local args = { + minwid = minwid, + clicks = clicks, + button = button, + mods = mods, + mousepos = vim.fn.getmousepos(), + } + if not self.signs then self.signs = {} end + args.char = vim.fn.screenstring(args.mousepos.screenrow, args.mousepos.screencol) + if args.char == " " then args.char = vim.fn.screenstring(args.mousepos.screenrow, args.mousepos.screencol - 1) end + args.sign = self.signs[args.char] + if not args.sign then -- update signs if not found on first click + for _, sign_def in ipairs(vim.fn.sign_getdefined()) do + if sign_def.text then self.signs[sign_def.text:gsub("%s", "")] = sign_def end + end + args.sign = self.signs[args.char] + end + vim.api.nvim_set_current_win(args.mousepos.winid) + vim.api.nvim_win_set_cursor(0, { args.mousepos.line, 0 }) + return args +end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/ui.lua b/vim/config/nvim/lua/astronvim/utils/ui.lua new file mode 100644 index 0000000..97363b5 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/ui.lua @@ -0,0 +1,230 @@ +--- ### AstroNvim UI Options +-- +-- Utility functions for easy UI toggles. +-- +-- This module can be loaded with `local ui = require("astronvim.utils.ui")` +-- +-- @module astronvim.utils.ui +-- @see astronvim.utils +-- @copyright 2022 +-- @license GNU General Public License v3.0 + +local M = {} + +local notify = require("astronvim.utils").notify + +local function bool2str(bool) return bool and "on" or "off" end + +--- Toggle notifications for UI toggles +function M.toggle_ui_notifications() -- TODO: rename to toggle_notifications in AstroNvim v4 + vim.g.ui_notifications_enabled = not vim.g.ui_notifications_enabled + notify(string.format("Notifications %s", bool2str(vim.g.ui_notifications_enabled))) +end + +--- Toggle autopairs +function M.toggle_autopairs() + local ok, autopairs = pcall(require, "nvim-autopairs") + if ok then + if autopairs.state.disabled then + autopairs.enable() + else + autopairs.disable() + end + vim.g.autopairs_enabled = autopairs.state.disabled + notify(string.format("autopairs %s", bool2str(not autopairs.state.disabled))) + else + notify "autopairs not available" + end +end + +--- Toggle diagnostics +function M.toggle_diagnostics() + vim.g.diagnostics_mode = (vim.g.diagnostics_mode - 1) % 4 + vim.diagnostic.config(require("astronvim.utils.lsp").diagnostics[vim.g.diagnostics_mode]) + if vim.g.diagnostics_mode == 0 then + notify "diagnostics off" + elseif vim.g.diagnostics_mode == 1 then + notify "only status diagnostics" + elseif vim.g.diagnostics_mode == 2 then + notify "virtual text off" + else + notify "all diagnostics on" + end +end + +--- Toggle background="dark"|"light" +function M.toggle_background() + vim.go.background = vim.go.background == "light" and "dark" or "light" + notify(string.format("background=%s", vim.go.background)) +end + +--- Toggle cmp entrirely +function M.toggle_cmp() + vim.g.cmp_enabled = not vim.g.cmp_enabled + local ok, _ = pcall(require, "cmp") + notify(ok and string.format("completion %s", bool2str(vim.g.cmp_enabled)) or "completion not available") +end + +--- Toggle auto format +function M.toggle_autoformat() + vim.g.autoformat_enabled = not vim.g.autoformat_enabled + notify(string.format("Global autoformatting %s", bool2str(vim.g.autoformat_enabled))) +end + +--- Toggle buffer local auto format +function M.toggle_buffer_autoformat() + local old_val = vim.b.autoformat_enabled + if old_val == nil then old_val = vim.g.autoformat_enabled end + vim.b.autoformat_enabled = not old_val + notify(string.format("Buffer autoformatting %s", bool2str(vim.b.autoformat_enabled))) +end + +--- Toggle buffer semantic token highlighting for all language servers that support it +---@param bufnr? number the buffer to toggle the clients on +function M.toggle_buffer_semantic_tokens(bufnr) + vim.b.semantic_tokens_enabled = not vim.b.semantic_tokens_enabled + for _, client in ipairs(vim.lsp.get_active_clients()) do + if client.server_capabilities.semanticTokensProvider then + vim.lsp.semantic_tokens[vim.b.semantic_tokens_enabled and "start" or "stop"](bufnr or 0, client.id) + notify(string.format("Buffer lsp semantic highlighting %s", bool2str(vim.b.semantic_tokens_enabled))) + end + end +end + +--- Toggle buffer LSP inlay hints +---@param bufnr? number the buffer to toggle the clients on +function M.toggle_buffer_inlay_hints(bufnr) + vim.b.inlay_hints_enabled = not vim.b.inlay_hints_enabled + -- TODO: remove check after dropping support for Neovim v0.9 + if vim.lsp.inlay_hint then + vim.lsp.inlay_hint(bufnr or 0, vim.b.inlay_hints_enabled) + notify(string.format("Inlay hints %s", bool2str(vim.b.inlay_hints_enabled))) + end +end + +--- Toggle codelens +function M.toggle_codelens() + vim.g.codelens_enabled = not vim.g.codelens_enabled + if not vim.g.codelens_enabled then vim.lsp.codelens.clear() end + notify(string.format("CodeLens %s", bool2str(vim.g.codelens_enabled))) +end + +--- Toggle showtabline=2|0 +function M.toggle_tabline() + vim.opt.showtabline = vim.opt.showtabline:get() == 0 and 2 or 0 + notify(string.format("tabline %s", bool2str(vim.opt.showtabline:get() == 2))) +end + +--- Toggle conceal=2|0 +function M.toggle_conceal() + vim.opt.conceallevel = vim.opt.conceallevel:get() == 0 and 2 or 0 + notify(string.format("conceal %s", bool2str(vim.opt.conceallevel:get() == 2))) +end + +--- Toggle laststatus=3|2|0 +function M.toggle_statusline() + local laststatus = vim.opt.laststatus:get() + local status + if laststatus == 0 then + vim.opt.laststatus = 2 + status = "local" + elseif laststatus == 2 then + vim.opt.laststatus = 3 + status = "global" + elseif laststatus == 3 then + vim.opt.laststatus = 0 + status = "off" + end + notify(string.format("statusline %s", status)) +end + +--- Toggle signcolumn="auto"|"no" +function M.toggle_signcolumn() + if vim.wo.signcolumn == "no" then + vim.wo.signcolumn = "yes" + elseif vim.wo.signcolumn == "yes" then + vim.wo.signcolumn = "auto" + else + vim.wo.signcolumn = "no" + end + notify(string.format("signcolumn=%s", vim.wo.signcolumn)) +end + +--- Set the indent and tab related numbers +function M.set_indent() + local input_avail, input = pcall(vim.fn.input, "Set indent value (>0 expandtab, <=0 noexpandtab): ") + if input_avail then + local indent = tonumber(input) + if not indent or indent == 0 then return end + vim.bo.expandtab = (indent > 0) -- local to buffer + indent = math.abs(indent) + vim.bo.tabstop = indent -- local to buffer + vim.bo.softtabstop = indent -- local to buffer + vim.bo.shiftwidth = indent -- local to buffer + notify(string.format("indent=%d %s", indent, vim.bo.expandtab and "expandtab" or "noexpandtab")) + end +end + +--- Change the number display modes +function M.change_number() + local number = vim.wo.number -- local to window + local relativenumber = vim.wo.relativenumber -- local to window + if not number and not relativenumber then + vim.wo.number = true + elseif number and not relativenumber then + vim.wo.relativenumber = true + elseif number and relativenumber then + vim.wo.number = false + else -- not number and relativenumber + vim.wo.relativenumber = false + end + notify(string.format("number %s, relativenumber %s", bool2str(vim.wo.number), bool2str(vim.wo.relativenumber))) +end + +--- Toggle spell +function M.toggle_spell() + vim.wo.spell = not vim.wo.spell -- local to window + notify(string.format("spell %s", bool2str(vim.wo.spell))) +end + +--- Toggle paste +function M.toggle_paste() + vim.opt.paste = not vim.opt.paste:get() -- local to window + notify(string.format("paste %s", bool2str(vim.opt.paste:get()))) +end + +--- Toggle wrap +function M.toggle_wrap() + vim.wo.wrap = not vim.wo.wrap -- local to window + notify(string.format("wrap %s", bool2str(vim.wo.wrap))) +end + +--- Toggle syntax highlighting and treesitter +function M.toggle_syntax() + local ts_avail, parsers = pcall(require, "nvim-treesitter.parsers") + if vim.g.syntax_on then -- global var for on//off + if ts_avail and parsers.has_parser() then vim.cmd.TSBufDisable "highlight" end + vim.cmd.syntax "off" -- set vim.g.syntax_on = false + else + if ts_avail and parsers.has_parser() then vim.cmd.TSBufEnable "highlight" end + vim.cmd.syntax "on" -- set vim.g.syntax_on = true + end + notify(string.format("syntax %s", bool2str(vim.g.syntax_on))) +end + +--- Toggle URL/URI syntax highlighting rules +function M.toggle_url_match() + vim.g.highlighturl_enabled = not vim.g.highlighturl_enabled + require("astronvim.utils").set_url_match() +end + +local last_active_foldcolumn +--- Toggle foldcolumn=0|1 +function M.toggle_foldcolumn() + local curr_foldcolumn = vim.wo.foldcolumn + if curr_foldcolumn ~= "0" then last_active_foldcolumn = curr_foldcolumn end + vim.wo.foldcolumn = curr_foldcolumn == "0" and (last_active_foldcolumn or "1") or "0" + notify(string.format("foldcolumn=%s", vim.wo.foldcolumn)) +end + +return M diff --git a/vim/config/nvim/lua/astronvim/utils/updater.lua b/vim/config/nvim/lua/astronvim/utils/updater.lua new file mode 100644 index 0000000..9b5e268 --- /dev/null +++ b/vim/config/nvim/lua/astronvim/utils/updater.lua @@ -0,0 +1,339 @@ +--- ### AstroNvim Updater +-- +-- AstroNvim Updater utilities to use within AstroNvim and user configurations. +-- +-- This module can also loaded with `local updater = require("astronvim.utils.updater")` +-- +-- @module astronvim.utils.updater +-- @see astronvim.utils +-- @copyright 2022 +-- @license GNU General Public License v3.0 + +local git = require "astronvim.utils.git" + +local M = {} + +local utils = require "astronvim.utils" +local notify = utils.notify + +local function echo(messages) + -- if no parameter provided, echo a new line + messages = messages or { { "\n" } } + if type(messages) == "table" then vim.api.nvim_echo(messages, false, {}) end +end + +local function confirm_prompt(messages, type) + return vim.fn.confirm(messages, "&Yes\n&No", (type == "Error" or type == "Warning") and 2 or 1, type or "Question") + == 1 +end + +--- Helper function to generate AstroNvim snapshots (For internal use only) +---@param write? boolean Whether or not to write to the snapshot file (default: false) +---@return table # The plugin specification table of the snapshot +function M.generate_snapshot(write) + local file + local prev_snapshot = require(astronvim.updater.snapshot.module) + for _, plugin in ipairs(prev_snapshot) do + prev_snapshot[plugin[1]] = plugin + end + local plugins = assert(require("lazy").plugins()) + table.sort(plugins, function(l, r) return l[1] < r[1] end) + local function git_commit(dir) + local commit = assert(utils.cmd({ "git", "-C", dir, "rev-parse", "HEAD" }, false)) + if commit then return vim.trim(commit) end + end + if write == true then + file = assert(io.open(astronvim.updater.snapshot.path, "w")) + file:write "return {\n" + end + local snapshot = vim.tbl_map(function(plugin) + plugin = { plugin[1], commit = git_commit(plugin.dir), version = plugin.version } + if prev_snapshot[plugin[1]] and prev_snapshot[plugin[1]].version then + plugin.version = prev_snapshot[plugin[1]].version + end + if file then + file:write((" { %q, "):format(plugin[1])) + if plugin.version then + file:write(("version = %q"):format(plugin.version)) + else + file:write(("commit = %q"):format(plugin.commit)) + end + file:write ", optional = true },\n" + end + return plugin + end, plugins) + if file then + file:write "}\n" + file:close() + end + return snapshot +end + +--- Get the current AstroNvim version +---@param quiet? boolean Whether to quietly execute or send a notification +---@return string # The current AstroNvim version string +function M.version(quiet) + local version = astronvim.install.version or git.current_version(false) or "unknown" + if astronvim.updater.options.channel ~= "stable" then version = ("nightly (%s)"):format(version) end + if version and not quiet then notify(("Version: *%s*"):format(version)) end + return version +end + +--- Get the full AstroNvim changelog +---@param quiet? boolean Whether to quietly execute or display the changelog +---@return table # The current AstroNvim changelog table of commit messages +function M.changelog(quiet) + local summary = {} + vim.list_extend(summary, git.pretty_changelog(git.get_commit_range())) + if not quiet then echo(summary) end + return summary +end + +--- Attempt an update of AstroNvim +---@param target string The target if checking out a specific tag or commit or nil if just pulling +local function attempt_update(target, opts) + -- if updating to a new stable version or a specific commit checkout the provided target + if opts.channel == "stable" or opts.commit then + return git.checkout(target, false) + -- if no target, pull the latest + else + return git.pull(false) + end +end + +--- Cancelled update message +local cancelled_message = { { "Update cancelled", "WarningMsg" } } + +--- Sync Packer and then update Mason +function M.update_packages() + require("lazy").sync { wait = true } + require("astronvim.utils.mason").update_all() +end + +--- Create a table of options for the currently installed AstroNvim version +---@param write? boolean Whether or not to write to the rollback file (default: false) +---@return table # The table of updater options +function M.create_rollback(write) + local snapshot = { branch = git.current_branch(), commit = git.local_head() } + if snapshot.branch == "HEAD" then snapshot.branch = "main" end + snapshot.remote = git.branch_remote(snapshot.branch, false) or "origin" + snapshot.remotes = { [snapshot.remote] = git.remote_url(snapshot.remote) } + + if write == true then + local file = assert(io.open(astronvim.updater.rollback_file, "w")) + file:write("return " .. vim.inspect(snapshot, { newline = " ", indent = "" })) + file:close() + end + + return snapshot +end + +--- AstroNvim's rollback to saved previous version function +function M.rollback() + local rollback_avail, rollback_opts = pcall(dofile, astronvim.updater.rollback_file) + if not rollback_avail then + notify("No rollback file available", vim.log.levels.ERROR) + return + end + M.update(rollback_opts) +end + +--- Check if an update is available +---@param opts? table the settings to use for checking for an update +---@return table|boolean? # The information of an available update (`{ source = string, target = string }`), false if no update is available, or nil if there is an error +function M.update_available(opts) + if not opts then opts = astronvim.updater.options end + opts = require("astronvim.utils").extend_tbl({ remote = "origin" }, opts) + -- if the git command is not available, then throw an error + if not git.available() then + notify( + "`git` command is not available, please verify it is accessible in a command line. This may be an issue with your `PATH`", + vim.log.levels.ERROR + ) + return + end + + -- if installed with an external package manager, disable the internal updater + if not git.is_repo() then + notify("Updater not available for non-git installations", vim.log.levels.ERROR) + return + end + -- set up any remotes defined by the user if they do not exist + for remote, entry in pairs(opts.remotes and opts.remotes or {}) do + local url = git.parse_remote_url(entry) + local current_url = git.remote_url(remote, false) + local check_needed = false + if not current_url then + git.remote_add(remote, url) + check_needed = true + elseif + current_url ~= url + and confirm_prompt( + ("Remote %s is currently: %s\n" .. "Would you like us to set it to %s ?"):format(remote, current_url, url) + ) + then + git.remote_update(remote, url) + check_needed = true + end + if check_needed and git.remote_url(remote, false) ~= url then + vim.api.nvim_err_writeln("Error setting up remote " .. remote .. " to " .. url) + return + end + end + local is_stable = opts.channel == "stable" + if is_stable then + opts.branch = "main" + elseif not opts.branch then + opts.branch = "nightly" + end + -- setup branch if missing + if not git.ref_verify(opts.remote .. "/" .. opts.branch, false) then + git.remote_set_branches(opts.remote, opts.branch, false) + end + -- fetch the latest remote + if not git.fetch(opts.remote) then + vim.api.nvim_err_writeln("Error fetching remote: " .. opts.remote) + return + end + -- switch to the necessary branch only if not on the stable channel + if not is_stable then + local local_branch = (opts.remote == "origin" and "" or (opts.remote .. "_")) .. opts.branch + if git.current_branch() ~= local_branch then + echo { + { "Switching to branch: " }, + { opts.remote .. "/" .. opts.branch .. "\n\n", "String" }, + } + if not git.checkout(local_branch, false) then + git.checkout("-b " .. local_branch .. " " .. opts.remote .. "/" .. opts.branch, false) + end + end + -- check if the branch was switched to successfully + if git.current_branch() ~= local_branch then + vim.api.nvim_err_writeln("Error checking out branch: " .. opts.remote .. "/" .. opts.branch) + return + end + end + local update = { source = git.local_head() } + if is_stable then -- if stable get tag commit + local version_search = opts.version or "latest" + update.version = git.latest_version(git.get_versions(version_search)) + if not update.version then -- continue only if stable version is found + vim.api.nvim_err_writeln("Error finding version: " .. version_search) + return + end + update.target = git.tag_commit(update.version) + elseif opts.commit then -- if commit specified use it + update.target = git.branch_contains(opts.remote, opts.branch, opts.commit) and opts.commit or nil + else -- get most recent commit + update.target = git.remote_head(opts.remote, opts.branch) + end + + if not update.source or not update.target then -- continue if current and target commits were found + vim.api.nvim_err_writeln "Error checking for updates" + return + elseif update.source ~= update.target then + -- update available + return update + else + return false + end +end + +--- AstroNvim's updater function +---@param opts? table the settings to use for the update +function M.update(opts) + if not opts then opts = astronvim.updater.options end + opts = require("astronvim.utils").extend_tbl( + { remote = "origin", show_changelog = true, sync_plugins = true, auto_quit = false }, + opts + ) + local available_update = M.update_available(opts) + if available_update == nil then + return + elseif not available_update then -- continue if current and target commits were found + notify "No updates available" + elseif -- prompt user if they want to accept update + not opts.skip_prompts + and not confirm_prompt( + ("Update available to %s\nUpdating requires a restart, continue?"):format( + available_update.version or available_update.target + ) + ) + then + echo(cancelled_message) + return + else -- perform update + local source, target = available_update.source, available_update.target + M.create_rollback(true) -- create rollback file before updating + -- calculate and print the changelog + local changelog = git.get_commit_range(source, target) + local breaking = git.breaking_changes(changelog) + if + #breaking > 0 + and not opts.skip_prompts + and not confirm_prompt( + ("Update contains the following breaking changes:\n%s\nWould you like to continue?"):format( + table.concat(breaking, "\n") + ), + "Warning" + ) + then + echo(cancelled_message) + return + end + -- attempt an update + local updated = attempt_update(target, opts) + -- check for local file conflicts and prompt user to continue or abort + if + not updated + and not opts.skip_prompts + and not confirm_prompt( + "Unable to pull due to local modifications to base files.\nReset local files and continue?", + "Error" + ) + then + echo(cancelled_message) + return + -- if continued and there were errors reset the base config and attempt another update + elseif not updated then + git.hard_reset(source) + updated = attempt_update(target, opts) + end + -- if update was unsuccessful throw an error + if not updated then + vim.api.nvim_err_writeln "Error occurred performing update" + return + end + -- print a summary of the update with the changelog + local summary = { + { "AstroNvim updated successfully to ", "Title" }, + { git.current_version(), "String" }, + { "!\n", "Title" }, + { + opts.auto_quit and "AstroNvim will now update plugins and quit.\n\n" + or "After plugins update, please restart.\n\n", + "WarningMsg", + }, + } + if opts.show_changelog and #changelog > 0 then + vim.list_extend(summary, { { "Changelog:\n", "Title" } }) + vim.list_extend(summary, git.pretty_changelog(changelog)) + end + echo(summary) + + -- if the user wants to auto quit, create an autocommand to quit AstroNvim on the update completing + if opts.auto_quit then + vim.api.nvim_create_autocmd("User", { + desc = "Auto quit AstroNvim after update completes", + pattern = "AstroUpdateComplete", + command = "quitall", + }) + end + + require("lazy.core.plugin").load() -- force immediate reload of lazy + if opts.sync_plugins then require("lazy").sync { wait = true } end + utils.event "UpdateComplete" + end +end + +return M diff --git a/vim/config/nvim/lua/lazy_snapshot.lua b/vim/config/nvim/lua/lazy_snapshot.lua new file mode 100644 index 0000000..58fc3a2 --- /dev/null +++ b/vim/config/nvim/lua/lazy_snapshot.lua @@ -0,0 +1,54 @@ +return { + { "AstroNvim/astrotheme", version = "^2", optional = true }, + { "JoosepAlviste/nvim-ts-context-commentstring", commit = "7f625207f225eea97ef7a6abe7611e556c396d2f", optional = true }, + { "L3MON4D3/LuaSnip", version = "^1", optional = true }, + { "MunifTanjim/nui.nvim", version = "^0.1", optional = true }, + { "NMAC427/guess-indent.nvim", commit = "b8ae749fce17aa4c267eec80a6984130b94f80b2", optional = true }, + { "NvChad/nvim-colorizer.lua", commit = "dde3084106a70b9a79d48f426f6d6fec6fd203f7", optional = true }, + { "Shatur/neovim-session-manager", commit = "51827268c5ee56567b7033af9ed547ab704553b9", optional = true }, + { "akinsho/toggleterm.nvim", version = "^2", optional = true }, + { "b0o/SchemaStore.nvim", commit = "cd5c2a0db954011fcbeac7bbbc0c7ae9e23626e3", optional = true }, + { "echasnovski/mini.bufremove", commit = "7821606e35c1ac931b56d8e3155f45ffe76ee7e5", optional = true }, + { "folke/lazy.nvim", version = "^9", optional = true }, + { "folke/neoconf.nvim", version = "^1", optional = true }, + { "folke/neodev.nvim", version = "^2", optional = true }, + { "folke/which-key.nvim", version = "^1", optional = true }, + { "goolord/alpha-nvim", commit = "e4fc5e29b731bdf55d204c5c6a11dc3be70f3b65", optional = true }, + { "hrsh7th/cmp-buffer", commit = "3022dbc9166796b644a841a02de8dd1cc1d311fa", optional = true }, + { "hrsh7th/cmp-nvim-lsp", commit = "44b16d11215dce86f253ce0c30949813c0a90765", optional = true }, + { "hrsh7th/cmp-path", commit = "91ff86cd9c29299a64f968ebb45846c485725f23", optional = true }, + { "hrsh7th/nvim-cmp", commit = "c4e491a87eeacf0408902c32f031d802c7eafce8", optional = true }, + { "jay-babu/mason-null-ls.nvim", version = "^2", optional = true }, + { "jay-babu/mason-nvim-dap.nvim", version = "^2", optional = true }, + { "jose-elias-alvarez/null-ls.nvim", commit = "db09b6c691def0038c456551e4e2772186449f35", optional = true }, + { "kevinhwang91/nvim-ufo", version = "^1", optional = true }, + { "kevinhwang91/promise-async", version = "^1", optional = true }, + { "lewis6991/gitsigns.nvim", version = "^0.6", optional = true }, + { "lukas-reineke/indent-blankline.nvim", version = "^2", optional = true }, + { "max397574/better-escape.nvim", commit = "7031dc734add47bb71c010e0551829fa5799375f", optional = true }, + { "mfussenegger/nvim-dap", version = "^0.6", optional = true }, + { "mrjones2014/smart-splits.nvim", version = "^1", optional = true }, + { "neovim/nvim-lspconfig", commit = "4b26897a80c41eb2f116b271cbdcd4686fb52dd6", optional = true }, + { "numToStr/Comment.nvim", version = "^0.8", optional = true }, + { "nvim-lua/plenary.nvim", version = "^0.1", optional = true }, + { "nvim-neo-tree/neo-tree.nvim", version = "^3", optional = true }, + { "nvim-telescope/telescope-fzf-native.nvim", commit = "9bc8237565ded606e6c366a71c64c0af25cd7a50", optional = true }, + { "nvim-telescope/telescope.nvim", version = "^0.1", optional = true }, + { "nvim-tree/nvim-web-devicons", commit = "efbfed0567ef4bfac3ce630524a0f6c8451c5534", optional = true }, + { "nvim-treesitter/nvim-treesitter", commit = "51ea343f705a89326cff8dd7a0542d7fe0e6699a", optional = true }, + { "nvim-treesitter/nvim-treesitter-textobjects", commit = "52f1f3280d9092bfaee5c45be5962fabee3d9654", optional = true }, + { "onsails/lspkind.nvim", commit = "57610d5ab560c073c465d6faf0c19f200cb67e6e", optional = true }, + { "rafamadriz/friendly-snippets", commit = "8f5db6c5b691a6bbaa5dabd9afeb164ef8a06d04", optional = true }, + { "rcarriga/cmp-dap", commit = "d16f14a210cd28988b97ca8339d504533b7e09a4", optional = true }, + { "rcarriga/nvim-dap-ui", version = "^3", optional = true }, + { "rcarriga/nvim-notify", version = "^3", optional = true }, + { "rebelot/heirline.nvim", version = "^1", optional = true }, + { "s1n7ax/nvim-window-picker", version = "^2", optional = true }, + { "saadparwaiz1/cmp_luasnip", commit = "18095520391186d634a0045dacaa346291096566", optional = true }, + { "stevearc/aerial.nvim", version = "^1", optional = true }, + { "stevearc/dressing.nvim", version = "^1", optional = true }, + { "williamboman/mason-lspconfig.nvim", version = "^1.1", optional = true }, + { "williamboman/mason.nvim", version = "^1", optional = true }, + { "windwp/nvim-autopairs", commit = "ae5b41ce880a6d850055e262d6dfebd362bb276e", optional = true }, + { "windwp/nvim-ts-autotag", commit = "6be1192965df35f94b8ea6d323354f7dc7a557e4", optional = true }, +} diff --git a/vim/config/nvim/lua/plugins/alpha.lua b/vim/config/nvim/lua/plugins/alpha.lua new file mode 100644 index 0000000..90ab1ec --- /dev/null +++ b/vim/config/nvim/lua/plugins/alpha.lua @@ -0,0 +1,38 @@ +return { + "goolord/alpha-nvim", + cmd = "Alpha", + opts = function() + local dashboard = require "alpha.themes.dashboard" + dashboard.section.header.val = { + " █████ ███████ ████████ ██████ ██████", + "██ ██ ██ ██ ██ ██ ██ ██", + "███████ ███████ ██ ██████ ██ ██", + "██ ██ ██ ██ ██ ██ ██ ██", + "██ ██ ███████ ██ ██ ██ ██████", + " ", + " ███ ██ ██ ██ ██ ███ ███", + " ████ ██ ██ ██ ██ ████ ████", + " ██ ██ ██ ██ ██ ██ ██ ████ ██", + " ██ ██ ██ ██ ██ ██ ██ ██ ██", + " ██ ████ ████ ██ ██ ██", + } + dashboard.section.header.opts.hl = "DashboardHeader" + + local button = require("astronvim.utils").alpha_button + local get_icon = require("astronvim.utils").get_icon + dashboard.section.buttons.val = { + button("LDR n ", get_icon("FileNew", 2, true) .. "New File "), + button("LDR f f", get_icon("Search", 2, true) .. "Find File "), + button("LDR f o", get_icon("DefaultFile", 2, true) .. "Recents "), + button("LDR f w", get_icon("WordFile", 2, true) .. "Find Word "), + button("LDR f '", get_icon("Bookmarks", 2, true) .. "Bookmarks "), + button("LDR S l", get_icon("Refresh", 2, true) .. "Last Session "), + } + + dashboard.config.layout[1].val = vim.fn.max { 2, vim.fn.floor(vim.fn.winheight(0) * 0.2) } + dashboard.config.layout[3].val = 5 + dashboard.config.opts.noautocmd = true + return dashboard + end, + config = require "plugins.configs.alpha", +} diff --git a/vim/config/nvim/lua/plugins/cmp.lua b/vim/config/nvim/lua/plugins/cmp.lua new file mode 100644 index 0000000..341287c --- /dev/null +++ b/vim/config/nvim/lua/plugins/cmp.lua @@ -0,0 +1,112 @@ +return { + { + "L3MON4D3/LuaSnip", + build = vim.fn.has "win32" == 0 + and "echo 'NOTE: jsregexp is optional, so not a big deal if it fails to build\n'; make install_jsregexp" + or nil, + dependencies = { "rafamadriz/friendly-snippets" }, + opts = { store_selection_keys = "" }, + config = require "plugins.configs.luasnip", + }, + { + "hrsh7th/nvim-cmp", + dependencies = { + "saadparwaiz1/cmp_luasnip", + "hrsh7th/cmp-buffer", + "hrsh7th/cmp-path", + "hrsh7th/cmp-nvim-lsp", + }, + event = "InsertEnter", + opts = function() + local cmp = require "cmp" + local snip_status_ok, luasnip = pcall(require, "luasnip") + local lspkind_status_ok, lspkind = pcall(require, "lspkind") + local utils = require "astronvim.utils" + if not snip_status_ok then return end + local border_opts = { + border = "single", + winhighlight = "NormalFloat:NormalFloat,FloatBorder:FloatBorder", + } + + local function has_words_before() + local line, col = unpack(vim.api.nvim_win_get_cursor(0)) + return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match "%s" == nil + end + + return { + enabled = function() + local dap_prompt = utils.is_available "cmp-dap" -- add interoperability with cmp-dap + and vim.tbl_contains( + { "dap-repl", "dapui_watches", "dapui_hover" }, + vim.api.nvim_get_option_value("filetype", { buf = 0 }) + ) + if vim.api.nvim_get_option_value("buftype", { buf = 0 }) == "prompt" and not dap_prompt then return false end + return vim.g.cmp_enabled + end, + preselect = cmp.PreselectMode.None, + formatting = { + fields = { "kind", "abbr", "menu" }, + format = lspkind_status_ok and lspkind.cmp_format(utils.plugin_opts "lspkind.nvim") or nil, + }, + snippet = { + expand = function(args) luasnip.lsp_expand(args.body) end, + }, + duplicates = { + nvim_lsp = 1, + luasnip = 1, + cmp_tabnine = 1, + buffer = 1, + path = 1, + }, + confirm_opts = { + behavior = cmp.ConfirmBehavior.Replace, + select = false, + }, + window = { + completion = cmp.config.window.bordered(border_opts), + documentation = cmp.config.window.bordered(border_opts), + }, + mapping = { + [""] = cmp.mapping.select_prev_item { behavior = cmp.SelectBehavior.Select }, + [""] = cmp.mapping.select_next_item { behavior = cmp.SelectBehavior.Select }, + [""] = cmp.mapping.select_prev_item { behavior = cmp.SelectBehavior.Insert }, + [""] = cmp.mapping.select_next_item { behavior = cmp.SelectBehavior.Insert }, + [""] = cmp.mapping.select_prev_item { behavior = cmp.SelectBehavior.Insert }, + [""] = cmp.mapping.select_next_item { behavior = cmp.SelectBehavior.Insert }, + [""] = cmp.mapping(cmp.mapping.scroll_docs(-4), { "i", "c" }), + [""] = cmp.mapping(cmp.mapping.scroll_docs(4), { "i", "c" }), + [""] = cmp.mapping(cmp.mapping.complete(), { "i", "c" }), + [""] = cmp.config.disable, + [""] = cmp.mapping { i = cmp.mapping.abort(), c = cmp.mapping.close() }, + [""] = cmp.mapping.confirm { select = false }, + [""] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_next_item() + elseif luasnip.expand_or_jumpable() then + luasnip.expand_or_jump() + elseif has_words_before() then + cmp.complete() + else + fallback() + end + end, { "i", "s" }), + [""] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_prev_item() + elseif luasnip.jumpable(-1) then + luasnip.jump(-1) + else + fallback() + end + end, { "i", "s" }), + }, + sources = cmp.config.sources { + { name = "nvim_lsp", priority = 1000 }, + { name = "luasnip", priority = 750 }, + { name = "buffer", priority = 500 }, + { name = "path", priority = 250 }, + }, + } + end, + }, +} diff --git a/vim/config/nvim/lua/plugins/configs/alpha.lua b/vim/config/nvim/lua/plugins/configs/alpha.lua new file mode 100644 index 0000000..3619166 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/alpha.lua @@ -0,0 +1,17 @@ +return function(_, opts) + require("alpha").setup(opts.config) + + vim.api.nvim_create_autocmd("User", { + pattern = "LazyVimStarted", + desc = "Add Alpha dashboard footer", + once = true, + callback = function() + local stats = require("lazy").stats() + local ms = math.floor(stats.startuptime * 100 + 0.5) / 100 + opts.section.footer.val = + { " ", " ", " ", "AstroNvim loaded " .. stats.count .. " plugins  in " .. ms .. "ms" } + opts.section.footer.opts.hl = "DashboardFooter" + pcall(vim.cmd.AlphaRedraw) + end, + }) +end diff --git a/vim/config/nvim/lua/plugins/configs/cmp-dap.lua b/vim/config/nvim/lua/plugins/configs/cmp-dap.lua new file mode 100644 index 0000000..cb4f048 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/cmp-dap.lua @@ -0,0 +1,7 @@ +return function() + require("cmp").setup.filetype({ "dap-repl", "dapui_watches", "dapui_hover" }, { + sources = { + { name = "dap" }, + }, + }) +end diff --git a/vim/config/nvim/lua/plugins/configs/guess-indent.lua b/vim/config/nvim/lua/plugins/configs/guess-indent.lua new file mode 100644 index 0000000..d291d70 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/guess-indent.lua @@ -0,0 +1,4 @@ +return function(_, opts) + require("guess-indent").setup(opts) + vim.cmd.lua { args = { "require('guess-indent').set_from_buffer('auto_cmd')" }, mods = { silent = true } } +end diff --git a/vim/config/nvim/lua/plugins/configs/heirline.lua b/vim/config/nvim/lua/plugins/configs/heirline.lua new file mode 100644 index 0000000..60d5e6a --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/heirline.lua @@ -0,0 +1,117 @@ +return function(_, opts) + local heirline = require "heirline" + local C = require("astronvim.utils.status.env").fallback_colors + local get_hlgroup = require("astronvim.utils").get_hlgroup + local lualine_mode = require("astronvim.utils.status.hl").lualine_mode + local function resolve_lualine(orig, ...) return (not orig or orig == "NONE") and lualine_mode(...) or orig end + + local function setup_colors() + local Normal = get_hlgroup("Normal", { fg = C.fg, bg = C.bg }) + local Comment = get_hlgroup("Comment", { fg = C.bright_grey, bg = C.bg }) + local Error = get_hlgroup("Error", { fg = C.red, bg = C.bg }) + local StatusLine = get_hlgroup("StatusLine", { fg = C.fg, bg = C.dark_bg }) + local TabLine = get_hlgroup("TabLine", { fg = C.grey, bg = C.none }) + local TabLineFill = get_hlgroup("TabLineFill", { fg = C.fg, bg = C.dark_bg }) + local TabLineSel = get_hlgroup("TabLineSel", { fg = C.fg, bg = C.none }) + local WinBar = get_hlgroup("WinBar", { fg = C.bright_grey, bg = C.bg }) + local WinBarNC = get_hlgroup("WinBarNC", { fg = C.grey, bg = C.bg }) + local Conditional = get_hlgroup("Conditional", { fg = C.bright_purple, bg = C.dark_bg }) + local String = get_hlgroup("String", { fg = C.green, bg = C.dark_bg }) + local TypeDef = get_hlgroup("TypeDef", { fg = C.yellow, bg = C.dark_bg }) + local GitSignsAdd = get_hlgroup("GitSignsAdd", { fg = C.green, bg = C.dark_bg }) + local GitSignsChange = get_hlgroup("GitSignsChange", { fg = C.orange, bg = C.dark_bg }) + local GitSignsDelete = get_hlgroup("GitSignsDelete", { fg = C.bright_red, bg = C.dark_bg }) + local DiagnosticError = get_hlgroup("DiagnosticError", { fg = C.bright_red, bg = C.dark_bg }) + local DiagnosticWarn = get_hlgroup("DiagnosticWarn", { fg = C.orange, bg = C.dark_bg }) + local DiagnosticInfo = get_hlgroup("DiagnosticInfo", { fg = C.white, bg = C.dark_bg }) + local DiagnosticHint = get_hlgroup("DiagnosticHint", { fg = C.bright_yellow, bg = C.dark_bg }) + local HeirlineInactive = resolve_lualine(get_hlgroup("HeirlineInactive", { bg = nil }).bg, "inactive", C.dark_grey) + local HeirlineNormal = resolve_lualine(get_hlgroup("HeirlineNormal", { bg = nil }).bg, "normal", C.blue) + local HeirlineInsert = resolve_lualine(get_hlgroup("HeirlineInsert", { bg = nil }).bg, "insert", C.green) + local HeirlineVisual = resolve_lualine(get_hlgroup("HeirlineVisual", { bg = nil }).bg, "visual", C.purple) + local HeirlineReplace = resolve_lualine(get_hlgroup("HeirlineReplace", { bg = nil }).bg, "replace", C.bright_red) + local HeirlineCommand = resolve_lualine(get_hlgroup("HeirlineCommand", { bg = nil }).bg, "command", C.bright_yellow) + local HeirlineTerminal = resolve_lualine(get_hlgroup("HeirlineTerminal", { bg = nil }).bg, "insert", HeirlineInsert) + + local colors = astronvim.user_opts("heirline.colors", { + close_fg = Error.fg, + fg = StatusLine.fg, + bg = StatusLine.bg, + section_fg = StatusLine.fg, + section_bg = StatusLine.bg, + git_branch_fg = Conditional.fg, + mode_fg = StatusLine.bg, + treesitter_fg = String.fg, + scrollbar = TypeDef.fg, + git_added = GitSignsAdd.fg, + git_changed = GitSignsChange.fg, + git_removed = GitSignsDelete.fg, + diag_ERROR = DiagnosticError.fg, + diag_WARN = DiagnosticWarn.fg, + diag_INFO = DiagnosticInfo.fg, + diag_HINT = DiagnosticHint.fg, + winbar_fg = WinBar.fg, + winbar_bg = WinBar.bg, + winbarnc_fg = WinBarNC.fg, + winbarnc_bg = WinBarNC.bg, + tabline_bg = TabLineFill.bg, + tabline_fg = TabLineFill.bg, + buffer_fg = Comment.fg, + buffer_path_fg = WinBarNC.fg, + buffer_close_fg = Comment.fg, + buffer_bg = TabLineFill.bg, + buffer_active_fg = Normal.fg, + buffer_active_path_fg = WinBarNC.fg, + buffer_active_close_fg = Error.fg, + buffer_active_bg = Normal.bg, + buffer_visible_fg = Normal.fg, + buffer_visible_path_fg = WinBarNC.fg, + buffer_visible_close_fg = Error.fg, + buffer_visible_bg = Normal.bg, + buffer_overflow_fg = Comment.fg, + buffer_overflow_bg = TabLineFill.bg, + buffer_picker_fg = Error.fg, + tab_close_fg = Error.fg, + tab_close_bg = TabLineFill.bg, + tab_fg = TabLine.fg, + tab_bg = TabLine.bg, + tab_active_fg = TabLineSel.fg, + tab_active_bg = TabLineSel.bg, + inactive = HeirlineInactive, + normal = HeirlineNormal, + insert = HeirlineInsert, + visual = HeirlineVisual, + replace = HeirlineReplace, + command = HeirlineCommand, + terminal = HeirlineTerminal, + }) + + for _, section in ipairs { + "git_branch", + "file_info", + "git_diff", + "diagnostics", + "lsp", + "macro_recording", + "mode", + "cmd_info", + "treesitter", + "nav", + } do + if not colors[section .. "_bg"] then colors[section .. "_bg"] = colors["section_bg"] end + if not colors[section .. "_fg"] then colors[section .. "_fg"] = colors["section_fg"] end + end + return colors + end + + heirline.load_colors(setup_colors()) + heirline.setup(opts) + + local augroup = vim.api.nvim_create_augroup("Heirline", { clear = true }) + vim.api.nvim_create_autocmd("User", { + pattern = "AstroColorScheme", + group = augroup, + desc = "Refresh heirline colors", + callback = function() require("heirline.utils").on_colorscheme(setup_colors()) end, + }) +end diff --git a/vim/config/nvim/lua/plugins/configs/lspconfig.lua b/vim/config/nvim/lua/plugins/configs/lspconfig.lua new file mode 100644 index 0000000..e9583bd --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/lspconfig.lua @@ -0,0 +1,56 @@ +return function(_, _) + local lsp = require "astronvim.utils.lsp" + local utils = require "astronvim.utils" + local get_icon = utils.get_icon + local signs = { + { name = "DiagnosticSignError", text = get_icon "DiagnosticError", texthl = "DiagnosticSignError" }, + { name = "DiagnosticSignWarn", text = get_icon "DiagnosticWarn", texthl = "DiagnosticSignWarn" }, + { name = "DiagnosticSignHint", text = get_icon "DiagnosticHint", texthl = "DiagnosticSignHint" }, + { name = "DiagnosticSignInfo", text = get_icon "DiagnosticInfo", texthl = "DiagnosticSignInfo" }, + { name = "DapStopped", text = get_icon "DapStopped", texthl = "DiagnosticWarn" }, + { name = "DapBreakpoint", text = get_icon "DapBreakpoint", texthl = "DiagnosticInfo" }, + { name = "DapBreakpointRejected", text = get_icon "DapBreakpointRejected", texthl = "DiagnosticError" }, + { name = "DapBreakpointCondition", text = get_icon "DapBreakpointCondition", texthl = "DiagnosticInfo" }, + { name = "DapLogPoint", text = get_icon "DapLogPoint", texthl = "DiagnosticInfo" }, + } + + for _, sign in ipairs(signs) do + vim.fn.sign_define(sign.name, sign) + end + lsp.setup_diagnostics(signs) + + local orig_handler = vim.lsp.handlers["$/progress"] + vim.lsp.handlers["$/progress"] = function(_, msg, info) + local progress, id = astronvim.lsp.progress, ("%s.%s"):format(info.client_id, msg.token) + progress[id] = progress[id] and utils.extend_tbl(progress[id], msg.value) or msg.value + if progress[id].kind == "end" then + vim.defer_fn(function() + progress[id] = nil + utils.event "LspProgress" + end, 100) + end + utils.event "LspProgress" + orig_handler(_, msg, info) + end + + if vim.g.lsp_handlers_enabled then + vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, { border = "rounded", silent = true }) + vim.lsp.handlers["textDocument/signatureHelp"] = + vim.lsp.with(vim.lsp.handlers.signature_help, { border = "rounded", silent = true }) + end + local setup_servers = function() + vim.tbl_map(require("astronvim.utils.lsp").setup, astronvim.user_opts "lsp.servers") + vim.api.nvim_exec_autocmds("FileType", {}) + require("astronvim.utils").event "LspSetup" + end + if require("astronvim.utils").is_available "mason-lspconfig.nvim" then + vim.api.nvim_create_autocmd("User", { + desc = "set up LSP servers after mason-lspconfig", + pattern = "AstroMasonLspSetup", + once = true, + callback = setup_servers, + }) + else + setup_servers() + end +end diff --git a/vim/config/nvim/lua/plugins/configs/lspkind.lua b/vim/config/nvim/lua/plugins/configs/lspkind.lua new file mode 100644 index 0000000..ae8b150 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/lspkind.lua @@ -0,0 +1 @@ +return function(_, opts) require("lspkind").init(opts) end diff --git a/vim/config/nvim/lua/plugins/configs/luasnip.lua b/vim/config/nvim/lua/plugins/configs/luasnip.lua new file mode 100644 index 0000000..a78c576 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/luasnip.lua @@ -0,0 +1,4 @@ +return function(_, opts) + if opts then require("luasnip").config.setup(opts) end + vim.tbl_map(function(type) require("luasnip.loaders.from_" .. type).lazy_load() end, { "vscode", "snipmate", "lua" }) +end diff --git a/vim/config/nvim/lua/plugins/configs/mason-lspconfig.lua b/vim/config/nvim/lua/plugins/configs/mason-lspconfig.lua new file mode 100644 index 0000000..019874e --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/mason-lspconfig.lua @@ -0,0 +1,4 @@ +return function(_, opts) + require("mason-lspconfig").setup(opts) + require("astronvim.utils").event "MasonLspSetup" +end diff --git a/vim/config/nvim/lua/plugins/configs/mason-null-ls.lua b/vim/config/nvim/lua/plugins/configs/mason-null-ls.lua new file mode 100644 index 0000000..e693a3e --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/mason-null-ls.lua @@ -0,0 +1,5 @@ +-- TODO: REMOVE THIS UNNECESSARY FILE +return function(_, opts) + local mason_null_ls = require "mason-null-ls" + mason_null_ls.setup(opts) +end diff --git a/vim/config/nvim/lua/plugins/configs/mason-nvim-dap.lua b/vim/config/nvim/lua/plugins/configs/mason-nvim-dap.lua new file mode 100644 index 0000000..d2b6ed5 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/mason-nvim-dap.lua @@ -0,0 +1,5 @@ +-- TODO: remove unnecessary file in AstroNvim v4 +return function(_, opts) + local mason_nvim_dap = require "mason-nvim-dap" + mason_nvim_dap.setup(opts) +end diff --git a/vim/config/nvim/lua/plugins/configs/mason.lua b/vim/config/nvim/lua/plugins/configs/mason.lua new file mode 100644 index 0000000..21739d0 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/mason.lua @@ -0,0 +1,26 @@ +return function(_, opts) + require("mason").setup(opts) + + -- TODO: AstroNvim v4: change these auto command names to not conflict with core Mason commands + local cmd = vim.api.nvim_create_user_command + cmd("MasonUpdate", function(options) require("astronvim.utils.mason").update(options.fargs) end, { + nargs = "*", + desc = "Update Mason Package", + complete = function(arg_lead) + local _ = require "mason-core.functional" + return _.sort_by( + _.identity, + _.filter(_.starts_with(arg_lead), require("mason-registry").get_installed_package_names()) + ) + end, + }) + cmd( + "MasonUpdateAll", + function() require("astronvim.utils.mason").update_all() end, + { desc = "Update Mason Packages" } + ) + + for _, plugin in ipairs { "mason-lspconfig", "mason-null-ls", "mason-nvim-dap" } do + pcall(require, plugin) + end +end diff --git a/vim/config/nvim/lua/plugins/configs/notify.lua b/vim/config/nvim/lua/plugins/configs/notify.lua new file mode 100644 index 0000000..84bf6c2 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/notify.lua @@ -0,0 +1,5 @@ +return function(_, opts) + local notify = require "notify" + notify.setup(opts) + vim.notify = notify +end diff --git a/vim/config/nvim/lua/plugins/configs/nvim-autopairs.lua b/vim/config/nvim/lua/plugins/configs/nvim-autopairs.lua new file mode 100644 index 0000000..823f017 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/nvim-autopairs.lua @@ -0,0 +1,10 @@ +return function(_, opts) + local npairs = require "nvim-autopairs" + npairs.setup(opts) + + if not vim.g.autopairs_enabled then npairs.disable() end + local cmp_status_ok, cmp = pcall(require, "cmp") + if cmp_status_ok then + cmp.event:on("confirm_done", require("nvim-autopairs.completion.cmp").on_confirm_done { tex = false }) + end +end diff --git a/vim/config/nvim/lua/plugins/configs/nvim-dap-ui.lua b/vim/config/nvim/lua/plugins/configs/nvim-dap-ui.lua new file mode 100644 index 0000000..314387a --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/nvim-dap-ui.lua @@ -0,0 +1,7 @@ +return function(_, opts) + local dap, dapui = require "dap", require "dapui" + dap.listeners.after.event_initialized["dapui_config"] = function() dapui.open() end + dap.listeners.before.event_terminated["dapui_config"] = function() dapui.close() end + dap.listeners.before.event_exited["dapui_config"] = function() dapui.close() end + dapui.setup(opts) +end diff --git a/vim/config/nvim/lua/plugins/configs/nvim-treesitter.lua b/vim/config/nvim/lua/plugins/configs/nvim-treesitter.lua new file mode 100644 index 0000000..7ec9c74 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/nvim-treesitter.lua @@ -0,0 +1 @@ +return function(_, opts) require("nvim-treesitter.configs").setup(opts) end diff --git a/vim/config/nvim/lua/plugins/configs/nvim-web-devicons.lua b/vim/config/nvim/lua/plugins/configs/nvim-web-devicons.lua new file mode 100644 index 0000000..bc6cad3 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/nvim-web-devicons.lua @@ -0,0 +1,5 @@ +-- TODO: remove unnecessary file in AstroNvim v4 +return function(_, opts) + require("nvim-web-devicons").set_default_icon(require("astronvim.utils").get_icon "DefaultFile", "#6d8086", "66") + require("nvim-web-devicons").set_icon(opts) +end diff --git a/vim/config/nvim/lua/plugins/configs/telescope.lua b/vim/config/nvim/lua/plugins/configs/telescope.lua new file mode 100644 index 0000000..18d3e86 --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/telescope.lua @@ -0,0 +1,9 @@ +return function(_, opts) + local telescope = require "telescope" + telescope.setup(opts) + local utils = require "astronvim.utils" + local conditional_func = utils.conditional_func + conditional_func(telescope.load_extension, pcall(require, "notify"), "notify") + conditional_func(telescope.load_extension, pcall(require, "aerial"), "aerial") + conditional_func(telescope.load_extension, utils.is_available "telescope-fzf-native.nvim", "fzf") +end diff --git a/vim/config/nvim/lua/plugins/configs/which-key.lua b/vim/config/nvim/lua/plugins/configs/which-key.lua new file mode 100644 index 0000000..1c4da7a --- /dev/null +++ b/vim/config/nvim/lua/plugins/configs/which-key.lua @@ -0,0 +1,4 @@ +return function(_, opts) + require("which-key").setup(opts) + require("astronvim.utils").which_key_register() +end diff --git a/vim/config/nvim/lua/plugins/core.lua b/vim/config/nvim/lua/plugins/core.lua new file mode 100644 index 0000000..8f808c9 --- /dev/null +++ b/vim/config/nvim/lua/plugins/core.lua @@ -0,0 +1,121 @@ +return { + "nvim-lua/plenary.nvim", + "echasnovski/mini.bufremove", + { "AstroNvim/astrotheme", opts = { plugins = { ["dashboard-nvim"] = true } } }, + { "max397574/better-escape.nvim", event = "InsertCharPre", opts = { timeout = 300 } }, + { "NMAC427/guess-indent.nvim", event = "User AstroFile", config = require "plugins.configs.guess-indent" }, + { -- TODO: REMOVE neovim-session-manager with AstroNvim v4 + "Shatur/neovim-session-manager", + event = "BufWritePost", + cmd = "SessionManager", + enabled = vim.g.resession_enabled ~= true, + }, + { + "stevearc/resession.nvim", + enabled = vim.g.resession_enabled == true, + opts = { + buf_filter = function(bufnr) return require("astronvim.utils.buffer").is_restorable(bufnr) end, + tab_buf_filter = function(tabpage, bufnr) return vim.tbl_contains(vim.t[tabpage].bufs, bufnr) end, + extensions = { astronvim = {} }, + }, + }, + { + "s1n7ax/nvim-window-picker", + name = "window-picker", + opts = { picker_config = { statusline_winbar_picker = { use_winbar = "smart" } } }, + }, + { + "mrjones2014/smart-splits.nvim", + opts = { ignored_filetypes = { "nofile", "quickfix", "qf", "prompt" }, ignored_buftypes = { "nofile" } }, + }, + { + "windwp/nvim-autopairs", + event = "InsertEnter", + opts = { + check_ts = true, + ts_config = { java = false }, + fast_wrap = { + map = "", + chars = { "{", "[", "(", '"', "'" }, + pattern = string.gsub([[ [%'%"%)%>%]%)%}%,] ]], "%s+", ""), + offset = 0, + end_key = "$", + keys = "qwertyuiopzxcvbnmasdfghjkl", + check_comma = true, + highlight = "PmenuSel", + highlight_grey = "LineNr", + }, + }, + config = require "plugins.configs.nvim-autopairs", + }, + { + "folke/which-key.nvim", + event = "VeryLazy", + opts = { + icons = { group = vim.g.icons_enabled and "" or "+", separator = "" }, + disable = { filetypes = { "TelescopePrompt" } }, + }, + config = require "plugins.configs.which-key", + }, + { + "kevinhwang91/nvim-ufo", + event = { "User AstroFile", "InsertEnter" }, + dependencies = { "kevinhwang91/promise-async" }, + opts = { + preview = { + mappings = { + scrollB = "", + scrollF = "", + scrollU = "", + scrollD = "", + }, + }, + provider_selector = function(_, filetype, buftype) + local function handleFallbackException(bufnr, err, providerName) + if type(err) == "string" and err:match "UfoFallbackException" then + return require("ufo").getFolds(bufnr, providerName) + else + return require("promise").reject(err) + end + end + + return (filetype == "" or buftype == "nofile") and "indent" -- only use indent until a file is opened + or function(bufnr) + return require("ufo") + .getFolds(bufnr, "lsp") + :catch(function(err) return handleFallbackException(bufnr, err, "treesitter") end) + :catch(function(err) return handleFallbackException(bufnr, err, "indent") end) + end + end, + }, + }, + { + "numToStr/Comment.nvim", + keys = { + { "gc", mode = { "n", "v" }, desc = "Comment toggle linewise" }, + { "gb", mode = { "n", "v" }, desc = "Comment toggle blockwise" }, + }, + opts = function() + local commentstring_avail, commentstring = pcall(require, "ts_context_commentstring.integrations.comment_nvim") + return commentstring_avail and commentstring and { pre_hook = commentstring.create_pre_hook() } or {} + end, + }, + { + "akinsho/toggleterm.nvim", + cmd = { "ToggleTerm", "TermExec" }, + opts = { + size = 10, + on_create = function() + vim.opt.foldcolumn = "0" + vim.opt.signcolumn = "no" + end, + open_mapping = [[]], + shading_factor = 2, + direction = "float", + float_opts = { + border = "curved", + highlights = { border = "Normal", background = "Normal" }, + }, + }, + }, +} diff --git a/vim/config/nvim/lua/plugins/dap.lua b/vim/config/nvim/lua/plugins/dap.lua new file mode 100644 index 0000000..c532096 --- /dev/null +++ b/vim/config/nvim/lua/plugins/dap.lua @@ -0,0 +1,23 @@ +return { + "mfussenegger/nvim-dap", + enabled = vim.fn.has "win32" == 0, + dependencies = { + { + "jay-babu/mason-nvim-dap.nvim", + dependencies = { "nvim-dap" }, + cmd = { "DapInstall", "DapUninstall" }, + opts = { handlers = {} }, + }, + { + "rcarriga/nvim-dap-ui", + opts = { floating = { border = "rounded" } }, + config = require "plugins.configs.nvim-dap-ui", + }, + { + "rcarriga/cmp-dap", + dependencies = { "nvim-cmp" }, + config = require "plugins.configs.cmp-dap", + }, + }, + event = "User AstroFile", +} diff --git a/vim/config/nvim/lua/plugins/git.lua b/vim/config/nvim/lua/plugins/git.lua new file mode 100644 index 0000000..48e099c --- /dev/null +++ b/vim/config/nvim/lua/plugins/git.lua @@ -0,0 +1,17 @@ +local get_icon = require("astronvim.utils").get_icon +return { + "lewis6991/gitsigns.nvim", + enabled = vim.fn.executable "git" == 1, + event = "User AstroGitFile", + opts = { + signs = { + add = { text = get_icon "GitSign" }, + change = { text = get_icon "GitSign" }, + delete = { text = get_icon "GitSign" }, + topdelete = { text = get_icon "GitSign" }, + changedelete = { text = get_icon "GitSign" }, + untracked = { text = get_icon "GitSign" }, + }, + worktrees = vim.g.git_worktrees, + }, +} diff --git a/vim/config/nvim/lua/plugins/heirline.lua b/vim/config/nvim/lua/plugins/heirline.lua new file mode 100644 index 0000000..74550fa --- /dev/null +++ b/vim/config/nvim/lua/plugins/heirline.lua @@ -0,0 +1,87 @@ +return { + "rebelot/heirline.nvim", + event = "BufEnter", + opts = function() + local status = require "astronvim.utils.status" + return { + opts = { + disable_winbar_cb = function(args) + return not require("astronvim.utils.buffer").is_valid(args.buf) + or status.condition.buffer_matches({ + buftype = { "terminal", "prompt", "nofile", "help", "quickfix" }, + filetype = { "NvimTree", "neo%-tree", "dashboard", "Outline", "aerial" }, + }, args.buf) + end, + }, + statusline = { -- statusline + hl = { fg = "fg", bg = "bg" }, + status.component.mode(), + status.component.git_branch(), + status.component.file_info { filetype = {}, filename = false, file_modified = false }, + status.component.git_diff(), + status.component.diagnostics(), + status.component.fill(), + status.component.cmd_info(), + status.component.fill(), + status.component.lsp(), + status.component.treesitter(), + status.component.nav(), + status.component.mode { surround = { separator = "right" } }, + }, + winbar = { -- winbar + init = function(self) self.bufnr = vim.api.nvim_get_current_buf() end, + fallthrough = false, + { + condition = function() return not status.condition.is_active() end, + status.component.separated_path(), + status.component.file_info { + file_icon = { hl = status.hl.file_icon "winbar", padding = { left = 0 } }, + file_modified = false, + file_read_only = false, + hl = status.hl.get_attributes("winbarnc", true), + surround = false, + update = "BufEnter", + }, + }, + status.component.breadcrumbs { hl = status.hl.get_attributes("winbar", true) }, + }, + tabline = { -- bufferline + { -- file tree padding + condition = function(self) + self.winid = vim.api.nvim_tabpage_list_wins(0)[1] + return status.condition.buffer_matches( + { filetype = { "aerial", "dapui_.", "dap-repl", "neo%-tree", "NvimTree", "edgy" } }, + vim.api.nvim_win_get_buf(self.winid) + ) + end, + provider = function(self) return string.rep(" ", vim.api.nvim_win_get_width(self.winid) + 1) end, + hl = { bg = "tabline_bg" }, + }, + status.heirline.make_buflist(status.component.tabline_file_info()), -- component for each buffer tab + status.component.fill { hl = { bg = "tabline_bg" } }, -- fill the rest of the tabline with background color + { -- tab list + condition = function() return #vim.api.nvim_list_tabpages() >= 2 end, -- only show tabs if there are more than one + status.heirline.make_tablist { -- component for each tab + provider = status.provider.tabnr(), + hl = function(self) return status.hl.get_attributes(status.heirline.tab_type(self, "tab"), true) end, + }, + { -- close button for current tab + provider = status.provider.close_button { kind = "TabClose", padding = { left = 1, right = 1 } }, + hl = status.hl.get_attributes("tab_close", true), + on_click = { + callback = function() require("astronvim.utils.buffer").close_tab() end, + name = "heirline_tabline_close_tab_callback", + }, + }, + }, + }, + statuscolumn = vim.fn.has "nvim-0.9" == 1 and { + status.component.foldcolumn(), + status.component.fill(), + status.component.numbercolumn(), + status.component.signcolumn(), + } or nil, + } + end, + config = require "plugins.configs.heirline", +} diff --git a/vim/config/nvim/lua/plugins/lsp.lua b/vim/config/nvim/lua/plugins/lsp.lua new file mode 100644 index 0000000..e117709 --- /dev/null +++ b/vim/config/nvim/lua/plugins/lsp.lua @@ -0,0 +1,97 @@ +return { + "b0o/SchemaStore.nvim", + { + "folke/neodev.nvim", + opts = { + override = function(root_dir, library) + for _, astronvim_config in ipairs(astronvim.supported_configs) do + if root_dir:match(astronvim_config) then + library.plugins = true + break + end + end + vim.b.neodev_enabled = library.enabled + end, + }, + }, + { + "neovim/nvim-lspconfig", + dependencies = { + { + "folke/neoconf.nvim", + opts = function() + local global_settings, file_found + local _, depth = vim.fn.stdpath("config"):gsub("/", "") + for _, dir in ipairs(astronvim.supported_configs) do + dir = dir .. "/lua/user" + if vim.fn.isdirectory(dir) == 1 then + local path = dir .. "/neoconf.json" + if vim.fn.filereadable(path) == 1 then + file_found = true + global_settings = path + elseif not file_found then + global_settings = path + end + end + end + return { global_settings = global_settings and string.rep("../", depth):sub(1, -2) .. global_settings } + end, + }, + { + "williamboman/mason-lspconfig.nvim", + cmd = { "LspInstall", "LspUninstall" }, + opts = function(_, opts) + if not opts.handlers then opts.handlers = {} end + opts.handlers[1] = function(server) require("astronvim.utils.lsp").setup(server) end + end, + config = require "plugins.configs.mason-lspconfig", + }, + }, + cmd = function(_, cmds) -- HACK: lazy load lspconfig on `:Neoconf` if neoconf is available + if require("astronvim.utils").is_available "neoconf.nvim" then table.insert(cmds, "Neoconf") end + end, + event = "User AstroFile", + config = require "plugins.configs.lspconfig", + }, + { + "jose-elias-alvarez/null-ls.nvim", + dependencies = { + { + "jay-babu/mason-null-ls.nvim", + cmd = { "NullLsInstall", "NullLsUninstall" }, + opts = { handlers = {} }, + }, + }, + event = "User AstroFile", + opts = function() return { on_attach = require("astronvim.utils.lsp").on_attach } end, + }, + { + "stevearc/aerial.nvim", + event = "User AstroFile", + opts = { + attach_mode = "global", + backends = { "lsp", "treesitter", "markdown", "man" }, + disable_max_lines = vim.g.max_file.lines, + disable_max_size = vim.g.max_file.size, + layout = { min_width = 28 }, + show_guides = true, + filter_kind = false, + guides = { + mid_item = "├ ", + last_item = "└ ", + nested_top = "│ ", + whitespace = " ", + }, + keymaps = { + ["[y"] = "actions.prev", + ["]y"] = "actions.next", + ["[Y"] = "actions.prev_up", + ["]Y"] = "actions.next_up", + ["{"] = false, + ["}"] = false, + ["[["] = false, + ["]]"] = false, + }, + }, + }, +} diff --git a/vim/config/nvim/lua/plugins/mason.lua b/vim/config/nvim/lua/plugins/mason.lua new file mode 100644 index 0000000..ca9f595 --- /dev/null +++ b/vim/config/nvim/lua/plugins/mason.lua @@ -0,0 +1,25 @@ +return { + { + "williamboman/mason.nvim", + cmd = { + "Mason", + "MasonInstall", + "MasonUninstall", + "MasonUninstallAll", + "MasonLog", + "MasonUpdate", -- AstroNvim extension here as well + "MasonUpdateAll", -- AstroNvim specific + }, + opts = { + ui = { + icons = { + package_installed = "✓", + package_uninstalled = "✗", + package_pending = "⟳", + }, + }, + }, + build = ":MasonUpdate", + config = require "plugins.configs.mason", + }, +} diff --git a/vim/config/nvim/lua/plugins/neo-tree.lua b/vim/config/nvim/lua/plugins/neo-tree.lua new file mode 100644 index 0000000..3f204b7 --- /dev/null +++ b/vim/config/nvim/lua/plugins/neo-tree.lua @@ -0,0 +1,143 @@ +return { + "nvim-neo-tree/neo-tree.nvim", + branch = "main", -- HACK: force neo-tree to checkout `main` for initial v3 migration since default branch has changed + dependencies = { "MunifTanjim/nui.nvim" }, + cmd = "Neotree", + init = function() vim.g.neo_tree_remove_legacy_commands = true end, + opts = function() + local utils = require "astronvim.utils" + local get_icon = utils.get_icon + return { + auto_clean_after_session_restore = true, + close_if_last_window = true, + sources = { "filesystem", "buffers", "git_status" }, + source_selector = { + winbar = true, + content_layout = "center", + sources = { + { source = "filesystem", display_name = get_icon("FolderClosed", 1, true) .. "File" }, + { source = "buffers", display_name = get_icon("DefaultFile", 1, true) .. "Bufs" }, + { source = "git_status", display_name = get_icon("Git", 1, true) .. "Git" }, + { source = "diagnostics", display_name = get_icon("Diagnostic", 1, true) .. "Diagnostic" }, + }, + }, + default_component_configs = { + indent = { padding = 0 }, + icon = { + folder_closed = get_icon "FolderClosed", + folder_open = get_icon "FolderOpen", + folder_empty = get_icon "FolderEmpty", + folder_empty_open = get_icon "FolderEmpty", + default = get_icon "DefaultFile", + }, + modified = { symbol = get_icon "FileModified" }, + git_status = { + symbols = { + added = get_icon "GitAdd", + deleted = get_icon "GitDelete", + modified = get_icon "GitChange", + renamed = get_icon "GitRenamed", + untracked = get_icon "GitUntracked", + ignored = get_icon "GitIgnored", + unstaged = get_icon "GitUnstaged", + staged = get_icon "GitStaged", + conflict = get_icon "GitConflict", + }, + }, + }, + commands = { + system_open = function(state) + -- TODO: just use vim.ui.open when dropping support for Neovim <0.10 + (vim.ui.open or require("astronvim.utils").system_open)(state.tree:get_node():get_id()) + end, + parent_or_close = function(state) + local node = state.tree:get_node() + if (node.type == "directory" or node:has_children()) and node:is_expanded() then + state.commands.toggle_node(state) + else + require("neo-tree.ui.renderer").focus_node(state, node:get_parent_id()) + end + end, + child_or_open = function(state) + local node = state.tree:get_node() + if node.type == "directory" or node:has_children() then + if not node:is_expanded() then -- if unexpanded, expand + state.commands.toggle_node(state) + else -- if expanded and has children, seleect the next child + require("neo-tree.ui.renderer").focus_node(state, node:get_child_ids()[1]) + end + else -- if not a directory just open it + state.commands.open(state) + end + end, + copy_selector = function(state) + local node = state.tree:get_node() + local filepath = node:get_id() + local filename = node.name + local modify = vim.fn.fnamemodify + + local results = { + e = { val = modify(filename, ":e"), msg = "Extension only" }, + f = { val = filename, msg = "Filename" }, + F = { val = modify(filename, ":r"), msg = "Filename w/o extension" }, + h = { val = modify(filepath, ":~"), msg = "Path relative to Home" }, + p = { val = modify(filepath, ":."), msg = "Path relative to CWD" }, + P = { val = filepath, msg = "Absolute path" }, + } + + local messages = { + { "\nChoose to copy to clipboard:\n", "Normal" }, + } + for i, result in pairs(results) do + if result.val and result.val ~= "" then + vim.list_extend(messages, { + { ("%s."):format(i), "Identifier" }, + { (" %s: "):format(result.msg) }, + { result.val, "String" }, + { "\n" }, + }) + end + end + vim.api.nvim_echo(messages, false, {}) + local result = results[vim.fn.getcharstr()] + if result and result.val and result.val ~= "" then + utils.notify(("Copied: `%s`"):format(result.val)) + vim.fn.setreg("+", result.val) + end + end, + find_in_dir = function(state) + local node = state.tree:get_node() + local path = node:get_id() + require("telescope.builtin").find_files { + cwd = node.type == "directory" and path or vim.fn.fnamemodify(path, ":h"), + } + end, + }, + window = { + width = 30, + mappings = { + [""] = false, -- disable space until we figure out which-key disabling + ["[b"] = "prev_source", + ["]b"] = "next_source", + F = utils.is_available "telescope.nvim" and "find_in_dir" or nil, + O = "system_open", + Y = "copy_selector", + h = "parent_or_close", + l = "child_or_open", + o = "open", + }, + }, + filesystem = { + follow_current_file = { enabled = true }, + hijack_netrw_behavior = "open_current", + use_libuv_file_watcher = true, + }, + event_handlers = { + { + event = "neo_tree_buffer_enter", + handler = function(_) vim.opt_local.signcolumn = "auto" end, + }, + }, + } + end, +} diff --git a/vim/config/nvim/lua/plugins/telescope.lua b/vim/config/nvim/lua/plugins/telescope.lua new file mode 100644 index 0000000..280db06 --- /dev/null +++ b/vim/config/nvim/lua/plugins/telescope.lua @@ -0,0 +1,37 @@ +return { + "nvim-telescope/telescope.nvim", + dependencies = { + { "nvim-telescope/telescope-fzf-native.nvim", enabled = vim.fn.executable "make" == 1, build = "make" }, + }, + cmd = "Telescope", + opts = function() + local actions = require "telescope.actions" + local get_icon = require("astronvim.utils").get_icon + return { + defaults = { + git_worktrees = vim.g.git_worktrees, + prompt_prefix = get_icon("Selected", 1), + selection_caret = get_icon("Selected", 1), + path_display = { "truncate" }, + sorting_strategy = "ascending", + layout_config = { + horizontal = { prompt_position = "top", preview_width = 0.55 }, + vertical = { mirror = false }, + width = 0.87, + height = 0.80, + preview_cutoff = 120, + }, + mappings = { + i = { + [""] = actions.cycle_history_next, + [""] = actions.cycle_history_prev, + [""] = actions.move_selection_next, + [""] = actions.move_selection_previous, + }, + n = { q = actions.close }, + }, + }, + } + end, + config = require "plugins.configs.telescope", +} diff --git a/vim/config/nvim/lua/plugins/treesitter.lua b/vim/config/nvim/lua/plugins/treesitter.lua new file mode 100644 index 0000000..099afd3 --- /dev/null +++ b/vim/config/nvim/lua/plugins/treesitter.lua @@ -0,0 +1,95 @@ +return { + "nvim-treesitter/nvim-treesitter", + dependencies = { + "JoosepAlviste/nvim-ts-context-commentstring", + "nvim-treesitter/nvim-treesitter-textobjects", + "windwp/nvim-ts-autotag", + }, + event = "User AstroFile", + cmd = { + "TSBufDisable", + "TSBufEnable", + "TSBufToggle", + "TSDisable", + "TSEnable", + "TSToggle", + "TSInstall", + "TSInstallInfo", + "TSInstallSync", + "TSModuleInfo", + "TSUninstall", + "TSUpdate", + "TSUpdateSync", + }, + build = ":TSUpdate", + opts = function() + return { + autotag = { enable = true }, + context_commentstring = { enable = true, enable_autocmd = false }, + highlight = { + enable = true, + disable = function(_, bufnr) return vim.b[bufnr].large_buf end, + }, + incremental_selection = { enable = true }, + indent = { enable = true }, + textobjects = { + select = { + enable = true, + lookahead = true, + keymaps = { + ["ak"] = { query = "@block.outer", desc = "around block" }, + ["ik"] = { query = "@block.inner", desc = "inside block" }, + ["ac"] = { query = "@class.outer", desc = "around class" }, + ["ic"] = { query = "@class.inner", desc = "inside class" }, + ["a?"] = { query = "@conditional.outer", desc = "around conditional" }, + ["i?"] = { query = "@conditional.inner", desc = "inside conditional" }, + ["af"] = { query = "@function.outer", desc = "around function " }, + ["if"] = { query = "@function.inner", desc = "inside function " }, + ["al"] = { query = "@loop.outer", desc = "around loop" }, + ["il"] = { query = "@loop.inner", desc = "inside loop" }, + ["aa"] = { query = "@parameter.outer", desc = "around argument" }, + ["ia"] = { query = "@parameter.inner", desc = "inside argument" }, + }, + }, + move = { + enable = true, + set_jumps = true, + goto_next_start = { + ["]k"] = { query = "@block.outer", desc = "Next block start" }, + ["]f"] = { query = "@function.outer", desc = "Next function start" }, + ["]a"] = { query = "@parameter.inner", desc = "Next argument start" }, + }, + goto_next_end = { + ["]K"] = { query = "@block.outer", desc = "Next block end" }, + ["]F"] = { query = "@function.outer", desc = "Next function end" }, + ["]A"] = { query = "@parameter.inner", desc = "Next argument end" }, + }, + goto_previous_start = { + ["[k"] = { query = "@block.outer", desc = "Previous block start" }, + ["[f"] = { query = "@function.outer", desc = "Previous function start" }, + ["[a"] = { query = "@parameter.inner", desc = "Previous argument start" }, + }, + goto_previous_end = { + ["[K"] = { query = "@block.outer", desc = "Previous block end" }, + ["[F"] = { query = "@function.outer", desc = "Previous function end" }, + ["[A"] = { query = "@parameter.inner", desc = "Previous argument end" }, + }, + }, + swap = { + enable = true, + swap_next = { + [">K"] = { query = "@block.outer", desc = "Swap next block" }, + [">F"] = { query = "@function.outer", desc = "Swap next function" }, + [">A"] = { query = "@parameter.inner", desc = "Swap next argument" }, + }, + swap_previous = { + [""] = ":bnext", + [""] = ":bprev", + [""] = "v", -- select with SHIFT + [""] = "v", -- select with SHIFT + -- [""] = "pi" -- paste with Ctrl + v + }, + t = { + [""] = "v", -- select with SHIFT + [""] = "v",-- select with SHIFT + -- [""] = "pi", -- paste with Ctrl + v + -- [""] = "zi", -- undo with Ctrl + z + }, + v = { + [""] = "", -- select with SHIFT + [""] = "", -- select with SHIFT + -- [""] = "yi", -- copy with Ctrl + x + -- [""] = "di", -- cut with Ctrl + x + } + }, + -- g = { + -- cmp_enabled = true, + -- diagnostics_mode = 3, + -- wrap = true, + -- }, + options = { + opt = { + relativenumber = false, -- sets vim.opt.relativenumber + wrap = true, + }, + g = { + mapleader = " ", -- sets vim.g.mapleader + wrap = true, + }, + }, + opt ={ + diagnostic = { + config = { virtual_lines = false } + } + }, + polish = function() + vim.cmd[[diagnostic.disable()]] + -- vim.cmd [[TransparentDisable]] + -- vim.cmd [[let g:vim_monokai_tasty_italic = 1]] + -- vim.cmd [[:colorscheme vim-monokai-tasty]] + -- Sonokai theme + -- vim.cmd[[let g:sonokai_better_performance = 1]] + -- vim.cmd[[let g:sonokai_style = 'atlantis']] + -- vim.cmd[[let g:sonokai_style = 'andromeda']] + -- vim.cmd[[let g:sonokai_style = 'default']] + -- vim.cmd[[colorscheme sonokai]] + end, + dap = { + adapters = { + php = { + type = 'executable', + command = 'node', + args = {os.getenv('HOME') .. '/.config/nvim/vscode-php-debug/out/phpDebug.js'}, + } + }, + }, + lsp = { + formatting = { + format_on_save = false, + }, + config = { + intelephense = { + settings = { + intelephense = { + environment = { + includePaths = { + "core/includes" + } + }, + files = { + associations = { + "*.inc", + "*.theme", + "*.install", + "*.module", + "*.profile", + "*.php", + "*.phtml" + } + }, + diagnostics = { + enable = false, + run = false, + undefinedFunctions = false, + undefinedMethod = false, + undefinedClassConstants = false, + undefinedConstants = false, + undefinedProperties = false, + undefinedVariables = false + } + }, + }, + }, + } + } +} diff --git a/vim/config/nvim/lua/user/polish.lua b/vim/config/nvim/lua/user/polish.lua new file mode 100644 index 0000000..02d467d --- /dev/null +++ b/vim/config/nvim/lua/user/polish.lua @@ -0,0 +1,7 @@ +return function(local_vim) +-- Themery block +-- This block will be replaced by Themery. +vim.cmd("colorscheme night-owl") +-- end themery block + return local_vim +end