diff --git a/README.md b/README.md index c28d8c0..98899b8 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ return { keys = { { "la", ":Laravel artisan" }, { "lr", ":Laravel routes" }, + { "lm", ":Laravel related" }, { "lt", function() @@ -192,6 +193,7 @@ You can run `shell` as tinker will open a new terminal `Laravel cache:clear` purge the cache clears the cache for commands. `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 diff --git a/lua/laravel/telescope/make_entry.lua b/lua/laravel/telescope/make_entry.lua index 24edf03..ea87556 100644 --- a/lua/laravel/telescope/make_entry.lua +++ b/lua/laravel/telescope/make_entry.lua @@ -65,4 +65,36 @@ function make_entry.gen_from_laravel_routes(opts) end end +function make_entry.gen_from_model_relations(opts) + opts = opts or {} + + local displayer = entry_display.create { + separator = " ", + hl_chars = { ["["] = "TelescopeBorder", ["]"] = "TelescopeBorder" }, + items = { + { width = 40 }, + { width = 20 }, + { remaining = true }, + }, + } + + local make_display = function(entry) + return displayer { + { entry.value.class_name, "TelescopeResultsConstant" }, + { entry.value.type, "TelescopeResultsIdentifier" }, + { entry.value.extra_information or "", "TelescopeResultsFunction" }, + } + end + + return function(relation) + local class_parts = vim.split(relation.class, "\\") + relation.class_name = class_parts[#class_parts] + return make_entry.set_default_entry_mt({ + value = relation, + ordinal = relation.class_name, + display = make_display, + }, opts) + end +end + return make_entry diff --git a/lua/laravel/user-commands/laravel.lua b/lua/laravel/user-commands/laravel.lua index 1592799..b59fd89 100644 --- a/lua/laravel/user-commands/laravel.lua +++ b/lua/laravel/user-commands/laravel.lua @@ -15,6 +15,9 @@ local commands = { ["test:watch"] = function() return application.run("artisan", { "test" }, { runner = "watch" }) end, + ["related"] = function() + return require("telescope").extensions.laravel.related() + end, } return { diff --git a/lua/telescope/_extensions/laravel.lua b/lua/telescope/_extensions/laravel.lua index eba1c46..846cb46 100644 --- a/lua/telescope/_extensions/laravel.lua +++ b/lua/telescope/_extensions/laravel.lua @@ -1,3 +1,4 @@ +local make_related = require "telescope.pickers.related" local telescope = require "telescope" local actions = require "telescope.actions" local action_state = require "telescope.actions.state" @@ -10,6 +11,12 @@ 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 @@ -217,5 +224,6 @@ return telescope.register_extension { exports = { commands = commands, routes = routes, + related = make_related(pickers, application, lsp, make_entry, finders, conf, actions, action_state), }, } diff --git a/lua/telescope/pickers/related.lua b/lua/telescope/pickers/related.lua new file mode 100644 index 0000000..860578e --- /dev/null +++ b/lua/telescope/pickers/related.lua @@ -0,0 +1,134 @@ +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