adalessa / laravel.nvim

Plugin for Nvim to work with laravel projects.
MIT License
292 stars 18 forks source link

[Feature request] Model list and Model info #21

Closed naquad closed 2 weeks ago

naquad commented 1 year ago

It would be great to be able to see the model structure right from NeoVim.

Laravel has this ./artisan model:show <ModelName>, i.e.:

[21:27:50] (000) ~/p/m/m/sandbox ❯❯❯ ./artisan model:show Group

  App\Models\Group ..................................................................  
  Database .................................................................... pgsql  
  Table ...................................................................... groups  

  Attributes ............................................................ type / cast  
  id increments, unique ................................................ bigint / int  
  name unique, fillable ................................................. string(255)  
  description nullable, fillable ............................................... text  
  service fillable .......................................................... boolean  

  Relations .........................................................................  

  Observers .........................................................................

Seeing that in Telescope as a model list with a previewer showing colored model info (as it is in the console) would be awesome.

adalessa commented 1 year ago

@naquad thanks for the issue, I was thinking to add this on a floating window, but maybe the information depending on the model could be to much. I think like an split could work fine. Using the api this is simple here it is

vim.keymap.set('n', '<leader>lm', function ()
  local model = vim.fn.expand "%:t:r"
  require("laravel.application").run("artisan", { "model:show", model }, { runner = "terminal" })
end, {})

To explain it is just getting the name of the model from neovim using expand and just calling the api for the model show, and the terminal because the first time will ask for confirmation to install doctrine.

naquad commented 1 year ago

While its a quick cool solution for the single model w/o a nested namespace (e.g. App\Models\Domain\Entity), the list would be preferrable as it would allow to see the models when you're working in the controller or some file other than the model itself.

P. S. A small tweak to make the window stick:

    vim.keymap.set('n', '<leader>lm', function ()
      local winid = vim.api.nvim_get_current_win()
      local model = vim.fn.expand "%:t:r"
      require("laravel.application").run("artisan", { "model:show", model }, { runner = "terminal" })
      vim.api.nvim_set_current_win(winid)
      vim.cmd('stopinsert')
    end, {})
adalessa commented 1 year ago

Agree I think like a hover could work good. To do this it will be necessary to check the proper class under the cursor. validates in a way that is a model and calls the artisan command, format it and present it in a nice popup window. Do you agree with the idea of a hover ? or at least to be trigger from the class name under the cursor ?

Side not for the stop insert options focus = false and will do the same as set the current window

    vim.keymap.set('n', '<leader>lm', function ()
      local model = vim.fn.expand "%:t:r"
      require("laravel.application").run("artisan", { "model:show", model }, { runner = "terminal" , focus = false})
    end, {})
naquad commented 1 year ago

If I'm getting it right then the hover would be the word under the cursor and its not always working as expected. I've jammed a bit with Telescope's docs and Lua NeoVIM API to get what I want and here's some terrible Lua code: https://gist.github.com/naquad/fbf418884b59cfcfa88a97b07cdcc4b3

And the result is exactly what I want: image

Unfortunately, I've got somewhat stuck with runners and the structure of the plugin, so the only thing I can do at the moment is bring the code to the table if it helps somehow.

P. S. Kudos for the focus = false thing. Definitely better.

naquad commented 1 year ago

P. S. The floater is a great idea. I didn't get into the guts of vim.api.nvim_open_win() and didn't think about how to control it (close it? make it stick? move it around? where should it be open to don't interfere with the editing?) but it is definitely worth a shot.

adalessa commented 1 year ago

Yeah I am also learning more lua during the development process.

From your example can be extended. for the preview and integrate in a simpler way. I will explore it. The simplest way to get all the models is to define a folder, like you do app/models and look recursive there. But that will not work for other with directories as you mentioned.

So the solution needs to try to satisfy all cases or be possible to configure. I would say we can start with a function that takes a directory. and it can be configure in the keybinding level or by default use app/Models

dorkster100 commented 1 year ago

Default for Laravel was root app folder before, so it is a bit more tricky

adalessa commented 1 year ago

Yes, I have been playing around with this idea I found this package https://packagist.org/packages/haydenpierce/class-finder. With this is possible to quickly get all classes in a namespace and later check if they are Models, with the function is_subclass_of. This will require a companion package on the application, as a dev dependency. In other PR I also mentioned this idea, and is growing stronger.

naquad commented 1 year ago

Erm the autodiscovery thing seems nice, but it is a challenging start that stretches the automation a bit too far. Also, it may lead to strange effects. For example, I have a set of dummy models in tests/Unit used to test the abstract index and resource response generators and I definitely don't want to see them in the list of models.

Can we start with a configurable set of directories that has a default { "app/Models" } ? It'll be much simpler and more flexible as the user would be able to configure the plugin to his own needs.

adalessa commented 1 year ago

Yes maybe is a bit to much but interesting to develop, and it can just use the namespace defined in autoload not in autoload-dev so test will not be included. For your case It can be resolve with current tools, is just more of a gist. I will rework the one that I share with the gist that you share in order to help you

dorkster100 commented 1 year ago

Well, models should definitely not be taken outside app directory, but in general we should see when the default from app root to app/Models was made and decide from there if we want to support the root version, my projects are a bit older and still have models in root and root still contains some random classes as well

naquad commented 1 year ago

Btw I've made some updates to the gist: it is a Telescope extension now and uses your reference code for the split window, so now hitting <tab> in the picker opens the split with the model selected (not very efficient but it is too hard to steal the buffer from Telescope's term previewer). Additionally, there's a mapping that sets prompt default_text to the current word.

naquad commented 1 year ago

UPD: I've updated the gist to be more flexible. Now one can configure the locations and also provide a custom predicate for detecting the model file (by default it is just a PHP file with a valid class name).

I suggest abstracting out the model discovery, so the Laravel plugin would have 3 types of discovery:

  1. Gist becomes model_discovery_simple. It is file-based, and configurable (the locations argument).
  2. The one suggested by @adalessa based on the class-finder becomes something like model_discovery_composer (or sort of). It should have some arguments like which base class to inspect. A quick hackish solution. Some options are needed and also, maybe the plugin should run composer dump-autoload before running that thing.
  3. User-defined function.
naquad commented 1 year ago

UPD: implemented a simple PHP-based discovery, check out the gist

dorkster100 commented 1 year ago

thats pretty cool, i thought that we might be able to do simple PHP snippets from lua.

But if we start adding more things it will become ugly, adding a separate dev dependency could make it simpler, we could just expose your php snippet as php artisan model:list for instance.

naquad commented 1 year ago

@dorkster100 that requires a dependency => project changes + handling cases when it's not installed in the plugin. IMHO while it is that simple we can use it as is. Frankly, I don't think there should be a lot of support code, the bare minimum only.

When/If the support PHP code will become big enough it can be placed into a separate file nearby the extension and instead of php -r <code> it would be invoked as php <path to the file>.

adalessa commented 1 year ago

I have thought about that having php files in the plugin and run them, the problem that I think with that approach is that for container base system the files will run in a different version that the installed. and even if you want to run in the containers you need to mount them. I see the following pros and cons.

package: Pros: have better context. Can be extended to support different versions of the framework for older projects. Allows to other plugins or devs to implement more features outside of the plugin The plugin can enable the use base on the composer.json file Cons: Require an extra step of installing it per project Another project to maintain. For new versions need to keep the plugin and package updated.

Files: Pros: No extra steps of installation. easier to keep updated.

Cons: Possible mismatch of versions of php. Will have to support in code runtime multiples versions of language and framework.

Let me know what you think, and if there is something to add to the list.

naquad commented 1 year ago

Some 3 second research:

[10:19:21] (000) naquad@new-nq main ◼ ~/p/m/m/sandbox ❯❯❯ sail artisan tinker --help
... skip ...
      --execute[=EXECUTE]  Execute the given code using Tinker

The container problem is solved, kudos to Laravel for keeping us covered. Btw if artisan doesn't work in a container environment then whole your setup is dead and we can't do much about it. Local PHP is somewhat harder as it does require a way to specify the PHP binary path but that can be addressed using the opts and/or environment vars just like Laravel.nvim currently does.

While the code is super simple we're fine with multiple runtime versions. I've just checked that code with a dead project using PHP 7.2 and it works. Simplicity and the absence of dependencies are giving us some pretty good portability.

"The right way" (tm) with some custom deps needed by the plugin, complex code, and potentially more dependencies are show stoppers at least for me.

P. S. And if we're going with the files then stdin for the win:

[10:30:42] (000) naquad@new-nq main ◼ ~/p/m/m/sandbox ❯❯❯ echo 'echo "scripts can be passed via stdin";' | sail artisan tinker
scripts can be passed via stdin
dorkster100 commented 1 year ago

I agree that we don't need to overcomplicate things, if we for now only have a small snippet it is fine as is, later if it grows, we could probably go for a more elegant solution and I do believe that creating a package with a bundle of artisan commands is a more elegant and more composable approach. I also think it should be opt in, if the artisan commands are not present just disable part of the functionality.

For now I think we are fine as is.

adalessa commented 1 year ago

Agree that needs to be opt in to enhance the experience. I think that a developer should not have a problem to install an extra dev-package if that will provide a better experience. Adding to this is the feature for the tests, that having a command that can parse the tests (there is a command artisan test --list-tests) but the format list is not clear, so to have the plugin can improve that.

To add in the way to run it, if we do not include any laravel specific instead of running with tinker, it can just use php -r <code>.

adalessa commented 1 year ago

I have created a new issue to track this conversation of how to run php code, #29

adalessa commented 2 weeks ago

Closing This. To get the model list is possible using the resources picker, and the model info in visible with the new feature, and the relations can be seen by the related picker.