diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..6de8a8a --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +source_url "https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc" "sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0=" + +use devenv \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f46de9e --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/.tests + +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml + diff --git a/.neoconf.json b/.neoconf.json new file mode 100644 index 0000000..f0c23f8 --- /dev/null +++ b/.neoconf.json @@ -0,0 +1,15 @@ +{ + "neodev": { + "library": { + "plugins": [ + "plenary.nvim" + ] + } + }, + "lspconfig": { + "lua_ls": { + "Lua.runtime.version": "LuaJIT", + "Lua.workspace.checkThirdParty": false + } + } +} diff --git a/README.md b/README.md index 1e74296..fde347e 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,17 @@ Plugin for Neovim to enhance the development experience of Laravel projects Quick executing of artisan commands, list and navigate to routes. Information about the routes. Robust API to allow you to run any command in the way that you need. -# Preview +## Preview ![](./images/telescope_commands.png) ![](./images/telescope_routes.png) ![](./images/route_info.png) -# Requirements +## Requirements Treesitter, LSP Server (I use and recommend [phpactor](https://github.com/phpactor/phpactor)) +`fd` to look for files as migrations when are created +`rg` ripgrep to look usage of views -# Installation +## Installation Lazy ```lua return { @@ -27,26 +29,15 @@ return { { "la", ":Laravel artisan" }, { "lr", ":Laravel routes" }, { "lm", ":Laravel related" }, - { - "lt", - function() - require("laravel.tinker").send_to_tinker() - end, - mode = "v", - desc = "Laravel Application Routes", - }, }, event = { "VeryLazy" }, - config = function() - require("laravel").setup() - require("telescope").load_extension "laravel" - end, + config = true, } ``` - Dotenv is use to read environment variables from the `.env` file -For nicer notifications use `rcarriga/nvim-notify` +For nicer notifications I recommend `rcarriga/nvim-notify` + My lazy configuration for notify is ```lua return { @@ -61,121 +52,58 @@ return { } ``` -Default configuration -```lua -{ - split = { - relative = "editor", - position = "right", - size = "30%", - enter = true, - }, - bind_telescope = true, - lsp_server = "phpactor", - register_user_commands = true, - route_info = { - enable = true, - position = "right", - }, - default_runner = "buffer", - commands_runner = { - ["dump-server"] = "persist", - ["queue:listen"] = "persist", - ["serve"] = "persist", - ["websockets"] = "persist", - ["queue:restart"] = "watch", - ["tinker"] = "terminal", - ["db"] = "terminal", - }, - environment = { - resolver = require "laravel.environment.resolver"(true, true, nil), - environments = { - ["local"] = require("laravel.environment.native").setup(), - ["sail"] = require("laravel.environment.sail").setup(), - ["docker-compose"] = require("laravel.environment.docker_compose").setup(), - }, - }, - resources = { - ["make:cast"] = "app/Casts", - ["make:channel"] = "app/Broadcasting", - ["make:command"] = "app/Console/Commands", - ["make:component"] = "app/View/Components", - ["make:controller"] = "app/Http/Controllers", - ["make:event"] = "app/Events", - ["make:exception"] = "app/Exceptions", - ["make:factory"] = function(name) - return string.format("database/factories/%sFactory.php", name), nil - end, - ["make:job"] = "app/Jobs", - ["make:listener"] = "app/Listeners", - ["make:mail"] = "app/Mail", - ["make:middleware"] = "app/Http/Middleware", - ["make:migration"] = function(name) - local result = require("laravel.runners").sync { "fd", name .. ".php" } - if result.exit_code == 1 then - return "", result.error - end - - return result.out, nil - end, - ["make:model"] = "app/Models", - ["make:notification"] = "app/Notifications", - ["make:observer"] = "app/Observers", - ["make:policy"] = "app/Policies", - ["make:provider"] = "app/Providers", - ["make:request"] = "app/Http/Requests", - ["make:resource"] = "app/Http/Resources", - ["make:rule"] = "app/Rules", - ["make:scope"] = "app/Models/Scopes", - ["make:seeder"] = "database/seeders", - ["make:test"] = "tests/Feature", - } -} -``` +## Config -## Environments -There are many ways to run your laravel application, could be natively in you computer, using docker-compose, using sail, or others. -Even using docker-compose you could have differences from project to project. -In order to support this there is an `environment` configuration. Here you can set 2 properties. -`resolver` takes care of determining which environment to use. -The resolver will return the environment to use. The resolver will first look for an environment variable `NVIM_LARAVEL_ENV` which should correspond to configured environment name. -If is not set it will be ignored, if it is set but could not be found an error will be thrown. -Next is the auto auto-discovery this will look for docker-compose file and Sail file in the vendor directory. If they are present Sail will be used. -If only docker-compose.yml file is present docker-compose environment will be used. -If both checks fail will check if the php is executable and then local environment will be used. -The third part is try to use the provided default. -You can configure the resolver by passing parameters -```lua ----@param env_check boolean ----@param auto_discovery boolean ----@param default string|nil -return function(env_check, auto_discovery, default) -``` -Resolver can be configured to ignore environment variable, ignore auto-discovery and just use the default -```lua - resolver = require "laravel.environment.resolver"(false, false, "sail"), -``` +Default [config]("./lua/laravel/config/default.lua"), this can be set on the `setup` function +In this config there are several secitions, like `lsp_server` which is use to interact with neovim lsp client to look for classes by the name. +Currently support `phpactor` and `intelephense` as far as I know there are no other php lsp sever. + +By default the plugin register several commands like `Artisan` `Composer` `Npm` `Sail` `DockerCompose` `Yarn` and `Bun` if you don't want them you can use +`register_user_commands` and set it as `false`. -The environment needs to return a function that returns a list of executables in format of a table. This will be used when running the `run` method on the application `require('laravel.application').run('', {"args"}, {options})` -Let say you want to modify the Sail command, you can do it by calling the setup method on the environment Sail with options cmd -```lua - ["sail"] = require("laravel.environment.sail").setup({cmd = {"my-sail-binary"}}), -``` -You can set different commands for normal executables, or completely replace all the executables. -If you add your executable you can call it using -`require('laravel.application).run('executable', args, options)` -Changing the container name for docker compose +## Features +In adition to the selector for the commands, routes and api, you can use some extras features *Route Info* *Views Completion* *Routes Completion* +Route info can be seen in the above screenshots, this allows to see in the controller the route associated to it, and in case that is missing will +add a diagnostic error which indicate which method is missing. +![](./images/route_info.png) +This also will show error if a route is defined but the method is not defined +![](./images/missing_method.png) + +> Note: using lazy is likely that you will not see at first since the plugin will not load until you call one of the commands, after that it is just picked up + +The completions uses `none-ls` which was previusly know as `null-ls`. +Views and Routes completion works as providing the list of the respective in the php files for the function `view` and `route`. + +## UI +An important part of the plugin is how to show the results of the commands, for this I decide to use `nui` this allow to easily interact with split and popup +You can customize from the size and options. examples can be seen in the default [here](./lua/laravel/config/ui.lua) + +## Command Options +Of course not all the commands want to be run in the same way. you can specify for example which `ui` to use, if should `skip_args`. +Can also set `nui_opts` to define how the ui should display. +Also can define options this is usefull for example for `make:model` you may want to always use the flags `-mf` ```lua - require("laravel").setup({ - environment = { - environments = { - ["docker-compose"] = require("laravel.environment.docker_compose").setup({container_name = "testing"}), - } - } - }) + ["make:model"] = { options = { "-mf" } }, ``` +## Resources +A main part when creating resources like controllers, models, etc you most likely want to open it. Since the laravel commands does not return what was created +I base on the type and and provided name to look for the file here is the list that can also be customize to add in case you have custom or from a plugin that +you use [resources](./lua/laravel/config/resources.lua) + + + +# Environments +Running your laravel app has many forms, you can use something like *Laravel Herd* *Laravel Sail* *Docker Compose* or just the good `php artisan serve` this presents +a challange, a fundamental aspect you want to run the command where it should with sail, with in your docker or simple the php native executable. +In order to support this there is this [configuration](./lua/laravel/config/environments.lua) + +Since you may not want the same configuration for all you projects you can use the env variable `NVIM_LARAVEL_ENV` define it in your `.env` file in your project. +If you don't se it by default will use auto_discover this will go over each definition and test base on the conditions, if they are meant will use that. +In case no configuration matches will try to use the default one. + +For the docker compose one also you can use an env variable to define which container will be use to run the commands. ## Artisan To run Artisan commands you can use `:Artisan` which will autocomplete with the available @@ -199,120 +127,39 @@ You can run `shell` as tinker will open a new terminal `Laravel commands` shows the list of artisan commands and executes it. `Laravel routes` show the list of routes and goes to the implementation. `Laravel related` show the list of model related classes, includes observers, policy and relations and goes to the implementation. -`Laravel test` runs the application tests `Laravel test:watch` runs the application tests and keep monitoring the changes - -# Route Info -I want to have more information in the controller, I want to have the route information directly in the controller so I build route info, this will show the -![](./images/route_info.png) -This also will show error if a route is defined but the method is not defined -![](./images/missing_method.png) - -> Note: using lazy is likely that you will not see at first since the plugin will not load until you call one of the commands, after that it is just picked up +`Laravel history` each command is recorded in case you want to run the same again +`Laravel view-finder` This will look for the views that are use in the file and if only one will go to it, in case of more will show a select, in the view will look for the class that uses it +`Laravel recipes` There are some recipes like installing ide helper and running the model and eloquent command. and to install doctrine dbal +`Laravel health` trigger the neovim command `:checkhealth laravel` ## Lua API -As developer I want to enable other to extend this plugin to cover all your needs. For this I tried to have a good API. +### Telescope +One of the things you may want to change are actions or styles or something related to telescope +The picker is `require("telescope").extensions.laravel.routes` so you can call it and pass the arguments as usual for telescope +the same for `commands`, `related` and `history` -To run artisan commands you can execute +### Run commands +You may want to run commands of course you can use `:Artisan my-command args` but you may want to pass nui options and more for that you can use ```lua -require('laravel.application').run('artisan', {'your-command'}, {}) +local run = require "laravel.run" +run("artisan", {"my-command"}, {}) ``` -That is great but what about how to get the results not all commands behave in the same way. -Currently there are several runners: - -| Runner | Description | -| -------- | ---------------------------------------------------------------- | -| buffer | This opens an split and shows the results in a new buffer. This uses the `vim.fn.jobstart` to run the command | -| sync | This is more for api since the result of the command will be directly return to work with | -| async | Similar to `sync` but it takes a callback and will call it once the data is loaded, usefull for long process and to not block the editor | -| persist | One thing with buffers is that are temprary once you close the buffer the job is terminated, for some process you don't want that like, npm dev or other | -| watch | This is usefull for commands that needs to be retrigger one files are modifed, like queues restart, or tests | -| terminal | This runner uses the invocation of the `:term ` this provide a fully experience in a terminal, but it can't escape arguments in the command to execute | +This will be run in the nui, but you may want to do more plugins like and do something with the output +for that you can call the `api` -So you can run commands like ```lua -require('laravel.application').run('artisan', {'your-command'}, {runner = 'persist'}) +local api = require "laravel.api" ``` -Each runner returns different values since it have different behave. +Here you can use the methods `sync` and `async` I recommend the use of async that will not block the editor but there are cases that you may want to use sync. +have in consideration that to avoid hanging the editor there is the default timeout from plenary +The response and the value for callback is [ApiResponse](./lua/laravel/api/response.lua) +If you will use them I recommend peek into the code sync I use them a lot as building block for the plugin. -| Runner | Output | -| -- | -- | -| buffer | {buff, job} | -| sync | {out, exit_code, err } | -| async | {} | -| persist | {buff, job} | -| watch | {buff, job} | -| terminal | {buff, term_id} | - - -These runners are available for the following commands -- artisan -- composer -- sail -- npm -- yarn - -This is to provide the option to you to build what ever you need for you development experience. - -The commands have a default runner configure that you can customize -```lua - default_runner = "buffer", - commands_runner = { - ["dump-server"] = "persist", - ["queue:listen"] = "persist", - ["serve"] = "persist", - ["websockets"] = "persist", - ["queue:restart"] = "watch", - }, -``` - - -## Send to Tinker -Working with laravel tinker is a great tool so after thinking how can improve my flow with it I decide that selecting lines and have them send to tinker it was a good idea -So that is exactly what I did with the function `require("laravel.tinker").send_to_tinker()` which will grab the selected lines and send them to the open tinker or open a new one if is not already. -If you copy my keybindings from lazy or you can assign to your like is great. - - - -### Improve your flow. -Is normal that working with a project you have several things that you would like to start automatically. -Lets say for example that you would like to have two windows, one with the test running with every change of file and other with the dump server -we can write this -```lua -local function start() - vim.cmd "vsplit new" - local top = vim.api.nvim_get_current_win() - local width = vim.api.nvim_win_get_width(0) - vim.api.nvim_win_set_width(0, vim.fn.round(width * 2 / 3)) - vim.cmd "split new" - local bot = vim.api.nvim_get_current_win() - - local test_run = require("laravel.application").run('artisan', { "test" }, "watch", { open = false }) - local dump_run = require("laravel.application").run('artisan', { "dump-server" }, "persist", { open = false }) - - vim.api.nvim_win_set_buf(top, test_run.buff) - vim.api.nvim_win_set_buf(bot, dump_run.buff) -end -``` -The function will create an split, and resize it, split again and with these 2 windows will call the commands that want. Since in this case we want to set the position our self we send the option to not open. -After that we only need to set the buffer from the commands into the windows and ready. - - -Other example to just run everything -```lua -vim.api.nvim_create_user_command("StartMyApp", function () - require('laravel.application').run('artisan', {"serve"}) - require('laravel.application').run('artisan', {"queue:restart"}) - require('laravel.application').run('artisan', {"queue:listen"}) - require('laravel.application').run('yarn', {"dev"}, "persist") -end, {}) -``` -This will create your own command and when run will just call everyone of the commands, and split the windows as it needs and you resize when you want. Remember the `open = false` is an option to not have it display and run in the background. # Self promotion I am Ariel I am a developer and also content creator (mostly in Spanish) if you would like to show some love leave a start into the plugin and subscribe to my [Youtube](https://youtube.com/@Alpha_Dev) if you want to show even more love you can support becoming a member on Youtube. But just leaving a like or letting me know that you like and enjoy the plugin is appreciated. - # Collaboration I am open to review pr if you have ideas or ways to improve the plugin would be great. diff --git a/TODO.md b/TODO.md index 925f117..9fb8524 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,31 @@ # Ideas -Con telescope mostrar la lista de rutas y al seleccionar, llegar al controllador y al metodo - -- source: artisan list +## To implement +- [X] implement api +- [X] implement ui +- [X] implements a command history +- [X] remove bind telescope +- [X] Fix error when route list breaks it slows the heck out of the editor +- [X] order of arguments running command from telescope +- [X] completion for none-ls view('<"">') get all *.balde.php files. +- [X] implement default args | implement as options on the command options +- [X] escape on required args cancel it +- [X] when calling make commands without arguments run it in a popup +- [X] fix bug with `fd` switch from `sync` to proper use +- [X] How to implement checkhealth +- [X] completion route +- [X] detect library use by model show (command -precheck- on picker) +- [X] completion for routes when broken not loading to many making slow the editor +- [X] change the way of versions are being poll +- [X] maybe command for make only where only list commands of make principal laravel ones and open in a popup or how to configure from the command pallete +- [X] completion detect or not quotes +- [X] completion for model fields +- [X] remove completion for models +- [X] replace anonymous function from mapping with dedicated functions to have descriptions +- [X] using the get_type in completion is dangerus. better to replace it with ide-helper +- [X] recipes for ide-helper models and eloquent +- [X] implement watch +- [X] implement alternate file for livewire components `:A` +- [X] extend api .sync and .async with better respons object. maybe create a new object with method for example `.successful()` and `.failed()` +- [X] rework environment +- [X] re-write the readme and translate (not forget fd as dependency) move all info from readme to doc diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..b83639b --- /dev/null +++ b/devenv.lock @@ -0,0 +1,156 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1696609182, + "narHash": "sha256-PzVHnPnm+aceuQOoE3oExjHSxiLFAEiHFyQb3xXCI1c=", + "owner": "cachix", + "repo": "devenv", + "rev": "bd859ef4b207c2071f5bd3cae2a74f4d3e69c2e2", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1696757521, + "narHash": "sha256-cfgtLNCBLFx2qOzRLI6DHfqTdfWI+UbvsKYa3b3fvaA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2646b294a146df2781b1ca49092450e8a32814e1", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1685801374, + "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1696846637, + "narHash": "sha256-0hv4kbXxci2+pxhuXlVgftj/Jq79VSmtAyvfabCCtYk=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "42e1b6095ef80a51f79595d9951eb38e91c4e6ca", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..d1e8873 --- /dev/null +++ b/devenv.nix @@ -0,0 +1,25 @@ +{ pkgs, ... }: + +{ + # https://devenv.sh/basics/ + env.GREET = "devenv"; + + # https://devenv.sh/packages/ + packages = [ pkgs.git ]; + + enterShell = '' + git --version + ''; + + # https://devenv.sh/languages/ + # languages.nix.enable = true; + + # https://devenv.sh/pre-commit-hooks/ + pre-commit.hooks.luacheck.enable = true; + pre-commit.hooks.stylua.enable = true; + + # https://devenv.sh/processes/ + # processes.ping.exec = "ping example.com"; + + # See full reference at https://devenv.sh/reference/options/ +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..c7cb5ce --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,3 @@ +inputs: + nixpkgs: + url: github:NixOS/nixpkgs/nixpkgs-unstable diff --git a/doc/laravel.txt b/doc/laravel.txt new file mode 100644 index 0000000..c0a34d8 --- /dev/null +++ b/doc/laravel.txt @@ -0,0 +1,9 @@ +*laravel.txt* Plugin to work with Laravel Framework + +Author: Ariel D'Alessandro +Repo: https://github.com/adalessa/laravel.nvim +License: Same terms as Vim itself (see |license|) + +Usage *laravel* *:Artisan* + + vim:tw=78:et:ft=help:norl: diff --git a/doc/tags b/doc/tags new file mode 100644 index 0000000..c9714c2 --- /dev/null +++ b/doc/tags @@ -0,0 +1,3 @@ +:Artisan laravel.txt /*:Artisan* +laravel laravel.txt /*laravel* +laravel.txt laravel.txt /*laravel.txt* diff --git a/lua/.luarc.json b/lua/.luarc.json deleted file mode 100644 index e1b9d70..0000000 --- a/lua/.luarc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", - "Lua.workspace.checkThirdParty": false -} \ No newline at end of file diff --git a/lua/laravel/_autocommands.lua b/lua/laravel/_autocommands.lua deleted file mode 100644 index c40de59..0000000 --- a/lua/laravel/_autocommands.lua +++ /dev/null @@ -1,15 +0,0 @@ -local autocommands = {} - -autocommands.dir_changed = function(opts) - -- register an auto comamnds for the event DirChanged - -- this should calla the setup again with the provided options - local group = vim.api.nvim_create_augroup("laravel", {}) - vim.api.nvim_create_autocmd({ "DirChanged" }, { - group = group, - callback = function() - require("laravel").setup(opts) - end, - }) -end - -return autocommands diff --git a/lua/laravel/_config.lua b/lua/laravel/_config.lua deleted file mode 100644 index b7442ec..0000000 --- a/lua/laravel/_config.lua +++ /dev/null @@ -1,90 +0,0 @@ ----@class split ----@field relative string ----@field position string ----@field size string ----@field enter boolean -local split = { - relative = "editor", - position = "right", - size = "30%", - enter = true, -} - ----@class route_info ----@field enable boolean ----@field position string - ----@class laravel.config ----@field split split ----@field bind_telescope boolean ----@field default_runner string ----@field lsp_server string ----@field resources table ----@field register_user_commands boolean ----@field commands_runner table ----@field route_info route_info -local config = { - split = split, - bind_telescope = true, - register_user_commands = true, - lsp_server = "phpactor", - route_info = { - enable = true, - position = "right", - }, - default_runner = "buffer", - commands_runner = { - ["dump-server"] = "persist", - ["queue:listen"] = "persist", - ["serve"] = "persist", - ["websockets"] = "persist", - ["queue:restart"] = "watch", - ["tinker"] = "terminal", - ["db"] = "terminal", - }, - environment = { - resolver = require "laravel.environment.resolver"(true, true, nil), - environments = { - ["local"] = require("laravel.environment.native").setup(), - ["sail"] = require("laravel.environment.sail").setup(), - ["docker-compose"] = require("laravel.environment.docker_compose").setup(), - }, - }, - resources = { - ["make:cast"] = "app/Casts", - ["make:channel"] = "app/Broadcasting", - ["make:command"] = "app/Console/Commands", - ["make:component"] = "app/View/Components", - ["make:controller"] = "app/Http/Controllers", - ["make:event"] = "app/Events", - ["make:exception"] = "app/Exceptions", - ["make:factory"] = function(name) - return string.format("database/factories/%sFactory.php", name), nil - end, - ["make:job"] = "app/Jobs", - ["make:listener"] = "app/Listeners", - ["make:mail"] = "app/Mail", - ["make:middleware"] = "app/Http/Middleware", - ["make:migration"] = function(name) - local result = require("laravel.runners").sync { "fd", name .. ".php" } - if result.exit_code == 1 then - return "", result.error - end - - return result.out, nil - end, - ["make:model"] = "app/Models", - ["make:notification"] = "app/Notifications", - ["make:observer"] = "app/Observers", - ["make:policy"] = "app/Policies", - ["make:provider"] = "app/Providers", - ["make:request"] = "app/Http/Requests", - ["make:resource"] = "app/Http/Resources", - ["make:rule"] = "app/Rules", - ["make:scope"] = "app/Models/Scopes", - ["make:seeder"] = "database/seeders", - ["make:test"] = "tests/Feature", - }, -} - -return config diff --git a/lua/laravel/_container.lua b/lua/laravel/_container.lua deleted file mode 100644 index 9e7f75c..0000000 --- a/lua/laravel/_container.lua +++ /dev/null @@ -1,25 +0,0 @@ -local storage = {} - -return { - get = function(key, default) - if storage[key] ~= nil then - return storage[key] - end - - if type(default) == "function" then - return default() - end - return default - end, - set = function(key, value) - storage[key] = value - return value - end, - unset = function(key) - storage[key] = nil - return nil - end, - purge = function() - storage = {} - end, -} diff --git a/lua/laravel/_jobs.lua b/lua/laravel/_jobs.lua deleted file mode 100644 index 884c3ae..0000000 --- a/lua/laravel/_jobs.lua +++ /dev/null @@ -1,47 +0,0 @@ -local M = {} - -local cleanup_autocmd -local all_channels = {} - -local keys = vim.api.nvim_replace_termcodes("", true, false, true) -local function terminate(job_id) - vim.api.nvim_chan_send(job_id, keys) - vim.fn.jobstop(job_id) -end - ----@param job_id number ----@param bufnr number -M.register = function(job_id, bufnr) - if not cleanup_autocmd then - cleanup_autocmd = vim.api.nvim_create_autocmd("VimLeavePre", { - desc = "Clean up running overseer tasks on exit", - callback = function() - local job_ids = vim.tbl_keys(all_channels) - for _, j in ipairs(job_ids) do - terminate(j) - end - end, - }) - end - all_channels[job_id] = true - - vim.api.nvim_create_autocmd({ "BufUnload" }, { - buffer = bufnr, - callback = function() - M.terminate(job_id) - end, - }) -end - -M.unregister = function(job_id) - all_channels[job_id] = nil -end - -M.terminate = function(job_id) - if all_channels[job_id] ~= nil then - terminate(job_id) - all_channels[job_id] = nil - end -end - -return M diff --git a/lua/laravel/_lsp/init.lua b/lua/laravel/_lsp/init.lua index df2f25a..5d764a4 100644 --- a/lua/laravel/_lsp/init.lua +++ b/lua/laravel/_lsp/init.lua @@ -1,7 +1,6 @@ local phpactor = require "laravel._lsp.phpactor" local intelephense = require "laravel._lsp.intelephense" -local utils = require "laravel.utils" -local application = require "laravel.application" +local config = require "laravel.config" local servers = { phpactor = phpactor, @@ -17,8 +16,10 @@ local get_client = function(server_name) if not client then local server = require("lspconfig")[server_name] - local config = server.make_config(vim.fn.getcwd()) - local client_id = vim.lsp.start(config) + local client_id = vim.lsp.start(server.make_config(vim.fn.getcwd())) + if not client_id then + error "Could not start lsp client" + end client = vim.lsp.get_client_by_id(client_id) new_instance = true end @@ -29,18 +30,18 @@ end ---@param full_class string ---@param method string local go_to = function(full_class, method) - local server_name = application.get_options().lsp_server + local server_name = config.options.lsp_server local server = servers[server_name] if server == nil then - utils.notify("Route open", { msg = "No server name " .. server_name, level = "WARN" }) + vim.notify(string.format("No server with name %s found", server_name), vim.log.levels.WARN) return end local client, is_new_instance = get_client(server_name) if not client then - utils.notify("Route open", { msg = "Can't get lsp client", level = "WARN" }) + vim.notify("Can't get lsp client", vim.log.levels.WARN) return end diff --git a/lua/laravel/_lsp/intelephense.lua b/lua/laravel/_lsp/intelephense.lua index 3107d54..ea8c63b 100644 --- a/lua/laravel/_lsp/intelephense.lua +++ b/lua/laravel/_lsp/intelephense.lua @@ -1,5 +1,4 @@ local lsp_utils = require "laravel._lsp.utils" -local laravel_utils = require "laravel.utils" ---@param client table ---@param is_new_instance boolean @@ -25,7 +24,7 @@ local function go_to(client, is_new_instance, full_class, method) end if class_location == nil then - laravel_utils.notify("Route Open", { msg = "Could not find class for : " .. full_class, level = "WARN" }) + vim.notify("Could not find the class " .. full_class, vim.log.levels.WARN) if is_new_instance then vim.lsp.stop_client(client.id) end @@ -41,10 +40,7 @@ local function go_to(client, is_new_instance, full_class, method) vim.lsp.buf_request(0, "textDocument/documentSymbol", params, function(method_err, method_server_result, _, _) if method_err then - laravel_utils.notify( - "Route Open", - { msg = "Error when finding workspace symbols: " .. method_err.message, level = "WARN" } - ) + vim.notify("Error when finding workspace symbols " .. method_err.message, vim.log.levels.WARN) if is_new_instance then vim.lsp.stop_client(client.id) end @@ -53,10 +49,7 @@ local function go_to(client, is_new_instance, full_class, method) local method_locations = vim.lsp.util.symbols_to_items(method_server_result or {}, 0) or {} if vim.tbl_isempty(method_locations) then - lsp_utils.notify( - "Route open", - { msg = string.format("empty response looking for method: %s", method or "__invoke"), level = "WARN" } - ) + vim.notify(string.format("empty response looking for method: %s", method or "__invoke"), vim.log.levels.WARN) if is_new_instance then vim.lsp.stop_client(client.id) end @@ -84,7 +77,7 @@ local function go_to(client, is_new_instance, full_class, method) if row and col then local ok, err_msg = pcall(vim.api.nvim_win_set_cursor, 0, { row, col }) if not ok then - lsp_utils.notify("Route Open", { msg = "Erro setting row and col " .. err_msg, level = "WARN" }) + vim.notify(string.format("Error setting row and col %s", err_msg), vim.log.levels.WARN) end vim.cmd "normal zt" end diff --git a/lua/laravel/_lsp/phpactor.lua b/lua/laravel/_lsp/phpactor.lua index d96b4ac..e5f3818 100644 --- a/lua/laravel/_lsp/phpactor.lua +++ b/lua/laravel/_lsp/phpactor.lua @@ -1,5 +1,4 @@ local lsp_utils = require "laravel._lsp.utils" -local laravel_utils = require "laravel.utils" ---@param client table ---@param is_new_instance boolean @@ -14,7 +13,7 @@ local function go_to(client, is_new_instance, full_class, method) local locations = vim.lsp.util.symbols_to_items(resp.result or {}, nil) or {} if vim.tbl_isempty(locations) then - laravel_utils.notify("Route Open", { msg = "Empty response looking for class: " .. full_class, level = "WARN" }) + vim.notify("Could not find the class " .. full_class, vim.log.levels.WARN) if is_new_instance then vim.lsp.stop_client(client.id) end @@ -30,7 +29,7 @@ local function go_to(client, is_new_instance, full_class, method) end if class_location == nil then - laravel_utils.notify("Route Open", { msg = "Could not find class for : " .. full_class, level = "WARN" }) + vim.notify("Could not find the class " .. full_class, vim.log.levels.WARN) if is_new_instance then vim.lsp.stop_client(client.id) end @@ -46,10 +45,7 @@ local function go_to(client, is_new_instance, full_class, method) vim.lsp.buf_request(0, "textDocument/documentSymbol", params, function(method_err, method_server_result, _, _) if method_err then - laravel_utils.notify( - "Route Open", - { msg = "Error when finding workspace symbols: " .. method_err.message, level = "WARN" } - ) + vim.notify("Error when finding workspace symbols " .. method_err.message, vim.log.levels.WARN) if is_new_instance then vim.lsp.stop_client(client.id) end @@ -58,10 +54,7 @@ local function go_to(client, is_new_instance, full_class, method) local method_locations = vim.lsp.util.symbols_to_items(method_server_result or {}, 0) or {} if vim.tbl_isempty(method_locations) then - lsp_utils.notify( - "Route open", - { msg = string.format("empty response looking for method: %s", method or "__invoke"), level = "WARN" } - ) + vim.notify(string.format("empty response looking for method: %s", method or "__invoke"), vim.log.levels.WARN) if is_new_instance then vim.lsp.stop_client(client.id) end @@ -89,7 +82,7 @@ local function go_to(client, is_new_instance, full_class, method) if row and col then local ok, err_msg = pcall(vim.api.nvim_win_set_cursor, 0, { row, col }) if not ok then - lsp_utils.notify("Route Open", { msg = "Erro setting row and col " .. err_msg, level = "WARN" }) + vim.notify(string.format("Error setting row and col %s", err_msg), vim.log.levels.WARN) end vim.cmd "normal zt" end diff --git a/lua/laravel/api/init.lua b/lua/laravel/api/init.lua new file mode 100644 index 0000000..1ea95a6 --- /dev/null +++ b/lua/laravel/api/init.lua @@ -0,0 +1,61 @@ +local Job = require "plenary.job" +local environment = require "laravel.environment" +local ApiResponse = require "laravel.api.response" + +local M = {} + +function M.generate_command(name, args) + local executable = environment.get_executable(name) + if not executable then + error(string.format("Executable %s not found", name), vim.log.levels.ERROR) + end + + return vim.fn.extend(executable, args) +end + +--- Run the command sync +---@param program string +---@param args string[] +---@return ApiResponse +function M.sync(program, args) + local cmd = M.generate_command(program, args) + local command = table.remove(cmd, 1) + local stderr = {} + + local stdout, ret = Job:new({ + command = command, + args = cmd, + on_stderr = function(_, data) + table.insert(stderr, data) + end, + }):sync() + + return ApiResponse:new(stdout, ret, stderr) +end + +--- Run the command async +---@param program string +---@param args string[] +---@param callback function +function M.async(program, args, callback) + local cmd = M.generate_command(program, args) + local command = table.remove(cmd, 1) + + Job:new({ + command = command, + args = cmd, + on_exit = vim.schedule_wrap(function(j, exit_code) + callback(ApiResponse:new(j:result(), exit_code, j:stderr_result())) + end), + }):start() +end + +function M.is_composer_package_install(package) + return M.sync("composer", { "info", package }):successful() +end + +function M.php_execute(code) + return M.sync("artisan", { "tinker", "--execute", "echo " .. code }) +end + +return M diff --git a/lua/laravel/api/response.lua b/lua/laravel/api/response.lua new file mode 100644 index 0000000..fbcbf85 --- /dev/null +++ b/lua/laravel/api/response.lua @@ -0,0 +1,77 @@ +--- ApiResponse class represents the result of a command execution. +---@class ApiResponse +---@field stdout string[] The standard output of the command execution. +---@field exit_code number The exit code indicating how the command ended (0 for success, non-zero for failure). +---@field stderror string[] The standard error output in case of errors during command execution. +local ApiResponse = {} + +---@param stdout string[] +---@param exit_code number +---@param stderror string[] +---@return ApiResponse +function ApiResponse:new(stdout, exit_code, stderror) + local obj = { + stdout = stdout, + exit_code = exit_code, + stderror = stderror, + } + + setmetatable(obj, self) + self.__index = self + + return obj +end + +---@return boolean +function ApiResponse:successful() + return self.exit_code == 0 +end + +---@return boolean +function ApiResponse:failed() + return not self:successful() +end + +--- Returns the content +---@return string[] +function ApiResponse:content() + return self.stdout +end + +---@return string +function ApiResponse:prettyContent() + return table.concat(self:content(), "\r\n") +end + +---@return string|nil +function ApiResponse:first() + if self:failed() then + return nil + end + + return self.stdout[1] +end + +---@return string[]|nil +function ApiResponse:errors() + if self:successful() then + return nil + end + + if not vim.tbl_isempty(self.stderror) then + return self.stderror + end + + return self:content() +end + +function ApiResponse:prettyErrors() + local errors = self:errors() + if not errors then + return "" + end + + return table.concat(errors, "\r\n") +end + +return ApiResponse diff --git a/lua/laravel/application/init.lua b/lua/laravel/application/init.lua deleted file mode 100644 index 111eb2e..0000000 --- a/lua/laravel/application/init.lua +++ /dev/null @@ -1,153 +0,0 @@ -local container = require "laravel._container" -local environment = require "laravel.environment" -local utils = require "laravel.utils" -local runners = require "laravel.runners" - -local app = nil - ----@param options laravel.config -local initialize = function(options) - app = nil - - if vim.fn.filereadable "artisan" == 0 then - return - end - - local resolvedEnv = environment.initialize(options.environment.environments, options.environment.resolver) - -- fill with the environment - if not resolvedEnv then - utils.notify("App", { msg = "Could not initialize environment", level = "ERROR" }) - return - end - - app = { - envSetup = resolvedEnv, - environment = resolvedEnv(), - options = options, - } -end - ----@param command string ----@return boolean -local has_command = function(command) - local executable = app.environment.executables[command] - - return executable ~= nil -end - ----@param command string ----@param args table -local build_command = function(command, args) - local out = {} - if not has_command(command) then - utils.notify("Build command", { - msg = string.format("Command %s not available in environment", command), - level = "ERROR", - }) - - return - end - - local executable = app.environment.executables[command] - - for _, part in ipairs(executable) do - table.insert(out, part) - end - - for _, part in ipairs(args) do - table.insert(out, part) - end - return out -end - -local warmup = function() - app.environment = app.envSetup() - require("laravel.commands").load() - require("laravel.routes").load() -end - ----@return boolean -local ready = function() - return app ~= nil -end - ----@param command string ----@param args table ----@param opts table ----@return table, boolean -local run = function(command, args, opts) - opts = opts or {} - local is_tinker = command == "artisan" and args[1] == "tinker" - - local cmd = build_command(command, args) - local runner = opts.runner or app.options.commands_runner[args[1]] or app.options.default_runner - - local result, ok = runners[runner](cmd, opts) - - local function getChannel() - if runner == "buffer" then - return result.job - elseif runner == "terminal" then - return result.term_id - end - end - - if ok and is_tinker then - container.set("tinker", getChannel()) - vim.api.nvim_create_autocmd({ "BufDelete" }, { - buffer = result.buff, - callback = function() - container.unset "tinker" - end, - }) - end - - return result, ok -end - ----@param decorated function -local check_ready = function(decorated) - return function(...) - if not ready() then - utils.notify( - "application", - { level = "ERROR", msg = "The application is not ready for current working directory" } - ) - end - - return decorated(...) - end -end - -return { - initialize = initialize, - - run = check_ready(run), - - has_command = check_ready(has_command), - - warmup = check_ready(warmup), - - ready = ready, - - container = container, - - get_options = check_ready(function() - return app.options - end), - - get_info = function() - local info = { - ready = ready(), - } - - if not ready() then - return info - end - - info.options = app.options - info.environment = app.environment - - return info - end, -} diff --git a/lua/laravel/autocommands.lua b/lua/laravel/autocommands.lua new file mode 100644 index 0000000..fa5f7aa --- /dev/null +++ b/lua/laravel/autocommands.lua @@ -0,0 +1,13 @@ +local M = {} + +function M.setup() + local environment = require "laravel.environment" + + local group = vim.api.nvim_create_augroup("laravel", {}) + vim.api.nvim_create_autocmd({ "DirChanged" }, { + group = group, + callback = environment.setup, + }) +end + +return M diff --git a/lua/laravel/command.lua b/lua/laravel/command.lua deleted file mode 100644 index c6fd15c..0000000 --- a/lua/laravel/command.lua +++ /dev/null @@ -1,63 +0,0 @@ -local application = require "laravel.application" - ----@class CommandArgument ----@field name string ----@field is_required boolean ----@field is_array boolean ----@field description string ----@field default string - ----@class CommandOption ----@field name string ----@field shortcut string|nil ----@field accept_value boolean ----@field is_value_required boolean ----@field is_multiple boolean ----@field description string ----@field default any - ----@class CommandDefinition ----@field arguments CommandArgument[] ----@field options CommandOption[] - ----@class LaravelCommand ----@field name string ----@field description string ----@field usage string[] ----@field help string ----@field hidden boolean ----@field definition CommandDefinition ----@field runner string - -local M = {} - ----Gets list of commands from the raw json ----@param json string ----@return LaravelCommand[] -M.from_json = function(json) - local cmds = {} - - if json == "" or json == nil or #json == 0 then - return cmds - end - - for _, cmd in ipairs(vim.fn.json_decode(json).commands) do - if not cmd.hidden then - table.insert(cmds, cmd) - end - end - return cmds -end - ---- Gets the runner for a given command ----@param command LaravelCommand -M.get_runner = function(command) - local runner = application.get_options().commands_runner[command.name] - if runner ~= nil then - return runner - end - - return application.get_options().default_runner -end - -return M diff --git a/lua/laravel/commands.lua b/lua/laravel/commands.lua deleted file mode 100644 index 3b3409c..0000000 --- a/lua/laravel/commands.lua +++ /dev/null @@ -1,61 +0,0 @@ -local log = require("laravel.dev").log -local application = require "laravel.application" -local laravel_command = require "laravel.command" -local utils = require "laravel.utils" - -local container_key = "artisan_commands" - -return { - clean = function() - application.container.unset(container_key) - end, - - list = function() - if not application.ready() then - utils.notify( - "Commands List", - { level = "ERROR", msg = "The application is not ready for current working directory" } - ) - return nil - end - - local commands = application.container.get(container_key) - if commands then - return commands - end - -- return the commands - local result, ok = application.run("artisan", { "list", "--format=json" }, { runner = "sync" }) - if not ok then - return nil - end - - if result.exit_code == 1 then - log.error("app.commands(): stdout", result.out) - log.error("app.commands(): stderr", result.err) - return nil - end - - commands = laravel_command.from_json(result.out) - - if #commands > 0 then - application.container.set(container_key, commands) - end - - return commands - end, - - load = function() - application.run("artisan", { "list", "--format=json" }, { - runner = "async", - callback = function(j, exit_code) - if exit_code == 1 then - application.container.unset(container_key) - end - local commands = laravel_command.from_json(j:result()) - if #commands > 0 then - application.container.set(container_key, commands) - end - end, - }) - end, -} diff --git a/lua/laravel/commands/init.lua b/lua/laravel/commands/init.lua new file mode 100644 index 0000000..931a0cd --- /dev/null +++ b/lua/laravel/commands/init.lua @@ -0,0 +1,18 @@ +local utils = require "laravel.commands.utils" +local api = require "laravel.api" + +local M = {} + +M.list = {} + +function M.load() + M.list = {} + local result = api.sync("artisan", { "list", "--format=json" }) + if result:failed() then + error(result:failed(), vim.log.levels.ERROR) + end + + M.list = utils.from_json(result.stdout) +end + +return M diff --git a/lua/laravel/commands/utils.lua b/lua/laravel/commands/utils.lua new file mode 100644 index 0000000..46fe299 --- /dev/null +++ b/lua/laravel/commands/utils.lua @@ -0,0 +1,18 @@ +local M = {} + +M.from_json = function(json) + local cmds = {} + + if json == "" or json == nil or #json == 0 then + return cmds + end + + for _, cmd in ipairs(vim.fn.json_decode(json).commands) do + if not cmd.hidden then + table.insert(cmds, cmd) + end + end + return cmds +end + +return M diff --git a/lua/laravel/config/command_options.lua b/lua/laravel/config/command_options.lua new file mode 100644 index 0000000..51f4775 --- /dev/null +++ b/lua/laravel/config/command_options.lua @@ -0,0 +1,4 @@ +return { + ["tinker"] = { skip_args = true }, + ["docs"] = { ui = "popup", skip_args = true }, +} diff --git a/lua/laravel/config/default.lua b/lua/laravel/config/default.lua new file mode 100644 index 0000000..f9ccf06 --- /dev/null +++ b/lua/laravel/config/default.lua @@ -0,0 +1,17 @@ +return { + lsp_server = "phpactor", + register_user_commands = true, + features = { + null_ls = { + enable = true, + }, + route_info = { + enable = true, + position = "right", + }, + }, + ui = require "laravel.config.ui", + commands_options = require "laravel.config.command_options", + environments = require "laravel.config.environments", + resources = require "laravel.config.resources", +} diff --git a/lua/laravel/config/environments.lua b/lua/laravel/config/environments.lua new file mode 100644 index 0000000..893e3bb --- /dev/null +++ b/lua/laravel/config/environments.lua @@ -0,0 +1,43 @@ +return { + env_variable = "NVIM_LARAVEL_ENV", + auto_dicover = true, + default = "local", + definitions = { + ["sail"] = { + condition = { + file_exists = { "vendor/bin/sail", "docker-compose.yml" }, + }, + commands = { + sail = { "vendor/bin/sail" }, + { + commands = { "php", "composer", "npm", "yarn" }, + prefix = { "vendor/bin/sail" }, + }, + }, + }, + ["docker-compose"] = { + condition = { + file_exists = { "docker-compose.yml" }, + executable = { "docker" }, + }, + commands = { + compose = { "docker", "compose" }, + { + commands = { "php", "composer", "npm" }, + docker = { + container = { + env = "APP_SERVICE", + default = "app", + }, + exec = { "docker", "compose", "exec", "-it" }, + }, + }, + }, + }, + ["local"] = { + condition = { + executable = { "php" }, + }, + }, + }, +} diff --git a/lua/laravel/config/init.lua b/lua/laravel/config/init.lua new file mode 100644 index 0000000..aaa6fc3 --- /dev/null +++ b/lua/laravel/config/init.lua @@ -0,0 +1,13 @@ +local M = {} + +---@class LaravelOptions +M.defaults = require "laravel.config.default" + +--- @type LaravelOptions +M.options = {} + +function M.setup(options) + M.options = vim.tbl_deep_extend("force", {}, M.defaults, options or {}) +end + +return M diff --git a/lua/laravel/config/resources.lua b/lua/laravel/config/resources.lua new file mode 100644 index 0000000..549a09b --- /dev/null +++ b/lua/laravel/config/resources.lua @@ -0,0 +1,36 @@ +-- each element represent a command that after execution should open a file +-- if the return is a string is use as directory to search file. +-- if the return is a function will call and expects the result to be an string +return { + ["make:cast"] = "app/Casts", + ["make:channel"] = "app/Broadcasting", + ["make:command"] = "app/Console/Commands", + ["make:component"] = "app/View/Components", + ["make:controller"] = "app/Http/Controllers", + ["make:event"] = "app/Events", + ["make:exception"] = "app/Exceptions", + ["make:factory"] = function(name) + return string.format("database/factories/%sFactory.php", name) + end, + ["make:job"] = "app/Jobs", + ["make:listener"] = "app/Listeners", + ["make:mail"] = "app/Mail", + ["make:middleware"] = "app/Http/Middleware", + ["make:migration"] = function(name) + return vim.fn.systemlist(string.format("fd %s.php", name))[1] + end, + ["make:model"] = "app/Models", + ["make:notification"] = "app/Notifications", + ["make:observer"] = "app/Observers", + ["make:policy"] = "app/Policies", + ["make:provider"] = "app/Providers", + ["make:request"] = "app/Http/Requests", + ["make:resource"] = "app/Http/Resources", + ["make:rule"] = "app/Rules", + ["make:scope"] = "app/Models/Scopes", + ["make:seeder"] = "database/seeders", + ["make:test"] = "tests/Feature", + ["make:view"] = function(name) + return "resources/views/" .. name:gsub("%.", "/") .. ".blade.php" + end, +} diff --git a/lua/laravel/config/ui.lua b/lua/laravel/config/ui.lua new file mode 100644 index 0000000..d8bc1f8 --- /dev/null +++ b/lua/laravel/config/ui.lua @@ -0,0 +1,37 @@ +return { + default = "split", + nui_opts = { + split = { + enter = true, + relative = "editor", + position = "right", + size = "33%", + buf_options = {}, + win_options = { + number = false, + relativenumber = false, + }, + }, + popup = { + enter = true, + focusable = true, + relative = "editor", + border = { + style = "rounded", + }, + position = { + row = "20%", + col = "50%", + }, + size = { + width = "28%", + height = "35%", + }, + buf_options = {}, + win_options = { + number = false, + relativenumber = false, + }, + }, + }, +} diff --git a/lua/laravel/dev.lua b/lua/laravel/dev.lua deleted file mode 100644 index d359230..0000000 --- a/lua/laravel/dev.lua +++ /dev/null @@ -1,45 +0,0 @@ -local M = {} - -function M.reload() - require("plenary.reload").reload_module "laravel" -end - -local log_levels = { "trace", "debug", "info", "warn", "error", "fatal" } - -local function set_log_level() - local log_level = vim.env.LARAVEL_LOG or vim.g.laravel_log_level - - for _, level in pairs(log_levels) do - if level == log_level then - return log_level - end - end - - return "warn" -- default, if user hasn't set to one from log_levels -end - -local log_level = set_log_level() - -M.log = require("plenary.log").new { - plugin = "laravel", - level = log_level, -} - -local log_key = os.time() - -local function override(key) - local fn = M.log[key] - M.log[key] = function(...) - fn(log_key, ...) - end -end - -for _, v in pairs(log_levels) do - override(v) -end - -function M.get_log_key() - return log_key -end - -return M diff --git a/lua/laravel/environment/docker_compose.lua b/lua/laravel/environment/docker_compose.lua deleted file mode 100644 index 1e408fa..0000000 --- a/lua/laravel/environment/docker_compose.lua +++ /dev/null @@ -1,34 +0,0 @@ -local utils = require "laravel.utils" - -local M = {} - ----@param opts table|nil ----@return function -M.setup = function(opts) - return function() - opts = opts or {} - - local container = utils.get_env "APP_SERVICE" or opts.container_name or "app" - - local cmd = opts.cmd or { "docker", "compose", "exec", "-it", container } - - local function get_cmd(args) - return vim.fn.extend(cmd, args) - end - - local executables = { - artisan = opts.artisan or get_cmd { "php", "artisan" }, - composer = opts.composer or get_cmd { "composer" }, - npm = opts.npm or get_cmd { "npm" }, - yarn = opts.yarn or get_cmd { "yarn" }, - php = opts.php or get_cmd { "php" }, - compose = opts.compose or { "docker", "compose" }, - } - - return { - executables = vim.tbl_deep_extend("force", executables, opts.executables or {}), - } - end -end - -return M diff --git a/lua/laravel/environment/environment.lua b/lua/laravel/environment/environment.lua new file mode 100644 index 0000000..4f96f9b --- /dev/null +++ b/lua/laravel/environment/environment.lua @@ -0,0 +1,107 @@ +local get_env = require("laravel.utils").get_env + +---@class Environment +---@field name string +---@field condition table|nil +---@field commands table +local Environment = {} + +local cache = {} + +---@param env table +---@return Environment +function Environment:new(name, env) + local obj = { + name = name, + condition = env.condition or nil, + commands = env.commands or {}, + } + + setmetatable(obj, self) + self.__index = self + + return obj +end + +---@return boolean +function Environment:check() + if not self.condition then + return true + end + + for _, file in pairs(self.condition.file_exists or {}) do + if vim.fn.filereadable(file) ~= 1 then + return false + end + end + + for _, exec in pairs(self.condition.executable or {}) do + if vim.fn.executable(exec) == 0 then + return false + end + end + + return true +end + +---@param name string +---@return table|nil +function Environment:executable(name) + if cache[name] then + return cache[name] + end + + -- check commands directly by name + if self.commands[name] then + cache[name] = self.commands[name] + return cache[name] + end + + for _, value in pairs(self.commands) do + if vim.tbl_contains(value.commands or {}, name) then + -- is on the list have to process it + if value.docker then + -- is set to run from docker + if not value.docker.container then + error( + "Configuration indicates docker but there is no container information, check the configuration", + vim.log.levels.ERROR + ) + end + + local container = value.docker.container.default + if value.docker.container.env and get_env(value.docker.container.env) then + container = get_env(value.docker.container.env) + end + + if not container then + error("Could not resolve container name check the configuration", vim.log.levels.ERROR) + end + + if not value.docker.exec then + error("Need to define a docker exec command", vim.log.levels.ERROR) + end + + cache[name] = vim.fn.extend(value.docker.exec, { container, name }) + + return cache[name] + end + + if value.prefix then + cache[name] = vim.fn.extend(value.prefix, { name }) + + return cache[name] + end + end + end + + -- if is not define look for the executable in the system + if vim.fn.executable(name) == 1 then + cache[name] = { name } + return { name } + end + + return nil +end + +return Environment diff --git a/lua/laravel/environment/init.lua b/lua/laravel/environment/init.lua index c936de9..20777e5 100644 --- a/lua/laravel/environment/init.lua +++ b/lua/laravel/environment/init.lua @@ -1,9 +1,76 @@ -local environment = {} +local config = require "laravel.config" +local user_commands = require "laravel.user_commands" +local get_env = require("laravel.utils").get_env +local Environment = require "laravel.environment.environment" ----@param environments table ----@param resolver function -environment.initialize = function(environments, resolver) - return resolver(environments) +local M = {} + +M.environment = nil + +---@return Environment|nil +local function resolve() + local opts = config.options.environments + + if opts.env_variable then + local env_name = get_env(opts.env_variable) + if env_name and opts.definitions[env_name] then + return Environment:new(env_name, opts.definitions[env_name]) + end + end + + if opts.auto_dicover then + for name, opt in pairs(opts.definitions) do + local env = Environment:new(name, opt) + if env:check() then + return env + end + end + end + + if opts.default then + if opts.definitions[opts.default] then + return Environment:new(opts.default, opts.definitions[opts.default]) + end + end + + return nil +end + +function M.setup() + M.environment = nil + if vim.fn.filereadable "artisan" == 0 then + return + end + + M.environment = resolve() + + if not M.environment then + return + end + + user_commands.setup() + + if config.options.features.route_info.enable then + require("laravel.route_info").setup() + end + + if config.options.features.null_ls.enable then + require("laravel.null_ls").setup() + end +end + +---@param name string +---@return string[]|nil +function M.get_executable(name) + if not M.environment then + return nil + end + + if name == "artisan" then + return vim.fn.extend(M.environment:executable "php", { "artisan" }) + end + + return M.environment:executable(name) end -return environment +return M diff --git a/lua/laravel/environment/native.lua b/lua/laravel/environment/native.lua deleted file mode 100644 index bb12687..0000000 --- a/lua/laravel/environment/native.lua +++ /dev/null @@ -1,23 +0,0 @@ -local M = {} - ----@param opts table|nil ----@return function -M.setup = function(opts) - return function() - opts = opts or {} - - local executables = { - artisan = opts.artisan or { "php", "artisan" }, - composer = opts.composer or { "composer" }, - npm = opts.npm or { "npm" }, - yarn = opts.yarn or { "yarn" }, - php = opts.php or { "php" }, - } - - return { - executables = vim.tbl_deep_extend("force", executables, opts.executables or {}), - } - end -end - -return M diff --git a/lua/laravel/environment/resolver.lua b/lua/laravel/environment/resolver.lua deleted file mode 100644 index 74d4c7d..0000000 --- a/lua/laravel/environment/resolver.lua +++ /dev/null @@ -1,60 +0,0 @@ -local utils = require "laravel.utils" - ----@param env_check boolean ----@param auto_discovery boolean ----@param default string|nil -return function(env_check, auto_discovery, default) - return function(environments) - local env_name = utils.get_env "NVIM_LARAVEL_ENV" - if env_check and env_name ~= nil then - local environment = environments[env_name] - if environment == nil then - utils.notify("Environment resolver", { - msg = "NVIM_LARAVEL_ENV defined as " .. env_name .. " but there is no such environment defined", - level = "ERROR", - }) - return nil - else - return environment - end - end - - if auto_discovery then - -- check for sail - if - environments.sail ~= nil - and vim.fn.filereadable "vendor/bin/sail" == 1 - and vim.fn.filereadable "docker-compose.yml" == 1 - then - return environments.sail - end - -- check for docker-compose - if environments["docker-compose"] ~= nil and vim.fn.filereadable "docker-compose.yml" == 1 then - return environments["docker-compose"] - end - -- check for native - if environments["local"] ~= nil and vim.fn.executable "php" then - return environments["local"] - end - end - - if default then - local environment = environments[default] - if environment == nil then - utils.notify( - "Environment resolver", - { msg = "Default define as " .. default .. " but there is no environment define", level = "ERROR" } - ) - return nil - else - return environment - end - end - - utils.notify( - "Environment resolver", - { msg = "Could not resolve any environment please check your configuration", level = "ERROR" } - ) - return nil - end -end diff --git a/lua/laravel/environment/sail.lua b/lua/laravel/environment/sail.lua deleted file mode 100644 index 00ce7dd..0000000 --- a/lua/laravel/environment/sail.lua +++ /dev/null @@ -1,30 +0,0 @@ -local M = {} - ----@param opts table|nil ----@return function -M.setup = function(opts) - return function() - opts = opts or {} - - local cmd = opts.cmd or { "vendor/bin/sail" } - - local function get_cmd(args) - return vim.fn.extend(cmd, args) - end - - local executables = { - artisan = opts.artisan or get_cmd { "artisan" }, - composer = opts.composer or get_cmd { "composer" }, - npm = opts.npm or get_cmd { "npm" }, - yarn = opts.yarn or get_cmd { "yarn" }, - sail = opts.sail or cmd, - php = opts.php or get_cmd { "php" }, - } - - return { - executables = vim.tbl_deep_extend("force", executables, opts.executables or {}), - } - end -end - -return M diff --git a/lua/laravel/health.lua b/lua/laravel/health.lua new file mode 100644 index 0000000..61407e6 --- /dev/null +++ b/lua/laravel/health.lua @@ -0,0 +1,73 @@ +local environment = require "laravel.environment" +local api = require "laravel.api" +local M = {} + +M.check = function() + vim.health.report_start "Laravel" + + if vim.fn.executable "fd" == 1 then + vim.health.report_ok "fd installed" + else + vim.health.report_warn( + "fd is missing, is required for opening the migration", + { "Installed from your package manager or source https://github.com/sharkdp/fd" } + ) + end + + if vim.fn.executable "rg" == 1 then + vim.health.report_ok "rg installed" + else + vim.health.report_warn( + "ripgrep is missing, is required for finding view usage", + { "Installed from your package manager" } + ) + end + + vim.health.report_start "Environment" + + if not environment.environment then + vim.health.report_error( + "Environment not configure for this directory, no more checks", + { "Check project is laravel, current directory `:pwd` is the root of laravel project" } + ) + return + end + + vim.health.report_ok "Environment setup successful" + + vim.health.report_info("Name: " .. environment.environment.name) + vim.health.report_info "Condition: " + vim.health.report_info(vim.inspect(environment.environment.condition)) + vim.health.report_info "Commands: " + vim.health.report_info(vim.inspect(environment.environment.commands)) + + vim.health.report_start "Composer Dependencies" + + if not environment.get_executable "composer" then + vim.health.report_error "Composer executable not found can't check dependencies" + end + + local composer_dependencies = { + { + name = "doctrine/dbal", + messages = "This is required for model:show, related model picker", + }, + { + name = "laravel/tinker", + messages = "This is required for tinker repl", + }, + } + + for _, dependency in pairs(composer_dependencies) do + if api.is_composer_package_install(dependency.name) then + vim.health.report_ok(string.format("Composer dependency `%s` is installed", dependency.name)) + else + vim.health.report_warn( + string.format("Composer dependency `%s` is not installed", dependency.name), + { dependency.messages } + ) + end + end +end + +return M diff --git a/lua/laravel/history.lua b/lua/laravel/history.lua new file mode 100644 index 0000000..0440306 --- /dev/null +++ b/lua/laravel/history.lua @@ -0,0 +1,18 @@ +local list = {} + +local M = {} + +function M.add(jobId, name, args, opts) + table.insert(list, { + jobId = jobId, + name = name, + args = args, + opts = opts, + }) +end + +function M.all() + return list +end + +return M diff --git a/lua/laravel/init.lua b/lua/laravel/init.lua index 78e9701..43690ce 100644 --- a/lua/laravel/init.lua +++ b/lua/laravel/init.lua @@ -1,45 +1,14 @@ -local Dev = require "laravel.dev" -local defaults = require "laravel._config" -local application = require "laravel.application" -local _autocommands = require "laravel._autocommands" - -local log = Dev.log - local M = {} ----Set up laravel plugin ----@param opts laravel.config|nil +---@param opts? LaravelOptions function M.setup(opts) - log.trace "setup(): Setting up..." - log.trace("setup(): log_key", Dev.get_log_key()) - - _autocommands.dir_changed(opts or {}) - - local options = vim.tbl_deep_extend("force", defaults, opts or {}) - - application.initialize(options) - - if not application.ready() then - return - end - - application.warmup() - - -- TODO: remove once 0.9 was general available - if vim.fn.has "nvim-0.9.0" ~= 1 then - vim.treesitter.query.get = vim.treesitter.get_query - vim.treesitter.query.set = vim.treesitter.set_query - end - - log.debug("setup(): Complete config", options) - - if options.register_user_commands then - require("laravel.user-commands").setup() - end + local config = require "laravel.config" + local environment = require "laravel.environment" + local autocmds = require "laravel.autocommands" - if options.route_info.enable then - require("laravel.route_info").register() - end + config.setup(opts) + autocmds.setup() + environment.setup() end return M diff --git a/lua/laravel/null_ls/completion/init.lua b/lua/laravel/null_ls/completion/init.lua new file mode 100644 index 0000000..a674337 --- /dev/null +++ b/lua/laravel/null_ls/completion/init.lua @@ -0,0 +1,62 @@ +local M = {} + +local completions = { + view = require "laravel.null_ls.completion.view", + route = require "laravel.null_ls.completion.route", +} + +local function get_function_node(param_node) + local node = param_node + while node ~= nil and node.type(node) ~= "function_call_expression" do + node = node:parent() + end + + return node +end + +function M.setup() + local ok, null_ls = pcall(require, "null-ls") + if not ok then + error "Completition requires null ls" + end + + null_ls.deregister "Laravel" + + local laravel = { + name = "Laravel", + method = null_ls.methods.COMPLETION, + filetypes = { "php" }, + generator = { + fn = function(params, done) + local node = vim.treesitter.get_node() + if not node then + return + end + + local func_node = get_function_node(node) + if not func_node then + return + end + + if func_node:child_count() > 0 then + local node_text = vim.treesitter.get_node_text(func_node:child(0), params.bufnr, {}) + + -- encapsed_string initial node it means is in double quotes + -- string initial node it means single quotes + -- arguments does not have quotes + local have_quotes = node.type(node) == "encapsed_string" or node.type(node) == "string" + local completion = completions[node_text] + if not completion then + return + end + completion(done, not have_quotes) + end + end, + async = true, + }, + } + + null_ls.register(laravel) +end + +return M diff --git a/lua/laravel/null_ls/completion/route.lua b/lua/laravel/null_ls/completion/route.lua new file mode 100644 index 0000000..bef3b4a --- /dev/null +++ b/lua/laravel/null_ls/completion/route.lua @@ -0,0 +1,37 @@ +local failed_last_time = false + +return function(done, should_quote) + local routes = require "laravel.routes" + + local candidates = {} + + if vim.tbl_isempty(routes.list) then + if failed_last_time then + return + end + + local ok, res = pcall(routes.load) + if not ok or not res then + failed_last_time = true + return + end + end + failed_last_time = false + + for _, route in pairs(routes.list) do + if route.name then + local insert = route.name + if should_quote then + insert = string.format("'%s'", route.name) + end + table.insert(candidates, { + label = string.format("%s (route)", route.name), + insertText = insert, + kind = vim.lsp.protocol.CompletionItemKind["Value"], + documentation = string.format("[%s] %s", route.name, route.uri), + }) + end + end + + done { { items = candidates, isIncomplete = false } } +end diff --git a/lua/laravel/null_ls/completion/view.lua b/lua/laravel/null_ls/completion/view.lua new file mode 100644 index 0000000..7320ee8 --- /dev/null +++ b/lua/laravel/null_ls/completion/view.lua @@ -0,0 +1,25 @@ +local paths = require "laravel.paths" +local scan = require "plenary.scandir" + +return function(done, should_quote) + local candidates = {} + local view_path = paths.resource_path "views" + local rule = string.format("^%s/(.*).blade.php$", view_path:gsub("-", "%%-")) + local finds = scan.scan_dir(view_path, { hidden = false, depth = 4 }) + for _, value in pairs(finds) do + local name = value:match(rule):gsub("/", ".") + + local insert = name + if should_quote then + insert = string.format("'%s'", name) + end + table.insert(candidates, { + label = string.format("%s (view)", name), + insertText = insert, + kind = vim.lsp.protocol.CompletionItemKind["Value"], + documentation = value, + }) + end + + done { { items = candidates, isIncomplete = false } } +end diff --git a/lua/laravel/null_ls/init.lua b/lua/laravel/null_ls/init.lua new file mode 100644 index 0000000..808a4b1 --- /dev/null +++ b/lua/laravel/null_ls/init.lua @@ -0,0 +1,7 @@ +local M = {} + +function M.setup() + require("laravel.null_ls.completion").setup() +end + +return M diff --git a/lua/laravel/paths.lua b/lua/laravel/paths.lua new file mode 100644 index 0000000..3fa1d78 --- /dev/null +++ b/lua/laravel/paths.lua @@ -0,0 +1,34 @@ +local api = require "laravel.api" + +local cache = {} + +local M = {} + +---@return ApiResponse +local function exec(cmd) + if not cache[cmd] then + cache[cmd] = api.php_execute(cmd) + end + + return cache[cmd] +end + +local function get_cwd() + return vim.fn.getcwd() +end + +local function get_base_path() + return exec("base_path()"):first() +end + +local function map_path(path) + return path:gsub(get_base_path():gsub("-", "%%-"), get_cwd()) +end + +function M.resource_path(resource) + local path = exec(string.format("resource_path('%s')", resource)):first() + + return map_path(path) +end + +return M diff --git a/lua/laravel/recipes/doctrine-dbal.lua b/lua/laravel/recipes/doctrine-dbal.lua new file mode 100644 index 0000000..f91deef --- /dev/null +++ b/lua/laravel/recipes/doctrine-dbal.lua @@ -0,0 +1,24 @@ +local api = require "laravel.api" + +local M = {} + +function M.run() + if not api.is_composer_package_install "doctrine/dbal" then + api.async( + "composer", + { "require", "--dev", "doctrine/dbal" }, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify("Cant install doctrine/dbal\n\r" .. response:prettyErrors(), vim.log.levels.ERROR) + else + vim.notify("Installation completed", vim.log.levels.INFO) + end + end + ) + else + vim.notify("Already Installed", vim.log.levels.INFO) + end +end + +return M diff --git a/lua/laravel/recipes/ide-helper.lua b/lua/laravel/recipes/ide-helper.lua new file mode 100644 index 0000000..96dc6d2 --- /dev/null +++ b/lua/laravel/recipes/ide-helper.lua @@ -0,0 +1,45 @@ +local api = require "laravel.api" + +local M = {} + +local function writeModels() + api.async( + "artisan", + { "ide-helper:models", "-n", "-W", "-M" }, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify(response:prettyErrors(), vim.log.levels.ERROR) + else + vim.notify("Ide Helper Models Complete", vim.log.levels.INFO) + end + end + ) +end + +local function installIdeHelperAndWrite() + api.async( + "composer", + { "require", "--dev", "barryvdh/laravel-ide-helper" }, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify("Cant install ide-helper\n\r" .. response:prettyErrors(), vim.log.levels.ERROR) + else + require("laravel.commands").list = {} + writeModels() + end + end + ) +end + +function M.run() + if not api.is_composer_package_install "barryvdh/laravel-ide-helper" then + writeModels() + return + end + + installIdeHelperAndWrite() +end + +return M diff --git a/lua/laravel/recipes/init.lua b/lua/laravel/recipes/init.lua new file mode 100644 index 0000000..1ac1280 --- /dev/null +++ b/lua/laravel/recipes/init.lua @@ -0,0 +1,23 @@ +local recipes = { + ["ide-helper"] = require "laravel.recipes.ide-helper", + ["doctrine-dbal"] = require "laravel.recipes.doctrine-dbal", +} + +local M = {} + +function M.run() + vim.ui.select(vim.tbl_keys(recipes), { prompt = "Recipe to run:" }, function(recipeName) + if not recipeName then + return + end + + local recipe = recipes[recipeName] + if recipe == nil then + error(string.format("Recipe %s not found", recipeName), vim.log.levels.ERROR) + end + + recipe.run() + end) +end + +return M diff --git a/lua/laravel/resources.lua b/lua/laravel/resources.lua deleted file mode 100644 index ba645bb..0000000 --- a/lua/laravel/resources.lua +++ /dev/null @@ -1,65 +0,0 @@ -local log = require("laravel.dev").log -local utils = require "laravel.utils" -local application = require "laravel.application" - -local M = {} - ----Opens the resource ----@param resource string ----@param name string -M.open = function(resource, name) - local directory = application.get_options().resources[resource] - local filename = "" - if type(directory) == "function" then - local err - filename, err = directory(name) - if err ~= nil then - log.error("resource.open(): Error getting the name", err) - return - end - filename = filename[1] - elseif type(directory) == "string" then - filename = string.format("%s/%s.php", directory, name) - end - - if vim.fn.findfile(filename) then - local uri = vim.uri_from_fname(string.format("%s/%s", vim.fn.getcwd(), filename)) - local buffer = vim.uri_to_bufnr(uri) - vim.api.nvim_win_set_buf(0, buffer) - - return - end - - utils.notify("resources.open", { - msg = string.format("Can't find resource %s", filename), - level = "INFO", - }) -end - ---- Identifies if the given command is a resource ----@param name string ----@return boolean -M.is_resource = function(name) - return application.get_options().resources[name] ~= nil -end - ---- Creates the resource and opens the file ----@param cmd table -M.create = function(cmd) - if not M.is_resource(cmd[1]) then - log.error("resource.create(): Invalid command", cmd) - return - end - - local resource = cmd[1] - local name = cmd[2] - - application.run("artisan", cmd, { - runner = "async", - callback = function() - M.open(resource, name) - end, - }) -end - -return M diff --git a/lua/laravel/resources/create.lua b/lua/laravel/resources/create.lua new file mode 100644 index 0000000..d48f92d --- /dev/null +++ b/lua/laravel/resources/create.lua @@ -0,0 +1,27 @@ +local is_resource = require "laravel.resources.is_resource" +local open = require "laravel.resources.open" +local api = require "laravel.api" + +return function(command) + local resource = command[1] + local name = command[2] + + if not is_resource(resource) then + vim.notify(string.format("Command %s is not a resource creation suported", resource), vim.log.levels.ERROR) + + return + end + + api.async( + "artisan", + command, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify(response:prettyErrors(), vim.log.levels.ERROR) + else + open(resource, name) + end + end + ) +end diff --git a/lua/laravel/resources/is_resource.lua b/lua/laravel/resources/is_resource.lua new file mode 100644 index 0000000..db81908 --- /dev/null +++ b/lua/laravel/resources/is_resource.lua @@ -0,0 +1,5 @@ +local config = require "laravel.config" + +return function(resource) + return config.options.resources[resource] ~= nil +end diff --git a/lua/laravel/resources/open.lua b/lua/laravel/resources/open.lua new file mode 100644 index 0000000..82829a1 --- /dev/null +++ b/lua/laravel/resources/open.lua @@ -0,0 +1,21 @@ +local config = require "laravel.config" + +return function(resource, name) + local directory = config.options.resources[resource] + local filename = "" + if type(directory) == "function" then + filename = directory(name) + elseif type(directory) == "string" then + filename = string.format("%s/%s.php", directory, name) + end + + if vim.fn.findfile(filename) then + local uri = vim.uri_from_fname(string.format("%s/%s", vim.fn.getcwd(), filename)) + local buffer = vim.uri_to_bufnr(uri) + vim.api.nvim_win_set_buf(0, buffer) + + return + end + + vim.notify(string.format("Can't find resource %s", filename), vim.log.levels.INFO) +end diff --git a/lua/laravel/route_info.lua b/lua/laravel/route_info/init.lua similarity index 84% rename from lua/laravel/route_info.lua rename to lua/laravel/route_info/init.lua index 8228485..38bcb9a 100644 --- a/lua/laravel/route_info.lua +++ b/lua/laravel/route_info/init.lua @@ -1,29 +1,8 @@ -local utils = require "laravel.utils" -local laravel_routes = require "laravel.routes" -local application = require "laravel.application" +local routes = require "laravel.routes" +local config = require "laravel.config" local get_node_text = vim.treesitter.get_node_text - -local options = application.get_options().route_info - --- TODO: remove once 0.9 was general available -if vim.fn.has "nvim-0.9.0" ~= 1 then - vim.treesitter.query.get = vim.treesitter.get_query - vim.treesitter.query.set = vim.treesitter.set_query -end - -vim.treesitter.query.set( - "php", - "laravel_route_info", - [[ - (namespace_definition (namespace_name) @namespace) - (class_declaration (name) @class) - (method_declaration - (visibility_modifier) @visibility - (name) @method - ) - ]] -) +local options = config.options.features.route_info local function is_same_class(action, class) return string.sub(action, 1, string.len(class)) == class @@ -72,19 +51,33 @@ local function set_route_to_methods(event) vim.api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) vim.diagnostic.reset(namespace, bufnr) - local routes = laravel_routes.list() - if routes == nil then - return + if #routes.list == 0 then + routes.load() end local php_parser = vim.treesitter.get_parser(bufnr, "php") local tree = php_parser:parse()[1] if tree == nil then - utils.notify("route_info.set_route_to_methods", { msg = "Could not retrive syntax tree", level = "WARN" }) - return + error("Could not retrieve syntx tree", vim.log.levels.ERROR) end local query = vim.treesitter.query.get("php", "laravel_route_info") + if query == nil then + vim.treesitter.query.set( + "php", + "laravel_route_info", + [[ + (namespace_definition (namespace_name) @namespace) + (class_declaration (name) @class) + (method_declaration + (visibility_modifier) @visibility + (name) @method + ) + ]] + ) + + query = vim.treesitter.query.get("php", "laravel_route_info") + end local class, class_namespace, methods, visibilities = "", "", {}, {} local class_pos = 0 @@ -119,7 +112,7 @@ local function set_route_to_methods(event) end local errors = {} - for _, route in pairs(routes) do + for _, route in pairs(routes.list) do local found = false for _, method in pairs(class_methods) do local action_full = route.action @@ -153,12 +146,14 @@ end local group = vim.api.nvim_create_augroup("laravel.route_info", {}) -local register = function() +local M = {} + +function M.setup() vim.api.nvim_create_autocmd({ "BufWritePost" }, { pattern = { "routes/*.php" }, group = group, callback = function() - laravel_routes.load() + routes.load() end, }) @@ -169,6 +164,4 @@ local register = function() }) end -return { - register = register, -} +return M diff --git a/lua/laravel/routes.lua b/lua/laravel/routes.lua deleted file mode 100644 index 23b7701..0000000 --- a/lua/laravel/routes.lua +++ /dev/null @@ -1,88 +0,0 @@ -local log = require("laravel.dev").log -local application = require "laravel.application" -local laravel_route = require "laravel.route" -local utils = require "laravel.utils" -local lsp = require "laravel._lsp" - -local container_key = "artisan_routes" - ----@param route LaravelRoute -local go_to = function(route) - if route.action == "Closure" then - if vim.tbl_contains(route.middlewares, "api") then - vim.cmd "edit routes/api.php" - vim.fn.search(route.uri:gsub("api", "") .. "") - elseif vim.tbl_contains(route.middlewares, "web") then - vim.cmd "edit routes/web.php" - if route.uri == "/" then - vim.fn.search "['\"]/['\"]" - else - vim.fn.search("/" .. route.uri) - end - else - utils.notify("Route", { msg = "Could not open the route location", level = "WARN" }) - return - end - - vim.cmd "normal zt" - return - end - - local action = vim.fn.split(route.action, "@") - lsp.go_to(action[1], action[2]) -end - -return { - load = function() - application.run("artisan", { "route:list", "--json" }, { - runner = "async", - callback = function(j, exit_code) - if exit_code == 1 then - application.container.unset(container_key) - return - end - local routes = laravel_route.from_json(j:result()) - if #routes > 0 then - application.container.set(container_key, routes) - end - end, - }) - end, - clean = function() - application.container.unset(container_key) - end, - list = function() - if not application.ready() then - utils.notify( - "Route List", - { level = "ERROR", msg = "The application is not ready for current working directory" } - ) - return nil - end - - local routes = application.container.get(container_key) - if routes then - return routes - end - - local result, ok = application.run("artisan", { "route:list", "--json" }, { runner = "sync" }) - if not ok then - return nil - end - - if result.exit_code == 1 then - log.error("app.routes(): stdout", result.out) - log.error("app.routes(): stderr", result.err) - return nil - end - - routes = laravel_route.from_json(result.out) - - if #routes > 0 then - application.container.set(container_key, routes) - end - - return routes - end, - go_to = go_to, -} diff --git a/lua/laravel/routes/go.lua b/lua/laravel/routes/go.lua new file mode 100644 index 0000000..18975bd --- /dev/null +++ b/lua/laravel/routes/go.lua @@ -0,0 +1,26 @@ +local lsp = require "laravel._lsp" + +return function(route) + if route.action == "Closure" then + if vim.tbl_contains(route.middlewares, "api") then + vim.cmd "edit routes/api.php" + vim.fn.search(route.uri:gsub("api", "") .. "") + elseif vim.tbl_contains(route.middlewares, "web") then + vim.cmd "edit routes/web.php" + if route.uri == "/" then + vim.fn.search "['\"]/['\"]" + else + vim.fn.search("/" .. route.uri) + end + else + vim.notify("Could not open the route location", vim.log.levels.WARN) + return + end + + vim.cmd "normal zt" + return + end + + local action = vim.fn.split(route.action, "@") + lsp.go_to(action[1], action[2]) +end diff --git a/lua/laravel/routes/init.lua b/lua/laravel/routes/init.lua new file mode 100644 index 0000000..2348854 --- /dev/null +++ b/lua/laravel/routes/init.lua @@ -0,0 +1,20 @@ +local utils = require "laravel.routes.utils" +local api = require "laravel.api" + +local M = {} + +M.list = {} + +function M.load() + M.list = {} + local result = api.sync("artisan", { "route:list", "--json" }) + if result:failed() then + error(result:errors(), vim.log.levels.ERROR) + end + + M.list = utils.from_json(result.stdout) + + return true +end + +return M diff --git a/lua/laravel/route.lua b/lua/laravel/routes/utils.lua similarity index 68% rename from lua/laravel/route.lua rename to lua/laravel/routes/utils.lua index bbaba91..6c6eb53 100644 --- a/lua/laravel/route.lua +++ b/lua/laravel/routes/utils.lua @@ -1,11 +1,3 @@ ----@class LaravelRoute ----@field uri string ----@field action string ----@field domain string|nil ----@field methods string[] ----@field middlewares string[] ----@field name string|nil - local M = {} local function check_nil(value) @@ -15,9 +7,6 @@ local function check_nil(value) return value end ---- Gets list of routes from the raw json ----@param json string ----@return LaravelRoute[] M.from_json = function(json) local routes = {} if json == "" or json == nil or #json == 0 then diff --git a/lua/laravel/run.lua b/lua/laravel/run.lua new file mode 100644 index 0000000..fd2ecb4 --- /dev/null +++ b/lua/laravel/run.lua @@ -0,0 +1,40 @@ +local config = require "laravel.config" +local environment = require "laravel.environment" +local history = require "laravel.history" +local Popup = require "nui.popup" +local Split = require "nui.split" + +local ui_builders = { + split = Split, + popup = Popup, +} + +---@param name string +---@param args string[] +---@param opts table|nil +return function(name, args, opts) + opts = opts or {} + local executable = environment.get_executable(name) + if not executable then + error(string.format("Executable %s not found", name), vim.log.levels.ERROR) + return + end + local cmd = vim.fn.extend(executable, args) + + local command_option = config.options.commands_options[args[1]] or {} + + opts = vim.tbl_extend("force", command_option, opts) + + local selected_ui = opts.ui or config.options.ui.default + + local instance = ui_builders[selected_ui](opts.nui_opts or config.options.ui.nui_opts[selected_ui]) + + instance:mount() + + -- This returns thhe job id + local jobId = vim.fn.termopen(table.concat(cmd, " ")) + + history.add(jobId, name, args, opts) + + vim.cmd "startinsert" +end diff --git a/lua/laravel/runners/async.lua b/lua/laravel/runners/async.lua deleted file mode 100644 index 97784eb..0000000 --- a/lua/laravel/runners/async.lua +++ /dev/null @@ -1,38 +0,0 @@ -local Job = require "plenary.job" -local utils = require "laravel.utils" - ---- Runs and returns the command inmediately ----@param cmd table ----@param opts table ----@return table, boolean -return function(cmd, opts) - opts = opts or {} - if type(cmd) ~= "table" then - utils.notify("runner.async", { - msg = "cmd has to be a table", - level = "ERROR", - }) - return { err = { "cmd is not a table" } }, false - end - - if type(opts.callback) ~= "function" then - utils.notify("runner.async", { - msg = "callback not pass", - level = "ERROR", - }) - return { err = { "callback is not a function" } }, false - end - - local command = table.remove(cmd, 1) - local stderr = {} - Job:new({ - command = command, - args = cmd, - on_exit = vim.schedule_wrap(opts.callback), - on_stderr = function(_, data) - table.insert(stderr, data) - end, - }):start() - - return {}, true -end diff --git a/lua/laravel/runners/buffer.lua b/lua/laravel/runners/buffer.lua deleted file mode 100644 index 4de383e..0000000 --- a/lua/laravel/runners/buffer.lua +++ /dev/null @@ -1,131 +0,0 @@ -local Split = require "nui.split" -local utils = require "laravel.utils" - ----@param cmd table ----@return string -local function sanetize_cmd(cmd) - -- this is to escape namespaces - for index, value in ipairs(cmd) do - cmd[index] = value:gsub("\\", "\\\\") - end - - return vim.fn.join(cmd, " ") -end - -local function splitStrings(strings) - local result = {} - - for _, str in ipairs(strings) do - local parts = {} - local stop - local start = 1 - if str == "" then - table.insert(result, "") - else - while start <= #str do - local index = string.find(str, "\r", start + 1) - - if index then - stop = index - table.insert(parts, string.sub(str, start, stop)) - start = stop + 1 - else - table.insert(parts, string.sub(str, start)) - break - end - end - - for _, part in ipairs(parts) do - table.insert(result, part) - end - end - end - - return result -end - ---- Runs in a buffers as a job ----@param cmd table ----@param opts table ----@return table, boolean -return function(cmd, opts) - local options = require("laravel.application").get_options() - local default = { - open = true, - focus = true, - buf_name = nil, - split = { - relative = options.split.relative, - position = options.split.position, - size = options.split.size, - enter = options.split.enter, - }, - } - - opts = vim.tbl_deep_extend("force", default, opts or {}) - - local job_id = 0 - - local bufnr = vim.api.nvim_create_buf(opts.listed or false, true) - - if opts.buf_name then - if vim.fn.bufexists(opts.buf_name) == 1 then - utils.notify("Buffer Run", { - msg = string.format("Buffer with the name `%s` already exists", opts.buf_name), - level = "ERROR", - }) - return {}, false - end - vim.api.nvim_buf_set_name(bufnr, opts.buf_name) - end - - local channel_id = vim.api.nvim_open_term(bufnr, { - on_input = function(_, _, _, data) - vim.api.nvim_chan_send(job_id, data) - end, - }) - - local function handle_output(_, data) - local lines = splitStrings(data) - vim.fn.chansend(channel_id, lines) - end - - if opts.open then - if opts.focus then - opts.split.enter = true - end - - local split = Split(opts.split) - - -- mount/open the component - split:mount() - - vim.api.nvim_win_set_buf(split.winid, bufnr) - - vim.api.nvim_win_call(split.winid, function() - -- vim.cmd "stopinsert" - vim.cmd "startinsert" - end) - end - - job_id = vim.fn.jobstart(sanetize_cmd(cmd), { - stdeout_buffered = true, - on_stdout = handle_output, - on_exit = function(id) - require("laravel._jobs").unregister(id) - vim.fn.chanclose(channel_id) - if opts.on_exit ~= nil then - opts.on_exit() - end - end, - pty = true, - width = options.split.width, - }) - - require("laravel._jobs").register(job_id, bufnr) - - return { - job = job_id, - buff = bufnr, - }, true -end diff --git a/lua/laravel/runners/init.lua b/lua/laravel/runners/init.lua deleted file mode 100644 index a290378..0000000 --- a/lua/laravel/runners/init.lua +++ /dev/null @@ -1,17 +0,0 @@ -local buffer = require "laravel.runners.buffer" -local sync = require "laravel.runners.sync" -local async = require "laravel.runners.async" -local persist = require "laravel.runners.persist" -local watch = require "laravel.runners.watch" -local terminal = require "laravel.runners.terminal" - -local runners = { - buffer = buffer, - sync = sync, - async = async, - persist = persist, - watch = watch, - terminal = terminal, -} - -return runners diff --git a/lua/laravel/runners/persist.lua b/lua/laravel/runners/persist.lua deleted file mode 100644 index 73beffe..0000000 --- a/lua/laravel/runners/persist.lua +++ /dev/null @@ -1,13 +0,0 @@ -local buffer = require "laravel.runners.buffer" - ---- Runs in a buffers as a job ----@param cmd table ----@param opts table|nil ----@return table, boolean -return function(cmd, opts) - opts = opts or {} - opts.listed = true - opts.buf_name = vim.fn.join(cmd, " ") - - return buffer(cmd, opts) -end diff --git a/lua/laravel/runners/sync.lua b/lua/laravel/runners/sync.lua deleted file mode 100644 index b97efbd..0000000 --- a/lua/laravel/runners/sync.lua +++ /dev/null @@ -1,35 +0,0 @@ -local Job = require "plenary.job" -local utils = require "laravel.utils" - ---- Runs and returns the command immediately ----@param cmd table ----@return table, boolean -return function(cmd) - if type(cmd) ~= "table" then - utils.notify("runners.sync", { - msg = "cmd has to be a table", - level = "ERROR", - }) - return { - out = {}, - exit_code = 1, - err = { "cmd is not a table" }, - }, false - end - - local command = table.remove(cmd, 1) - local stderr = {} - local stdout, ret = Job:new({ - command = command, - args = cmd, - on_stderr = function(_, data) - table.insert(stderr, data) - end, - }):sync() - - return { - out = stdout, - exit_code = ret, - err = stderr, - }, true -end diff --git a/lua/laravel/runners/terminal.lua b/lua/laravel/runners/terminal.lua deleted file mode 100644 index c71b8c9..0000000 --- a/lua/laravel/runners/terminal.lua +++ /dev/null @@ -1,32 +0,0 @@ ---- Runs in a new terminal and can operate in the terminal ----@param cmd table ----@param opts table ----@return table, boolean -return function(cmd, opts) - local options = require("laravel.application").get_options() - local default = { - focus = true, - split = { - cmd = options.split.cmd, - }, - } - - local cur_window = vim.api.nvim_get_current_win() - - opts = vim.tbl_deep_extend("force", default, opts or {}) - vim.cmd(string.format("vertical new term://%s", table.concat(cmd, " "))) - vim.cmd "startinsert" - - local buff = vim.api.nvim_get_current_buf() - local term_id = vim.b.terminal_job_id - - if not opts.focus then - vim.api.nvim_set_current_win(cur_window) - vim.cmd "stopinsert" - end - - return { - buff = buff, - term_id = term_id, - }, true -end diff --git a/lua/laravel/runners/watch.lua b/lua/laravel/runners/watch.lua deleted file mode 100644 index 915fec3..0000000 --- a/lua/laravel/runners/watch.lua +++ /dev/null @@ -1,78 +0,0 @@ ----@param cmd table ----@param opts table ----@return table, boolean -return function(cmd, opts) - opts = opts or {} - local options = require("laravel.application").get_options() - local default = { - open = true, - split = { - cmd = options.split.cmd, - width = options.split.width, - }, - } - - opts = vim.tbl_deep_extend("force", default, opts or {}) - - local pattern = opts.pattern or { "*.php" } - local buf_name = opts.buf_name or ("[Watched] " .. vim.fn.join(cmd, " ")) - - local bufnr = vim.api.nvim_create_buf(true, true) - vim.api.nvim_buf_set_name(bufnr, buf_name) - - if opts.open then - vim.cmd "vertical new" - local new_window = vim.api.nvim_get_current_win() - vim.api.nvim_win_set_width(new_window, 85) - vim.api.nvim_win_set_buf(new_window, bufnr) - end - - local run = function() - local windows = vim.fn.win_findbuf(bufnr) - local new_window = windows[1] - - local channel_id = vim.api.nvim_open_term(bufnr, {}) - if new_window ~= nil then - vim.api.nvim_win_set_cursor(new_window, { 1, 0 }) - end - - vim.fn.jobstart(vim.fn.join(cmd, " "), { - stdeout_buffered = true, - on_stdout = function(_, data) - vim.fn.chansend(channel_id, data) - end, - on_exit = function() - if new_window ~= nil then - local row = vim.api.nvim_buf_line_count(bufnr) - vim.api.nvim_win_set_cursor(new_window, { row, 0 }) - end - end, - pty = true, - width = options.split.width, - }) - end - - run() - - local group = vim.api.nvim_create_augroup("laravel.watch", {}) - - local au_cmd_id = vim.api.nvim_create_autocmd({ "BufWritePost" }, { - pattern = pattern, - group = group, - callback = function() - run() - end, - }) - - vim.api.nvim_create_autocmd({ "BufDelete" }, { - buffer = bufnr, - group = group, - callback = function() - vim.api.nvim_del_autocmd(au_cmd_id) - end, - }) - - return { - buff = bufnr, - }, true -end diff --git a/lua/laravel/status.lua b/lua/laravel/status.lua new file mode 100644 index 0000000..baee57a --- /dev/null +++ b/lua/laravel/status.lua @@ -0,0 +1,78 @@ +local environment = require "laravel.environment" +local api = require "laravel.api" + +local last_check = nil + +local M = {} + +local frequency = 120 + +local values = { + php = nil, + laravel = nil, +} + +local function get_values() + if last_check and (last_check + frequency > os.time()) then + return + end + if environment.get_executable "php" then + api.async( + "php", + { "-v" }, + ---@param response ApiResponse + function(response) + if response:successful() then + values.php = response:first():match "PHP ([%d%.]+)" + end + end + ) + end + if environment.get_executable "artisan" then + api.async( + "artisan", + { "--version" }, + ---@param response ApiResponse + function(response) + if response:successful() then + values.laravel = response:first():match "Laravel Framework ([%d%.]+)" + end + end + ) + end + last_check = os.time() +end + +local properties = { + php = { + has = function() + return environment.get_executable "php" ~= nil + end, + get = function() + return values.php + end, + }, + laravel = { + has = function() + return environment.get_executable "artisan" ~= nil + end, + get = function() + return values.laravel + end, + }, +} + +function M.get(property) + get_values() + return properties[property].get() +end + +function M.has(property) + return properties[property].has() +end + +function M.refresh() + last_check = nil +end + +return M diff --git a/lua/laravel/telescope/actions.lua b/lua/laravel/telescope/actions.lua new file mode 100644 index 0000000..72711a3 --- /dev/null +++ b/lua/laravel/telescope/actions.lua @@ -0,0 +1,53 @@ +local actions = require "telescope.actions" +local action_state = require "telescope.actions.state" +local ui_run = require "laravel.telescope.ui_run" +local go = require "laravel.routes.go" +local run = require "laravel.run" +local lsp = require "laravel._lsp" + +local M = {} + +function M.run(prompt_bufnr) + actions.close(prompt_bufnr) + local entry = action_state.get_selected_entry() + local command = entry.value + + vim.schedule(function() + ui_run(command) + end) +end + +function M.run_asking_options(prompt_bufnr) + actions.close(prompt_bufnr) + local entry = action_state.get_selected_entry() + local command = entry.value + + vim.schedule(function() + ui_run(command, true) + end) +end + +function M.open_route(prompt_bufnr) + actions.close(prompt_bufnr) + local entry = action_state.get_selected_entry() + vim.schedule(function() + go(entry.value) + end) +end + +function M.re_run_command(prompt_bufnr) + actions.close(prompt_bufnr) + local entry = action_state.get_selected_entry() + run(entry.value.name, entry.value.args, entry.value.opts) +end + +function M.open_relation(prompt_bufnr) + actions.close(prompt_bufnr) + local entry = action_state.get_selected_entry() + vim.schedule(function() + local action = vim.fn.split(entry.value.class, "@") + lsp.go_to(action[1], action[2]) + end) +end + +return M diff --git a/lua/laravel/telescope/make_entry.lua b/lua/laravel/telescope/make_entry.lua index ea87556..8cd4ddd 100644 --- a/lua/laravel/telescope/make_entry.lua +++ b/lua/laravel/telescope/make_entry.lua @@ -1,6 +1,6 @@ local entry_display = require "telescope.pickers.entry_display" -local make_entry = {} +local M = {} local handle_entry_index = function(opts, t, k) local override = ((opts or {}).entry_index or {})[k] @@ -15,7 +15,7 @@ local handle_entry_index = function(opts, t, k) return val end -make_entry.set_default_entry_mt = function(tbl, opts) +M.set_default_entry_mt = function(tbl, opts) return setmetatable({}, { __index = function(t, k) local override = handle_entry_index(opts, t, k) @@ -34,7 +34,7 @@ make_entry.set_default_entry_mt = function(tbl, opts) }) end -function make_entry.gen_from_laravel_routes(opts) +function M.gen_from_laravel_routes(opts) opts = opts or {} local displayer = entry_display.create { @@ -56,7 +56,7 @@ function make_entry.gen_from_laravel_routes(opts) end return function(route) - return make_entry.set_default_entry_mt({ + return M.set_default_entry_mt({ value = route, ordinal = route.uri, display = make_display, @@ -65,7 +65,7 @@ function make_entry.gen_from_laravel_routes(opts) end end -function make_entry.gen_from_model_relations(opts) +function M.gen_from_model_relations(opts) opts = opts or {} local displayer = entry_display.create { @@ -89,7 +89,7 @@ function make_entry.gen_from_model_relations(opts) return function(relation) local class_parts = vim.split(relation.class, "\\") relation.class_name = class_parts[#class_parts] - return make_entry.set_default_entry_mt({ + return M.set_default_entry_mt({ value = relation, ordinal = relation.class_name, display = make_display, @@ -97,4 +97,4 @@ function make_entry.gen_from_model_relations(opts) end end -return make_entry +return M diff --git a/lua/laravel/telescope/pickers/commands.lua b/lua/laravel/telescope/pickers/commands.lua new file mode 100644 index 0000000..3f112ed --- /dev/null +++ b/lua/laravel/telescope/pickers/commands.lua @@ -0,0 +1,54 @@ +local conf = require("telescope.config").values +local finders = require "telescope.finders" +local pickers = require "telescope.pickers" +local previewers = require "telescope.previewers" +local preview = require "laravel.telescope.preview" +local commands = require "laravel.commands" +local actions = require "laravel.telescope.actions" + +return function(opts) + opts = opts or {} + + if #commands.list == 0 then + commands.load() + end + + pickers + .new(opts, { + prompt_title = "Artisan commands", + finder = finders.new_table { + results = commands.list, + entry_maker = function(command) + return { + value = command, + display = command.name, + ordinal = command.name, + } + end, + }, + previewer = previewers.new_buffer_previewer { + title = "Help", + get_buffer_by_name = function(_, entry) + return entry.value.name + end, + define_preview = function(self, entry) + local command_preview = preview.command(entry.value) + + vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, command_preview.lines) + + local hl = vim.api.nvim_create_namespace "laravel" + for _, value in pairs(command_preview.highlights) do + vim.api.nvim_buf_add_highlight(self.state.bufnr, hl, value[1], value[2], value[3], value[4]) + end + end, + }, + sorter = conf.file_sorter(), + attach_mappings = function(_, map) + map("i", "", actions.run) + map("i", "", actions.run_asking_options) + + return true + end, + }) + :find() +end diff --git a/lua/laravel/telescope/pickers/history.lua b/lua/laravel/telescope/pickers/history.lua new file mode 100644 index 0000000..5b65967 --- /dev/null +++ b/lua/laravel/telescope/pickers/history.lua @@ -0,0 +1,34 @@ +local conf = require("telescope.config").values +local finders = require "telescope.finders" +local history = require "laravel.history" +local pickers = require "telescope.pickers" +local actions = require "laravel.telescope.actions" + +return function(opts) + opts = opts or {} + + pickers + .new(opts, { + prompt_title = "Laravel Command History", + finder = finders.new_table { + results = history.all(), + entry_maker = function(history_entry) + return { + value = history_entry, + display = string.format("%s %s", history_entry.name, vim.fn.join(history_entry.args, " ")), + ordinal = string.format("%s %s", history_entry.name, vim.fn.join(history_entry.args, " ")), + } + end, + }, + previewer = false, + sorter = conf.prefilter_sorter { + sorter = conf.generic_sorter(opts or {}), + }, + attach_mappings = function(_, map) + map("i", "", actions.re_run_command) + + return true + end, + }) + :find() +end diff --git a/lua/laravel/telescope/pickers/related.lua b/lua/laravel/telescope/pickers/related.lua new file mode 100644 index 0000000..1d4044d --- /dev/null +++ b/lua/laravel/telescope/pickers/related.lua @@ -0,0 +1,116 @@ +local pickers = require "telescope.pickers" +local make_entry = require "laravel.telescope.make_entry" +local finders = require "telescope.finders" +local conf = require("telescope.config").values +local api = require "laravel.api" +local actions = require "laravel.telescope.actions" + +return function(opts) + opts = opts or {} + + local file_type = vim.bo.filetype + local lang = vim.treesitter.language.get_lang(file_type) + if lang ~= "php" then + return false + end + + if not api.is_composer_package_install "doctrine/dbal" then + error "doctrine dbal not install this picker depends on it" + end + + local get_model_class_name = function() + local query = vim.treesitter.query.parse( + lang, + [[ (namespace_definition name: (namespace_name) @namespace) + (class_declaration name: (name) @class) ]] + ) + local tree = vim.treesitter.get_parser():parse()[1]:root() + local bufNr = vim.fn.bufnr() + local class = "" + for id, node, _ in query:iter_captures(tree, bufNr, tree:start(), tree:end_()) do + if query.captures[id] == "class" then + class = class .. "\\" .. vim.treesitter.get_node_text(node, 0) + elseif query.captures[id] == "namespace" then + class = vim.treesitter.get_node_text(node, 0) .. class + end + end + return class + end + + local class = get_model_class_name() + if class ~= "" then + local result = api.sync("artisan", { "model:show", class, "--json" }) + if result:failed() then + error(result:errors(), vim.log.levels.ERROR) + end + + local model_info = vim.fn.json_decode(result.stdout[1]) + if model_info == nil then + error(string.format("'artisan model:show %s --json' response could not be decoded", class), vim.log.levels.ERROR) + end + + local build_relation = function(info, relation_type) + if next(info) == nil then + return nil + end + if relation_type == "observers" and info["observer"][2] ~= nil then + return { + class = info["observer"][2], + type = relation_type, + extra_information = info["event"], + } + elseif relation_type == "relations" then + return { + class = info["related"], + type = relation_type, + extra_information = info["type"] .. " " .. info["name"], + } + elseif relation_type == "policy" then + return { + class = info[1], + type = relation_type, + extra_information = "", + } + end + return nil + end + + local relations = {} + local types = { "observers", "relations", "policy" } + for _, relation_type in ipairs(types) do + if model_info[relation_type] ~= vim.NIL and #model_info[relation_type] > 0 then + if type(model_info[relation_type]) == "table" and model_info[relation_type][1] ~= vim.NIL then + for _, info in ipairs(model_info[relation_type]) do + local relation = build_relation(info, relation_type) + if relation ~= nil then + table.insert(relations, relation) + end + end + else + local relation = build_relation({ model_info[relation_type] }, relation_type) + if relation ~= nil then + table.insert(relations, relation) + end + end + end + end + + pickers + .new(opts, { + prompt_title = "Related Files", + finder = finders.new_table { + results = relations, + entry_maker = make_entry.gen_from_model_relations(opts), + }, + sorter = conf.prefilter_sorter { + sorter = conf.generic_sorter(opts or {}), + }, + attach_mappings = function(_, map) + map("i", "", actions.open_relation) + + return true + end, + }) + :find() + end +end diff --git a/lua/laravel/telescope/pickers/routes.lua b/lua/laravel/telescope/pickers/routes.lua new file mode 100644 index 0000000..e7ac901 --- /dev/null +++ b/lua/laravel/telescope/pickers/routes.lua @@ -0,0 +1,53 @@ +local conf = require("telescope.config").values +local finders = require "telescope.finders" +local pickers = require "telescope.pickers" +local previewers = require "telescope.previewers" +local preview = require "laravel.telescope.preview" +local make_entry = require "laravel.telescope.make_entry" +local routes = require "laravel.routes" +local actions = require "laravel.telescope.actions" + +return function(opts) + opts = opts or {} + + if vim.tbl_isempty(routes.list) then + if not routes.load() then + return + end + end + + pickers + .new(opts, { + prompt_title = "Artisan Routes", + finder = finders.new_table { + results = routes.list, + entry_maker = opts.entry_maker or make_entry.gen_from_laravel_routes(opts), + }, + previewer = previewers.new_buffer_previewer { + title = "Help", + get_buffer_by_name = function(_, entry) + return entry.value.name + end, + define_preview = function(self, entry) + local route_preview = preview.route(entry.value) + + vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, route_preview.lines) + + local hl = vim.api.nvim_create_namespace "laravel" + for _, value in pairs(route_preview.highlights) do + vim.api.nvim_buf_add_highlight(self.state.bufnr, hl, value[1], value[2], value[3], value[4]) + end + end, + }, + sorter = conf.prefilter_sorter { + tag = "route_method", + sorter = conf.generic_sorter(opts or {}), + }, + attach_mappings = function(_, map) + map("i", "", actions.open_route) + + return true + end, + }) + :find() +end diff --git a/lua/laravel/telescope/ui_run.lua b/lua/laravel/telescope/ui_run.lua new file mode 100644 index 0000000..f496b09 --- /dev/null +++ b/lua/laravel/telescope/ui_run.lua @@ -0,0 +1,94 @@ +local application_run = require "laravel.run" +local is_resource = require "laravel.resources.is_resource" +local create = require "laravel.resources.create" +local config = require "laravel.config" +local laravel_run = require "laravel.run" + +return function(command, ask_options) + local command_options = config.options.commands_options[command.name] or {} + local function build_prompt(argument) + local prompt = "Argument " .. argument.name .. " " + if argument.is_required then + prompt = prompt .. "" + else + prompt = prompt .. "" + end + + return prompt .. ":" + end + + local function get_arguments(args, callback, values) + if #args == 0 then + callback(values) + return + end + + vim.ui.input({ prompt = build_prompt(args[1]) }, function(value) + -- esc value is nil + if value == nil and args[1].is_required then + return + end + -- enter value is "" + if value == "" and args[1].is_required then + print(vim.inspect(command)) + laravel_run("artisan", { command.name }, { ui = "popup" }) + return + end + + table.insert(values, value) + table.remove(args, 1) + get_arguments(args, callback, values) + end) + end + + local function run(args, options) + local cmd = { command.name } + for _, arg in pairs(args) do + table.insert(cmd, arg) + end + + if options ~= nil and options ~= "" then + if type(options) == "string" then + for _, value in pairs(vim.fn.split(options, " ")) do + table.insert(cmd, value) + end + elseif type(options) == "table" then + cmd = vim.fn.extend(cmd, options) + end + end + + if is_resource(cmd[1]) then + return create(cmd) + end + + application_run("artisan", cmd, {}) + end + + local args = {} + for _, argument in pairs(command.definition.arguments) do + table.insert(args, argument) + end + + if command_options.skip_args then + if ask_options then + vim.ui.input({ prompt = "Options" }, function(options) + run({}, options) + end) + return + end + run({}, nil) + return + end + + get_arguments(args, function(values) + if ask_options then + vim.ui.input({ prompt = "Options" }, function(options) + run(values, options) + end) + return + end + run(values, command_options.options) + end, {}) + + return true +end diff --git a/lua/laravel/tinker.lua b/lua/laravel/tinker.lua deleted file mode 100644 index 1a18523..0000000 --- a/lua/laravel/tinker.lua +++ /dev/null @@ -1,25 +0,0 @@ -local utils = require "laravel.utils" -local application = require "laravel.application" - -local function trim(s) - return s:match "^%s*(.-)%s*$" -end - -local send_to_tinker = function() - local lines = utils.get_visual_selection() - if nil == application.container.get "tinker" then - application.run("artisan", { "tinker" }, { focus = false }) - if nil == application.container.get "tinker" then - utils.notify("Send To Tinker", { msg = "Tinker terminal id not found and could create it", level = "ERROR" }) - return - end - end - - for _, line in ipairs(lines) do - vim.api.nvim_chan_send(application.container.get "tinker", trim(line) .. "\n") - end -end - -return { - send_to_tinker = send_to_tinker, -} diff --git a/lua/laravel/tinker/get_selection.lua b/lua/laravel/tinker/get_selection.lua new file mode 100644 index 0000000..945ae13 --- /dev/null +++ b/lua/laravel/tinker/get_selection.lua @@ -0,0 +1,20 @@ +local function get_vsel() + local bufnr = vim.api.nvim_win_get_buf(0) + local start = vim.fn.getpos "v" -- [bufnum, lnum, col, off] + local _end = vim.fn.getpos "." -- [bufnum, lnum, col, off] + if start[2] > _end[2] then + local x = _end + _end = start + start = x + end + return { + bufnr = bufnr, + mode = vim.fn.mode(), + pos = { start[2], start[3], _end[2], _end[3] }, + } +end + +return function() + local sel = get_vsel() + return vim.api.nvim_buf_get_lines(sel.bufnr, sel.pos[1] - 1, sel.pos[3], false) +end diff --git a/lua/laravel/tinker/init.lua b/lua/laravel/tinker/init.lua new file mode 100644 index 0000000..6489dba --- /dev/null +++ b/lua/laravel/tinker/init.lua @@ -0,0 +1,26 @@ +local run = require "laravel.run" +local get_selection = require "laravel.tinker.get_selection" + +local function trim(s) + return s:match "^%s*(.-)%s*$" +end + +local M = {} + +M.current_terminal = nil + +function M.send_to_tinker() + local lines = get_selection() + if nil == M.current_terminal then + run("artisan", { "tinker" }, { focus = false }) + if nil == M.current_terminal then + error("Tinker terminal id not found and could create it", vim.log.levels.ERROR) + end + end + + for _, line in ipairs(lines) do + vim.api.nvim_chan_send(M.current_terminal, trim(line) .. "\n") + end +end + +return M diff --git a/lua/laravel/user-commands/composer.lua b/lua/laravel/user-commands/composer.lua deleted file mode 100644 index 3c5b905..0000000 --- a/lua/laravel/user-commands/composer.lua +++ /dev/null @@ -1,56 +0,0 @@ -local utils = require "laravel.utils" -local application = require "laravel.application" - -local commands = { - update = function(cmd) - table.insert(cmd, 1, "update") - application.run("composer", cmd, {}) - end, - - install = function() - application.run("composer", { "install" }, {}) - end, - - ---@param cmd table - require = function(cmd) - table.insert(cmd, 1, "require") - application.run("composer", cmd, {}) - end, - - remove = function(cmd) - if #cmd == 0 then - utils.notify("composer.remove", { msg = "Need arguement for composer remove", level = "ERROR" }) - return - end - table.insert(cmd, 1, "remove") - application.run("composer", cmd, {}) - end, - - ["dump-autoload"] = function() - application.run("composer", { "dump-autoload" }, { - runner = "async", - callback = function() - utils.notify("composer.dump-autoload", { msg = "Completed", level = "INFO" }) - end, - }) - end, -} - -return { - setup = function() - vim.api.nvim_create_user_command("Composer", function(args) - local command = args.fargs[1] - if commands[command] ~= nil then - table.remove(args.fargs, 1) - return commands[command](args.fargs) - end - - return application.run("composer", args.fargs, {}) - end, { - nargs = "+", - complete = function() - return vim.tbl_keys(commands) - end, - }) - end, -} diff --git a/lua/laravel/user-commands/docker_compose.lua b/lua/laravel/user-commands/docker_compose.lua deleted file mode 100644 index 120ba5c..0000000 --- a/lua/laravel/user-commands/docker_compose.lua +++ /dev/null @@ -1,93 +0,0 @@ -local log = require("laravel.dev").log -local utils = require "laravel.utils" -local application = require "laravel.application" - -local commands = { - up = function() - application.run("compose", { "up", "-d" }, { - runner = "async", - callback = function(j, exit_code) - if exit_code ~= 0 then - log.error("compose.up(): stdout", j:result()) - log.error("compose.up(): stderr", j:result()) - utils.notify("compose.up", { msg = "Error on Compose up", level = "ERROR" }) - - return - end - utils.notify("compose.up", { msg = "Completed", level = "INFO" }) - end, - }) - end, - - ps = function() - application.run("compose", { "ps" }, { - runner = "async", - callback = function(j, exit_code) - if exit_code ~= 0 then - log.error("compose.ps(): stdout", j:result()) - log.error("compose.ps(): stderr", j:result()) - utils.notify("compose.ps", { msg = "Failed to run compose up", level = "ERROR" }) - - return - end - utils.notify("compose.ps", { raw = j:result(), level = "INFO" }) - end, - }) - end, - - restart = function() - application.run("compose", { "restart" }, { - runner = "async", - callback = function(j, exit_code) - if exit_code ~= 0 then - log.error("compose.restart(): stdout", j:result()) - log.error("compose.restart(): stderr", j:result()) - utils.notify("compose.restart", { msg = "Failed to restart compose", level = "ERROR" }) - - return - end - utils.notify("compose.restart", { msg = "compose restart complete", level = "INFO" }) - end, - }) - utils.notify("compose.restart", { msg = "compose restart starting", level = "INFO" }) - end, - - down = function() - application.run("compose", { "down" }, { - runner = "async", - callback = function(j, exit_code) - if exit_code ~= 0 then - log.error("compose.down(): stdout", j:result()) - log.error("compose.down(): stderr", j:result()) - utils.notify("compose.down", { msg = "Failed to down compose", level = "ERROR" }) - - return - end - utils.notify("compose.down", { msg = "compose Down complete", level = "INFO" }) - end, - }) - end, -} - -return { - setup = function() - if not application.has_command "compose" then - return - end - - vim.api.nvim_create_user_command("DockerCompose", function(args) - local command = args.fargs[1] - if commands[command] ~= nil then - table.remove(args.fargs, 1) - return commands[command](unpack(args.fargs)) - end - - application.run("compose", args.fargs, {}) - end, { - nargs = "+", - complete = function() - return vim.tbl_keys(commands) - end, - }) - end, -} diff --git a/lua/laravel/user-commands/laravel.lua b/lua/laravel/user-commands/laravel.lua deleted file mode 100644 index b59fd89..0000000 --- a/lua/laravel/user-commands/laravel.lua +++ /dev/null @@ -1,40 +0,0 @@ -local utils = require "laravel.utils" -local application = require "laravel.application" - -local commands = { - ["cache:clean"] = function() - application.container.purge() - utils.notify("laravel.cache:clean", { msg = "Cache cleaned", level = "INFO" }) - end, - ["routes"] = function() - return require("telescope").extensions.laravel.routes() - end, - ["artisan"] = function() - return require("telescope").extensions.laravel.commands() - end, - ["test:watch"] = function() - return application.run("artisan", { "test" }, { runner = "watch" }) - end, - ["related"] = function() - return require("telescope").extensions.laravel.related() - end, -} - -return { - setup = function() - vim.api.nvim_create_user_command("Laravel", function(args) - local command = args.fargs[1] - if commands[command] ~= nil then - table.remove(args.fargs, 1) - return commands[command](unpack(args.fargs)) - end - - utils.notify("laravel", { msg = "Unkown command", level = "ERROR" }) - end, { - nargs = "+", - complete = function() - return vim.tbl_keys(commands) - end, - }) - end, -} diff --git a/lua/laravel/user-commands/laravel_info.lua b/lua/laravel/user-commands/laravel_info.lua deleted file mode 100644 index 79e3385..0000000 --- a/lua/laravel/user-commands/laravel_info.lua +++ /dev/null @@ -1,34 +0,0 @@ -local application = require "laravel.application" -local Popup = require "nui.popup" -local event = require("nui.utils.autocmd").event - -return { - setup = function() - vim.api.nvim_create_user_command("LaravelInfo", function() - -- TODO: show information about the current state of the plugin - - local popup = Popup { - enter = true, - focusable = true, - border = { - style = "rounded", - }, - position = "50%", - size = { - width = "80%", - height = "60%", - }, - } - - -- mount/open the component - popup:mount() - - -- unmount component when cursor leaves buffer - popup:on(event.BufLeave, function() - popup:unmount() - end) - - vim.api.nvim_buf_set_lines(popup.bufnr, 0, 1, false, vim.fn.split(vim.inspect(application.get_info()), "\n")) - end, {}) - end, -} diff --git a/lua/laravel/user-commands/npm.lua b/lua/laravel/user-commands/npm.lua deleted file mode 100644 index 7c895bf..0000000 --- a/lua/laravel/user-commands/npm.lua +++ /dev/null @@ -1,29 +0,0 @@ -local application = require "laravel.application" - -local commands = { - dev = function() - return application.run("npm", { "run", "dev" }, { runner = "persist" }) - end, - build = function() - return application.run("npm", { "run", "build" }, {}) - end, -} - -return { - setup = function() - vim.api.nvim_create_user_command("Npm", function(args) - local command = args.fargs[1] - if commands[command] ~= nil then - table.remove(args.fargs, 1) - return commands[command](unpack(args.fargs)) - end - - return application.run("npm", args.fargs, {}) - end, { - nargs = "+", - complete = function() - return vim.tbl_keys(commands) - end, - }) - end, -} diff --git a/lua/laravel/user-commands/sail.lua b/lua/laravel/user-commands/sail.lua deleted file mode 100644 index cf00db7..0000000 --- a/lua/laravel/user-commands/sail.lua +++ /dev/null @@ -1,96 +0,0 @@ -local log = require("laravel.dev").log -local utils = require "laravel.utils" -local application = require "laravel.application" - -local commands = { - up = function() - application.run("sail", { "up", "-d" }, { - runner = "async", - callback = function(j, exit_code) - if exit_code ~= 0 then - log.error("sail.up(): stdout", j:result()) - log.error("sail.up(): stderr", j:result()) - utils.notify("sail.up", { msg = "Error on Sail up", level = "ERROR" }) - - return - end - utils.notify("sail.up", { msg = "Completed", level = "INFO" }) - end, - }) - end, - shell = function() - application.run("sail", { "shell" }, {}) - end, - - ps = function() - application.run("sail", { "ps" }, { - runner = "async", - callback = function(j, exit_code) - if exit_code ~= 0 then - log.error("sail.ps(): stdout", j:result()) - log.error("sail.ps(): stderr", j:result()) - utils.notify("sail.ps", { msg = "Failed to run Sail up", level = "ERROR" }) - - return - end - utils.notify("sail.ps", { raw = vim.fn.join(j:result(), "\n"), level = "INFO" }) - end, - }) - end, - - restart = function() - application.run("sail", { "restart" }, { - runner = "async", - callback = function(j, exit_code) - if exit_code ~= 0 then - log.error("sail.restart(): stdout", j:result()) - log.error("sail.restart(): stderr", j:result()) - utils.notify("sail.restart", { msg = "Failed to restart Sail", level = "ERROR" }) - - return - end - utils.notify("sail.restart", { msg = "Sail restart complete", level = "INFO" }) - end, - }) - utils.notify("sail.restart", { msg = "Sail restart starting", level = "INFO" }) - end, - - down = function() - application.run("sail", { "down" }, { - runner = "async", - callback = function(j, exit_code) - if exit_code ~= 0 then - log.error("sail.down(): stdout", j:result()) - log.error("sail.down(): stderr", j:result()) - utils.notify("sail.down", { msg = "Failed to down Sail", level = "ERROR" }) - - return - end - utils.notify("sail.down", { msg = "Sail Down complete", level = "INFO" }) - end, - }) - end, -} - -return { - setup = function() - if not application.has_command "sail" then - return - end - - vim.api.nvim_create_user_command("Sail", function(args) - local command = args.fargs[1] - if commands[command] ~= nil then - table.remove(args.fargs, 1) - return commands[command](unpack(args.fargs)) - end - - return application.run("sail", args.fargs, {}) - end, { - nargs = "+", - complete = function() - return vim.tbl_keys(commands) - end, - }) - end, -} diff --git a/lua/laravel/user-commands/yarn.lua b/lua/laravel/user-commands/yarn.lua deleted file mode 100644 index ce06c4c..0000000 --- a/lua/laravel/user-commands/yarn.lua +++ /dev/null @@ -1,29 +0,0 @@ -local application = require "laravel.application" - -local commands = { - dev = function() - require("laravel.yarn").run({ "run", "dev" }, "persist") - end, - build = function() - require("laravel.yarn").run { "run", "build" } - end, -} - -return { - setup = function() - vim.api.nvim_create_user_command("Yarn", function(args) - local command = args.fargs[1] - if commands[command] ~= nil then - table.remove(args.fargs, 1) - return commands[command](unpack(args.fargs)) - end - - return application.run("yarn", args.fargs, {}) - end, { - nargs = "+", - complete = function() - return vim.tbl_keys(commands) - end, - }) - end, -} diff --git a/lua/laravel/user-commands/artisan.lua b/lua/laravel/user_commands/artisan.lua similarity index 62% rename from lua/laravel/user-commands/artisan.lua rename to lua/laravel/user_commands/artisan.lua index 9b0a437..25d22e0 100644 --- a/lua/laravel/user-commands/artisan.lua +++ b/lua/laravel/user_commands/artisan.lua @@ -1,5 +1,7 @@ +local is_resource = require "laravel.resources.is_resource" local laravel_commands = require "laravel.commands" -local application = require "laravel.application" +local resources_create = require "laravel.resources.create" +local run = require "laravel.run" local function get_artisan_auto_complete(current_match, full_command) -- avoid getting autocomplete for when parameter is expected @@ -7,7 +9,7 @@ local function get_artisan_auto_complete(current_match, full_command) return {} end local complete_list = {} - local commands = laravel_commands.list() + local commands = laravel_commands.list if not commands then return complete_list end @@ -25,19 +27,17 @@ return { setup = function() vim.api.nvim_create_user_command("Artisan", function(args) if args.args == "" then - if application.get_options().bind_telescope then - local ok, telescope = pcall(require, "telescope") - if ok then - return telescope.extensions.laravel.commands() - end + local ok, telescope = pcall(require, "telescope") + if ok then + return telescope.extensions.laravel.commands() end end - local resources = require "laravel.resources" - if resources.is_resource(args.fargs[1]) then - return resources.create(args.fargs) + + if is_resource(args.fargs[1]) then + return resources_create(args.fargs) end - return application.run("artisan", args.fargs, {}) + return run("artisan", args.fargs, {}) end, { nargs = "*", complete = get_artisan_auto_complete, diff --git a/lua/laravel/user_commands/bun.lua b/lua/laravel/user_commands/bun.lua new file mode 100644 index 0000000..cbd0f38 --- /dev/null +++ b/lua/laravel/user_commands/bun.lua @@ -0,0 +1,17 @@ +local run = require "laravel.run" +local create_user_command = require "laravel.user_commands.create_user_command" + +local M = {} + +function M.setup() + return create_user_command("Bun", "bun", { + dev = function() + run("bun", { "run", "dev" }, { runner = "persist" }) + end, + build = function() + run("bun", { "run", "build" }) + end, + }) +end + +return M diff --git a/lua/laravel/user_commands/composer.lua b/lua/laravel/user_commands/composer.lua new file mode 100644 index 0000000..11e38f8 --- /dev/null +++ b/lua/laravel/user_commands/composer.lua @@ -0,0 +1,41 @@ +local create_user_command = require "laravel.user_commands.create_user_command" +local run = require "laravel.run" +local api = require "laravel.api" + +local M = {} + +function M.setup() + return create_user_command("Composer", "composer", { + update = function(cmd) + table.insert(cmd, 1, "update") + run("composer", cmd, {}) + end, + + install = function(cmd) + table.insert(cmd, 1, "install") + run("composer", cmd, {}) + end, + + ---@param cmd table + require = function(cmd) + table.insert(cmd, 1, "require") + run("composer", cmd, {}) + end, + + remove = function(cmd) + if #cmd == 0 then + error("Needs argument for composer remove", vim.log.levels.ERROR) + end + table.insert(cmd, 1, "remove") + run("composer", cmd, {}) + end, + + ["dump-autoload"] = function() + api.async("composer", { "dump-autoload" }, function() + vim.notify("Composer Dump autoload Completed", vim.log.levels.INFO) + end) + end, + }) +end + +return M diff --git a/lua/laravel/user_commands/create_user_command.lua b/lua/laravel/user_commands/create_user_command.lua new file mode 100644 index 0000000..2d1ecb0 --- /dev/null +++ b/lua/laravel/user_commands/create_user_command.lua @@ -0,0 +1,44 @@ +local run = require "laravel.run" +local environment = require "laravel.environment" + +return function(name, executable, commands, opts) + if executable then + if not environment.get_executable(executable) then + return + end + end + + vim.api.nvim_create_user_command( + name, + function(args) + local command = args.fargs[1] + if commands[command] ~= nil then + table.remove(args.fargs, 1) + return commands[command](args.fargs) + end + + if not command then + vim.ui.select(vim.fn.sort(vim.tbl_keys(commands)), { prompt = name }, function(action) + if not action then + return + end + if commands[action] ~= nil then + commands[action]() + end + end) + + return + end + + if executable then + return run(executable, args.fargs, {}) + end + end, + vim.tbl_deep_extend("force", { + nargs = "*", + complete = function() + return vim.tbl_keys(commands) + end, + }, opts or {}) + ) +end diff --git a/lua/laravel/user_commands/docker_compose.lua b/lua/laravel/user_commands/docker_compose.lua new file mode 100644 index 0000000..110447e --- /dev/null +++ b/lua/laravel/user_commands/docker_compose.lua @@ -0,0 +1,73 @@ +local create_user_command = require "laravel.user_commands.create_user_command" +local api = require "laravel.api" +local status = require "laravel.status" + +local M = {} + +function M.setup() + create_user_command("DockerCompose", "compose", { + up = function() + api.async( + "compose", + { "up", "-d" }, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify(response:prettyErrors(), vim.log.levels.ERROR) + else + vim.notify("Compose Up Completed", vim.log.levels.INFO) + status.refresh() + end + end + ) + end, + + ps = function() + api.async( + "compose", + { "ps" }, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify(response:prettyErrors(), vim.log.levels.ERROR) + else + vim.notify(response:prettyContent(), vim.log.levels.INFO) + end + end + ) + end, + + restart = function() + api.async( + "compose", + { "restart" }, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify(response:prettyErrors(), vim.log.levels.ERROR) + else + vim.notify("Compose restart complete", vim.log.levels.INFO) + end + end + ) + vim.notify("Compose restart starting", vim.log.levels.INFO) + end, + + down = function() + api.async( + "compose", + { "down" }, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify(response:prettyErrors(), vim.log.levels.ERROR) + else + vim.notify("Compose Down complete", vim.log.levels.INFO) + end + end + ) + end, + }) +end + +return M diff --git a/lua/laravel/user-commands/init.lua b/lua/laravel/user_commands/init.lua similarity index 71% rename from lua/laravel/user-commands/init.lua rename to lua/laravel/user_commands/init.lua index c52b99f..c55439a 100644 --- a/lua/laravel/user-commands/init.lua +++ b/lua/laravel/user_commands/init.lua @@ -2,10 +2,10 @@ local M = {} local modules = { "artisan", + "bun", "composer", "docker_compose", "laravel", - "laravel_info", "npm", "sail", "yarn", @@ -13,7 +13,7 @@ local modules = { M.setup = function() for _, module in pairs(modules) do - require(string.format("laravel.user-commands.%s", module)).setup() + require(string.format("laravel.user_commands.%s", module)).setup() end end diff --git a/lua/laravel/user_commands/laravel.lua b/lua/laravel/user_commands/laravel.lua new file mode 100644 index 0000000..29ea360 --- /dev/null +++ b/lua/laravel/user_commands/laravel.lua @@ -0,0 +1,38 @@ +local create_user_command = require "laravel.user_commands.create_user_command" +local environment = require "laravel.environment" + +local commands = { + ["cache:clean"] = function() + require("laravel.commands").list = {} + require("laravel.routes").list = {} + vim.notify("Laravel plugin cache cleaned", vim.log.levels.INFO) + end, + ["routes"] = require("telescope").extensions.laravel.routes, + ["artisan"] = require("telescope").extensions.laravel.commands, + ["test:watch"] = function() + require "laravel.watch"("artisan", { "test" }) + end, + ["related"] = require("telescope").extensions.laravel.related, + ["history"] = require("telescope").extensions.laravel.history, + ["recipes"] = require("laravel.recipes").run, + ["view-finder"] = require("laravel.view_finder").auto, + ["health"] = function() + vim.cmd [[checkhealth laravel]] + end, +} + +return { + setup = function() + if environment.get_executable "sail" then + commands["sail"] = function() + vim.cmd [[Sail]] + end + end + if environment.get_executable "compose" then + commands["docker-compose"] = function() + vim.cmd [[DockerCompose]] + end + end + create_user_command("Laravel", nil, commands) + end, +} diff --git a/lua/laravel/user_commands/npm.lua b/lua/laravel/user_commands/npm.lua new file mode 100644 index 0000000..9afe7a6 --- /dev/null +++ b/lua/laravel/user_commands/npm.lua @@ -0,0 +1,17 @@ +local run = require "laravel.run" +local create_user_command = require "laravel.user_commands.create_user_command" + +local M = {} + +function M.setup() + return create_user_command("Npm", "npm", { + dev = function() + run("npm", { "run", "dev" }, { runner = "persist" }) + end, + build = function() + run("npm", { "run", "build" }) + end, + }) +end + +return M diff --git a/lua/laravel/user_commands/sail.lua b/lua/laravel/user_commands/sail.lua new file mode 100644 index 0000000..0f4e46d --- /dev/null +++ b/lua/laravel/user_commands/sail.lua @@ -0,0 +1,76 @@ +local run = require "laravel.run" +local create_user_command = require "laravel.user_commands.create_user_command" +local api = require "laravel.api" +local status = require "laravel.status" + +return { + setup = function() + create_user_command("Sail", "sail", { + up = function() + api.async( + "sail", + { "up", "-d" }, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify(response:prettyErrors(), vim.log.levels.ERROR) + else + vim.notify("Sail up completed", vim.log.levels.INFO) + status.refresh() + end + end + ) + end, + + shell = function() + run("sail", { "shell" }, {}) + end, + + ps = function() + api.async( + "sail", + { "ps" }, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify(response:prettyErrors(), vim.log.levels.ERROR) + else + vim.notify(response:prettyContent(), vim.log.levels.INFO) + end + end + ) + end, + + restart = function() + api.async( + "sail", + { "restart" }, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify(response:prettyErrors(), vim.log.levels.ERROR) + else + vim.notify("Sail restart complete", vim.log.levels.INFO) + end + end + ) + vim.notify("Sail restart starting", vim.log.levels.INFO) + end, + + down = function() + api.async( + "sail", + { "down" }, + ---@param response ApiResponse + function(response) + if response:failed() then + vim.notify(response:prettyErrors(), vim.log.levels.ERROR) + else + vim.notify("Sail Down complete", vim.log.levels.INFO) + end + end + ) + end, + }) + end, +} diff --git a/lua/laravel/user_commands/yarn.lua b/lua/laravel/user_commands/yarn.lua new file mode 100644 index 0000000..e154ef4 --- /dev/null +++ b/lua/laravel/user_commands/yarn.lua @@ -0,0 +1,17 @@ +local run = require "laravel.run" +local create_user_command = require "laravel.user_commands.create_user_command" + +local M = {} + +function M.setup() + return create_user_command("Yarn", "yarn", { + dev = function() + run("yarn", { "run", "dev" }, { runner = "persist" }) + end, + build = function() + run("yarn", { "run", "build" }) + end, + }) +end + +return M diff --git a/lua/laravel/utils.lua b/lua/laravel/utils.lua index 045f128..61632a1 100644 --- a/lua/laravel/utils.lua +++ b/lua/laravel/utils.lua @@ -1,48 +1,34 @@ -local utils = {} - ----@param funname string ----@param opts table -function utils.notify(funname, opts) - local level = vim.log.levels[opts.level] - if not level then - error("Invalid error level", 2) - end - local body = string.format("[laravel.%s]: %s", funname, opts.msg) - if opts.raw ~= nil then - body = opts.raw - end - vim.notify(body, level, { - title = "Laravel.nvim", - }) -end +local M = {} -utils.get_visual_selection = function() - local sel = utils.get_vsel() - return vim.api.nvim_buf_get_lines(sel.bufnr, sel.pos[1] - 1, sel.pos[3], false) -end +function M.runRipgrep(pattern) + -- Build the ripgrep command + local rg_command = string.format('rg --vimgrep "%s"', pattern) -utils.get_vsel = function() - local bufnr = vim.api.nvim_win_get_buf(0) - local start = vim.fn.getpos "v" -- [bufnum, lnum, col, off] - local _end = vim.fn.getpos "." -- [bufnum, lnum, col, off] - if start[2] > _end[2] then - local x = _end - _end = start - start = x + -- Run the command and capture the output + local result = vim.fn.systemlist(rg_command) + + -- Process the result + local matches = {} + for _, line in ipairs(result) do + local parts = vim.fn.split(line, ":", true) + local file = parts[1] + local line_number = parts[2] + local text = parts[3] + + table.insert(matches, { file = file, line_number = line_number, text = text }) end - return { - bufnr = bufnr, - mode = vim.fn.mode(), - pos = { start[2], start[3], _end[2], _end[3] }, - } + + return matches end -utils.get_env = function(var) +---@param var string +---@return string|nil +function M.get_env(var) local envVal - if vim.fn.exists "*DotenvGet" == 1 then - envVal = vim.fn.DotenvGet(var) + if vim.api.nvim_call_function("exists", { "*DotenvGet" }) == 1 then + envVal = vim.api.nvim_call_function("DotenvGet", { var }) else - envVal = vim.fn.eval("$" .. var) + envVal = vim.api.nvim_call_function("eval", { "$" .. var }) end if envVal == "" then @@ -52,4 +38,4 @@ utils.get_env = function(var) return envVal end -return utils +return M diff --git a/lua/laravel/view_finder.lua b/lua/laravel/view_finder.lua new file mode 100644 index 0000000..510b7bd --- /dev/null +++ b/lua/laravel/view_finder.lua @@ -0,0 +1,95 @@ +local views = require "laravel.views" +local utils = require "laravel.utils" + +local M = {} + +-- Should be use from blades to find where is being use +function M.go_to_usage() + local fname = vim.uri_to_fname(vim.uri_from_bufnr(vim.api.nvim_get_current_buf())) + local view = views.name_from_fname(fname) + + local matches = utils.runRipgrep(string.format("view\\(['\\\"]%s['\\\"]", view)) + + if #matches == 0 then + vim.notify("No usage of this view found", vim.log.levels.WARN) + elseif #matches == 1 then + vim.cmd("edit " .. matches[1].file) + else + vim.ui.select( + vim.fn.sort(vim.tbl_map(function(item) + return item.file + end, matches)), + { prompt = "File: " }, + function(value) + if not value then + return + end + vim.cmd("edit " .. value) + end + ) + end +end + +-- should be use in files to find the views +function M.go_to_definition() + local bufnr = vim.api.nvim_get_current_buf() + local php_parser = vim.treesitter.get_parser(bufnr, "php") + local tree = php_parser:parse()[1] + if tree == nil then + error("Could not retrive syntax tree", vim.log.levels.ERROR) + end + + local query = vim.treesitter.query.get("php", "laravel_views") + + if query == nil then + vim.treesitter.query.set( + "php", + "laravel_views", + [[ + (function_call_expression + (name) @function_name (#eq? @function_name "view") + (arguments (argument (string (string_value) @view))) + ) + ]] + ) + + query = vim.treesitter.query.get("php", "laravel_views") + end + if not query then + error("Could not get treesitter query", vim.log.levels.ERROR) + end + + local founds = {} + for id, node in query:iter_captures(tree:root(), bufnr, 0, -1) do + if query.captures[id] == "view" then + table.insert(founds, vim.treesitter.get_node_text(node, bufnr)) + end + end + + if #founds == 0 then + vim.notify("No usage of this view found", vim.log.levels.WARN) + return + end + + if #founds > 1 then + vim.ui.select(vim.fn.sort(founds), { prompt = "Which view:" }, function(selected) + if not selected then + return + end + views.open(selected) + end) + return + end + views.open(founds[1]) +end + +function M.auto() + local ft = vim.o.filetype + if ft == "php" then + return M.go_to_definition() + elseif ft == "blade" then + return M.go_to_usage() + end +end + +return M diff --git a/lua/laravel/views.lua b/lua/laravel/views.lua new file mode 100644 index 0000000..f4fe99d --- /dev/null +++ b/lua/laravel/views.lua @@ -0,0 +1,26 @@ +local run = require "laravel.run" +local paths = require "laravel.paths" + +local M = {} + +function M.open(view) + local views_directory = paths.resource_path "views" + + local file_path = string.format("%s/%s.blade.php", views_directory, string.gsub(view, "%.", "/")) + + if vim.fn.findfile(file_path) then + vim.cmd("edit " .. file_path) + return + end + -- It creates the view if does not exists and user want it + if vim.fn.confirm("View does not exists, Should create it?", "&Yes\n&No") == 1 then + run("artisan", { "make:view", view }) + end +end + +function M.name_from_fname(fname) + local views_directory = paths.resource_path "views" .. "/" + return fname:gsub(views_directory:gsub("-", "%%-"), ""):gsub("%.blade%.php", ""):gsub("/", ".") +end + +return M diff --git a/lua/laravel/watch.lua b/lua/laravel/watch.lua new file mode 100644 index 0000000..87c2015 --- /dev/null +++ b/lua/laravel/watch.lua @@ -0,0 +1,58 @@ +local config = require "laravel.config" +local environment = require "laravel.environment" +local Split = require "nui.split" +local event = require("nui.utils.autocmd").event + +---@param name string +---@param args string[] +---@param opts table|nil +return function(name, args, opts) + opts = opts or {} + local executable = environment.get_executable(name) + if not executable then + error(string.format("Executable %s not found", name), vim.log.levels.ERROR) + return + end + local cmd = vim.fn.extend(executable, args) + + local command_option = config.options.commands_options[args[1]] or {} + + opts = vim.tbl_extend("force", command_option, opts) + + local nui_opts = opts.nui_opts or config.options.ui.nui_opts.split + nui_opts.enter = false + local instance = Split(nui_opts) + + instance:mount() + + local bufnr = instance.bufnr + + local run = function() + local chan_id = vim.api.nvim_open_term(bufnr, {}) + vim.fn.jobstart(table.concat(cmd, " "), { + stdeout_buffered = true, + on_stdout = function(_, data) + vim.fn.chansend(chan_id, data) + local row = vim.api.nvim_buf_line_count(bufnr) + vim.api.nvim_win_set_cursor(instance.winid, { row, 0 }) + end, + pty = true, + }) + end + run() + + local group = vim.api.nvim_create_augroup("laravel.watch", {}) + + local au_cmd_id = vim.api.nvim_create_autocmd({ "BufWritePost" }, { + pattern = opts.pattern or { "*.php" }, + group = group, + callback = function() + run() + end, + }) + + instance:on(event.BufHidden, function() + vim.api.nvim_del_autocmd(au_cmd_id) + vim.notify("AutoCmd for watch deleted", vim.log.levels.INFO, {}) + end) +end diff --git a/lua/telescope/_extensions/laravel.lua b/lua/telescope/_extensions/laravel.lua index 6d65e3a..5d35353 100644 --- a/lua/telescope/_extensions/laravel.lua +++ b/lua/telescope/_extensions/laravel.lua @@ -1,231 +1,8 @@ -local make_related = require "telescope.pickers.related" -local telescope = require "telescope" -local actions = require "telescope.actions" -local action_state = require "telescope.actions.state" -local conf = require("telescope.config").values -local finders = require "telescope.finders" -local pickers = require "telescope.pickers" -local previewers = require "telescope.previewers" -local preview = require "laravel.telescope.preview" -local make_entry = require "laravel.telescope.make_entry" -local laravel_commands = require "laravel.commands" -local laravel_routes = require "laravel.routes" -local application = require "laravel.application" -local lsp = require "laravel._lsp" - ----@class ModelRelation ----@field class string ----@field type string ----@field extra_information string - ---- runs a command from telescope ----@param command LaravelCommand ----@param ask_options boolean | nil ----@param runner string | nil -local function run_command(command, ask_options, runner) - -- use ui.input - -- problem it uses callbacks and how to control the flow for multiple - -- problem everything needs to be done in the callback because it does not block the execution - -- it will feel like javascript callback hell - -- since will have to for reach argument do an internal loop and from that pass a callback and so far - - ---@param argument CommandArgument - ---@return string - local function build_prompt(argument) - local prompt = "Argument " .. argument.name .. " " - if argument.is_required then - prompt = prompt .. "" - else - prompt = prompt .. "" - end - - return prompt .. ":" - end - - local function get_arguments(args, callback, values) - if #args == 0 then - callback(values) - return - end - - vim.ui.input({ prompt = build_prompt(args[1]) }, function(value) - if value == "" and args[1].is_required then - return - end - table.insert(values, value) - table.remove(args, 1) - get_arguments(args, callback, values) - end) - end - - local function run(args, options) - local cmd = { command.name } - for _, arg in pairs(args) do - table.insert(cmd, arg) - end - - if options ~= nil and options ~= "" then - for _, value in pairs(vim.fn.split(options, " ")) do - table.insert(cmd, value) - end - end - - local resources = require "laravel.resources" - if resources.is_resource(cmd[1]) then - return resources.create(cmd) - end - - application.run("artisan", cmd, { runner = runner }) - end - - local args = {} - for _, argument in pairs(command.definition.arguments) do - table.insert(args, argument) - end - - get_arguments(args, function(values) - if ask_options then - vim.ui.input({ prompt = "Options" }, function(options) - run(values, options) - end) - return - end - run(values, nil) - end, {}) - - return true -end - -local commands = function(opts) - opts = opts or {} - - local commands = laravel_commands.list() - - if commands == nil then - return - end - - pickers - .new(opts, { - prompt_title = "Artisan commands", - finder = finders.new_table { - results = commands, - entry_maker = function(command) - return { - value = command, - display = command.name, - ordinal = command.name, - } - end, - }, - previewer = previewers.new_buffer_previewer { - title = "Help", - get_buffer_by_name = function(_, entry) - return entry.value.name - end, - define_preview = function(self, entry) - local command_preview = preview.command(entry.value) - - vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, command_preview.lines) - - local hl = vim.api.nvim_create_namespace "laravel" - for _, value in pairs(command_preview.highlights) do - vim.api.nvim_buf_add_highlight(self.state.bufnr, hl, value[1], value[2], value[3], value[4]) - end - end, - }, - sorter = conf.file_sorter(), - attach_mappings = function(_, map) - map("i", "", function(prompt_bufnr) - actions.close(prompt_bufnr) - local entry = action_state.get_selected_entry() - ---@type LaravelCommand command - local command = entry.value - - vim.schedule(function() - run_command(command) - end) - end) - map("i", "", function(prompt_bufnr) - actions.close(prompt_bufnr) - local entry = action_state.get_selected_entry() - ---@type LaravelCommand command - local command = entry.value - - vim.schedule(function() - run_command(command, true) - end) - end) - map("i", "", function(prompt_bufnr) - actions.close(prompt_bufnr) - local entry = action_state.get_selected_entry() - ---@type LaravelCommand command - local command = entry.value - - vim.schedule(function() - run_command(command) - end) - end) - return true - end, - }) - :find() -end - -local routes = function(opts) - opts = opts or {} - - local routes = laravel_routes.list() - if routes == nil then - return - end - - pickers - .new(opts, { - prompt_title = "Artisan Routes", - finder = finders.new_table { - results = routes, - entry_maker = opts.entry_maker or make_entry.gen_from_laravel_routes(opts), - }, - previewer = previewers.new_buffer_previewer { - title = "Help", - get_buffer_by_name = function(_, entry) - return entry.value.name - end, - define_preview = function(self, entry) - local route_preview = preview.route(entry.value) - - vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, route_preview.lines) - - local hl = vim.api.nvim_create_namespace "laravel" - for _, value in pairs(route_preview.highlights) do - vim.api.nvim_buf_add_highlight(self.state.bufnr, hl, value[1], value[2], value[3], value[4]) - end - end, - }, - sorter = conf.prefilter_sorter { - tag = "route_method", - sorter = conf.generic_sorter(opts or {}), - }, - attach_mappings = function(_, map) - map("i", "", function(prompt_bufnr) - actions.close(prompt_bufnr) - local entry = action_state.get_selected_entry() - vim.schedule(function() - laravel_routes.go_to(entry.value) - end) - end) - - return true - end, - }) - :find() -end - -return telescope.register_extension { +return require("telescope").register_extension { exports = { - commands = commands, - routes = routes, - related = make_related(pickers, application, lsp, make_entry, finders, conf, actions, action_state), + commands = require "laravel.telescope.pickers.commands", + routes = require "laravel.telescope.pickers.routes", + related = require "laravel.telescope.pickers.related", + history = require "laravel.telescope.pickers.history", }, } diff --git a/lua/telescope/pickers/related.lua b/lua/telescope/pickers/related.lua deleted file mode 100644 index 860578e..0000000 --- a/lua/telescope/pickers/related.lua +++ /dev/null @@ -1,134 +0,0 @@ -local make_related = function(pickers, application, lsp, make_entry, finders, conf, actions, action_state) - local utils = require "laravel.utils" - return function(opts) - opts = opts or {} - - local file_type = vim.bo.filetype - local lang = vim.treesitter.language.get_lang(file_type) - if lang ~= "php" then - return false - end - - local get_model_class_name = function() - local query = vim.treesitter.query.parse( - lang, - [[ (namespace_definition name: (namespace_name) @namespace) - (class_declaration name: (name) @class) ]] - ) - local tree = vim.treesitter.get_parser():parse()[1]:root() - local bufNr = vim.fn.bufnr() - local class = "" - for id, node, _ in query:iter_captures(tree, bufNr, tree:start(), tree:end_()) do - if query.captures[id] == "class" then - class = class .. "\\" .. vim.treesitter.get_node_text(node, 0) - elseif query.captures[id] == "namespace" then - class = vim.treesitter.get_node_text(node, 0) .. class - end - end - return class - end - - local class = get_model_class_name() - if class ~= "" then - local result, ok = application.run("artisan", { "model:show", class, "--json" }, { runner = "sync" }) - if not ok then - utils.notify( - "Artisan", - { msg = "'php artisan model:show " .. class .. " --json' command failed", level = "ERROR" } - ) - return nil - end - - if result.exit_code ~= 0 or string.sub(result.out[1], 1, 1) ~= "{" or string.sub(result.out[1], -1) ~= "}" then - utils.notify( - "Artisan", - { msg = "'php artisan model:show" .. class .. " --json' response could not be decoded", level = "ERROR" } - ) - return nil - end - - local model_info = vim.fn.json_decode(result.out[1]) - if model_info == nil then - utils.notify( - "Artisan", - { msg = "'php artisan model:show" .. class .. " --json' response could not be decoded", level = "ERROR" } - ) - return nil - end - - ---@return ModelRelation|nil - local build_relation = function(info, relation_type) - if next(info) == nil then - return nil - end - if relation_type == "observers" and info["observer"][2] ~= nil then - return { - class = info["observer"][2], - type = relation_type, - extra_information = info["event"], - } - elseif relation_type == "relations" then - return { - class = info["related"], - type = relation_type, - extra_information = info["type"] .. " " .. info["name"], - } - elseif relation_type == "policy" then - return { - class = info[1], - type = relation_type, - extra_information = "", - } - end - return nil - end - - local relations = {} - local types = { "observers", "relations", "policy" } - for _, relation_type in ipairs(types) do - if model_info[relation_type] ~= vim.NIL and #model_info[relation_type] > 0 then - if type(model_info[relation_type]) == "table" and model_info[relation_type][1] ~= vim.NIL then - for _, info in ipairs(model_info[relation_type]) do - local relation = build_relation(info, relation_type) - if relation ~= nil then - table.insert(relations, relation) - end - end - else - local relation = build_relation({ model_info[relation_type] }, relation_type) - if relation ~= nil then - table.insert(relations, relation) - end - end - end - end - - pickers - .new(opts, { - prompt_title = "Related Files", - finder = finders.new_table { - results = relations, - entry_maker = make_entry.gen_from_model_relations(opts), - }, - sorter = conf.prefilter_sorter { - sorter = conf.generic_sorter(opts or {}), - }, - attach_mappings = function(_, map) - map("i", "", function(prompt_bufnr) - actions.close(prompt_bufnr) - local entry = action_state.get_selected_entry() - vim.schedule(function() - local action = vim.fn.split(entry.value.class, "@") - lsp.go_to(action[1], action[2]) - end) - end) - - return true - end, - }) - :find() - end - end -end - -return make_related diff --git a/tests/command/artisan_spec.lua b/tests/command/artisan_spec.lua new file mode 100644 index 0000000..e13f743 --- /dev/null +++ b/tests/command/artisan_spec.lua @@ -0,0 +1,15 @@ +local spy = require "luassert.spy" + +describe("Artisan user command", function() + local s = spy.new(function() end) + package.loaded["laravel.run"] = s + package.loaded["laravel.resources.is_resource"] = function() + return false + end + require("laravel.user_commands.artisan").setup() + + it("require formats the parameter", function() + vim.cmd [[Artisan --version]] + assert.spy(s).was.called_with("artisan", { "--version" }, {}) + end) +end) diff --git a/tests/command/composer_spec.lua b/tests/command/composer_spec.lua new file mode 100644 index 0000000..3d6bf7f --- /dev/null +++ b/tests/command/composer_spec.lua @@ -0,0 +1,17 @@ +local spy = require "luassert.spy" + +describe("Composer user command", function() + local s = spy.new(function() end) + package.loaded["laravel.run"] = s + require("laravel.user_commands.composer").setup() + + it("require formats the parameter", function() + vim.cmd [[Composer require laravel/folio]] + assert.spy(s).was.called_with("composer", { "require", "laravel/folio" }, {}) + end) + + it("passes the arguments to the install", function() + vim.cmd [[Composer install --options]] + assert.spy(s).was.called_with("composer", { "install", "--options" }, {}) + end) +end) diff --git a/tests/environment/resolver_spec.lua b/tests/environment/resolver_spec.lua new file mode 100644 index 0000000..35d37b2 --- /dev/null +++ b/tests/environment/resolver_spec.lua @@ -0,0 +1,13 @@ +describe("Environment Resolver test", function() + before_each(function() end) + + it("resolve by the environment variable", function() + package.loaded["laravel.environment.get_env"] = function() + return "test-environment" + end + local resolver = require "laravel.environment.resolver" + local test_resolver = resolver(true, false, nil) + local environment = test_resolver { ["test-environment"] = "test-environment" } + assert.equals("test-environment", environment) + end) +end) diff --git a/tests/init.lua b/tests/init.lua new file mode 100644 index 0000000..599eb61 --- /dev/null +++ b/tests/init.lua @@ -0,0 +1,37 @@ +local M = {} + +function M.root(root) + local f = debug.getinfo(1, "S").source:sub(2) + return vim.fn.fnamemodify(f, ":p:h:h") .. "/" .. (root or "") +end + +---@param plugin string +function M.load(plugin) + local name = plugin:match ".*/(.*)" + local package_root = M.root ".tests/site/pack/deps/start/" + if not vim.loop.fs_stat(package_root .. name) then + print("Installing " .. plugin) + vim.fn.mkdir(package_root, "p") + vim.fn.system { + "git", + "clone", + "--depth=1", + "https://github.com/" .. plugin .. ".git", + package_root .. "/" .. name, + } + end +end + +function M.setup() + vim.cmd [[set runtimepath=$VIMRUNTIME]] + vim.opt.runtimepath:append(M.root()) + vim.opt.packpath = { M.root ".tests/site" } + M.load "nvim-lua/plenary.nvim" + M.load "MunifTanjim/nui.nvim" + vim.env.XDG_CONFIG_HOME = M.root ".tests/config" + vim.env.XDG_DATA_HOME = M.root ".tests/data" + vim.env.XDG_STATE_HOME = M.root ".tests/state" + vim.env.XDG_CACHE_HOME = M.root ".tests/cache" +end + +M.setup() diff --git a/tests/run b/tests/run new file mode 100755 index 0000000..412d7b5 --- /dev/null +++ b/tests/run @@ -0,0 +1,3 @@ +#!/bin/sh + +nvim --headless -u tests/init.lua -c "PlenaryBustedDirectory tests {minimal_init = 'tests//init.lua', sequential = true}"