├── .luacheckrc ├── .stylua.toml ├── LICENSE ├── README.md ├── after └── plugin │ └── cmp-symfony-router.lua └── lua └── cmp-symfony-router ├── init.lua ├── types.lua └── util.lua /.luacheckrc: -------------------------------------------------------------------------------- 1 | read_globals = { 2 | "vim", 3 | } 4 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 120 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 4 5 | quote_style = "AutoPreferDouble" 6 | call_parentheses = "Always" 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Frederik Buchlak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmp-symfony-router 2 | 3 | [symfony routes](https://symfony.com/doc/current/routing.html#debugging-routes) source for [nvim-cmp](https://github.com/hrsh7th/nvim-cmp) 4 | 5 | ## Dependencies 6 | 7 | - [plenary.nvim](https://github.com/nvim-lua/plenary.nvim) is required 8 | 9 | ## Installation 10 | 11 | [lazy.nvim](https://github.com/folke/lazy.nvim) 12 | 13 | ```lua 14 | { 15 | "fbuchlak/cmp-symfony-router", 16 | dependencies = { "nvim-lua/plenary.nvim" } 17 | }, 18 | ``` 19 | 20 | ## Setup 21 | 22 | Add `symfony_router` as cmp source 23 | 24 | ```lua 25 | require("cmp").setup { 26 | sources = { 27 | { 28 | name = "symfony_router", 29 | -- these options are default, you don't need to include them in setup 30 | option = { 31 | console_command = { "php", "bin/console" }, -- see Configuration section 32 | cwd = nil, -- string|nil Defaults to vim.loop.cwd() 33 | cwd_files = { "composer.json", "bin/console" }, -- all these files must exist in cwd to trigger completion 34 | filetypes = { "php", "twig" }, 35 | } 36 | }, 37 | }, 38 | } 39 | ``` 40 | 41 | ## Configuration 42 | 43 | ### console_command 44 | 45 | Defines symfony console executable 46 | 47 | ```lua 48 | -- examples 49 | { "bin/console" } -- call console executable directly 50 | { "symfony", "console" } -- using symfony cli 51 | { "docker", "exec", "CONTAINER_NAME", "php", "bin/console" } -- docker 52 | { "docker", "compose", "exec", "SERVICE_NAME", "php", "bin/console" } -- docker compose 53 | ``` 54 | -------------------------------------------------------------------------------- /after/plugin/cmp-symfony-router.lua: -------------------------------------------------------------------------------- 1 | require("cmp").register_source("symfony_router", require("cmp-symfony-router").new()) 2 | -------------------------------------------------------------------------------- /lua/cmp-symfony-router/init.lua: -------------------------------------------------------------------------------- 1 | local source = {} 2 | 3 | local path_separator = vim.loop.os_uname().version:match("Windows") and "\\" or "/" 4 | 5 | local default_config = { 6 | console_command = { "php", "bin/console" }, 7 | cwd = nil, 8 | cwd_files = { "composer.json", "bin/console" }, 9 | filetypes = { "php", "twig" }, 10 | } 11 | 12 | function source.new() 13 | return setmetatable({}, { __index = source }) 14 | end 15 | 16 | function source.complete(_, params, callback) 17 | local opts = vim.tbl_extend("force", vim.deepcopy(default_config), vim.deepcopy(params.option or {})) 18 | vim.validate({ 19 | console_command = { opts.console_command, "table" }, 20 | cwd = { opts.cwd, "string", true }, 21 | cwd_files = { opts.cwd_files, "table" }, 22 | filetypes = { opts.filetypes, "table" }, 23 | }) 24 | 25 | if not vim.tbl_contains(opts.filetypes, vim.bo.filetype) then 26 | return callback({ items = {}, isIncomplete = false }) 27 | end 28 | 29 | local cwd = opts.cwd or vim.loop.cwd() 30 | for _, fname in ipairs(opts.cwd_files) do 31 | local fpath = cwd .. path_separator .. fname 32 | local ok, stat = pcall(vim.loop.fs_stat, fpath) 33 | if not ok or nil == stat then 34 | return callback({ items = {}, isIncomplete = false }) 35 | end 36 | end 37 | 38 | local command = table.remove(opts.console_command, 1) 39 | local args = opts.console_command 40 | vim.list_extend(args, { "debug:router", "--raw", "--no-interaction", "--format", "json" }) 41 | 42 | local stderr = false 43 | require("plenary.job") 44 | :new({ 45 | command = command, 46 | args = args, 47 | cwd = cwd, 48 | on_stderr = function() 49 | stderr = true 50 | end, 51 | on_exit = function(job) 52 | if stderr then 53 | return 54 | end 55 | 56 | local result = job:result() 57 | if "table" ~= type(result) then 58 | return 59 | end 60 | 61 | local ok, json = pcall(vim.json.decode, table.concat(result)) 62 | if not ok or "table" ~= type(json) then 63 | return 64 | end 65 | 66 | callback({ items = require("cmp-symfony-router.util").create_items(json), isIncomplete = false }) 67 | end, 68 | }) 69 | :start() 70 | end 71 | 72 | return source 73 | -------------------------------------------------------------------------------- /lua/cmp-symfony-router/types.lua: -------------------------------------------------------------------------------- 1 | ---@class cmp_symfony_router.Options 2 | ---@field console_command? string[] 3 | ---@field cwd? string 4 | ---@field cwd_files? string[] 5 | ---@field filetypes? string[] 6 | 7 | ---@class cmp_symfony_router.RouteDefaults: table 8 | ---@field _locale? string 9 | ---@field _canonical_route? string 10 | ---@field _controller? string 11 | 12 | ---@class cmp_symfony_router.RouteRequirements: table 13 | ---@field _locale? string 14 | 15 | ---@class cmp_symfony_router.RouteOptions 16 | ---@field compiler_class string 17 | ---@field utf8 boolean 18 | 19 | ---@class cmp_symfony_router.Route 20 | ---@field path string 21 | ---@field pathRegex string 22 | ---@field host string 23 | ---@field hostRegex string 24 | ---@field scheme string 25 | ---@field method string 26 | ---@field class string 27 | ---@field defaults cmp_symfony_router.RouteDefaults 28 | ---@field requirements cmp_symfony_router.RouteRequirements 29 | ---@field options cmp_symfony_router.RouteOptions 30 | 31 | ---@alias cmp_symfony_router.Routes table 32 | 33 | ---@class cmp_symfony_router.ResolvedRoute: cmp_symfony_router.Route 34 | ---@field _controller? string 35 | ---@field _paths string[] 36 | ---@field _methods string[] 37 | -------------------------------------------------------------------------------- /lua/cmp-symfony-router/util.lua: -------------------------------------------------------------------------------- 1 | local Util = {} 2 | local H = {} 3 | 4 | ---@param routes cmp_symfony_router.Routes 5 | function Util.create_items(routes) 6 | local resolved = {} 7 | for name, route in pairs(routes) do 8 | local route_name = route.defaults._canonical_route or name 9 | resolved[route_name] = resolved[route_name] or route 10 | 11 | resolved[route_name]._controller = route.defaults._controller 12 | resolved[route_name]._paths = resolved[route_name]._paths or {} 13 | if not vim.tbl_contains(resolved[route_name]._paths, route.path) then 14 | resolved[route_name]._paths[#resolved[route_name]._paths + 1] = route.path 15 | end 16 | 17 | resolved[route_name]._methods = resolved[route_name]._methods or {} 18 | for method in route.method:gmatch("%w+") do 19 | if not vim.tbl_contains(resolved[route_name]._methods, method) then 20 | resolved[route_name]._methods[#resolved[route_name]._methods + 1] = method 21 | end 22 | end 23 | end 24 | 25 | local items = {} 26 | for name, route in pairs(resolved) do 27 | items[#items + 1] = H.create_item(name, route) 28 | end 29 | 30 | return items 31 | end 32 | 33 | ---@param name string 34 | ---@param route cmp_symfony_router.ResolvedRoute 35 | function H.create_item(name, route) 36 | local documentation = { ("# %s"):format(name) } 37 | if route._controller ~= nil then 38 | vim.list_extend(documentation, { "", "## Controller", ("- `%s`"):format(route._controller) }) 39 | end 40 | vim.list_extend(documentation, H.create_description_item_values("## Paths", route._paths)) 41 | vim.list_extend(documentation, H.create_description_item_key_to_values("## Requirements", route.requirements)) 42 | route.defaults._canonical_route = nil 43 | route.defaults._controller = nil 44 | route.defaults._locale = nil 45 | vim.list_extend(documentation, H.create_description_item_key_to_values("## Defaults", route.defaults)) 46 | vim.list_extend(documentation, H.create_description_item_values("## Methods", route._methods)) 47 | 48 | return { 49 | label = name, 50 | documentation = { 51 | kind = "markdown", 52 | value = table.concat(documentation, "\n"), 53 | }, 54 | } 55 | end 56 | 57 | ---@param n string 58 | ---@param tbl table 59 | ---@return string[] 60 | function H.create_description_item_values(n, tbl) 61 | tbl = "table" == type(tbl) and tbl or {} 62 | local ret = {} 63 | for _, v in pairs(tbl) do 64 | ret[#ret + 1] = ("- `%s`"):format(v) 65 | end 66 | if 0 == #ret then 67 | return {} 68 | end 69 | return vim.tbl_flatten({ "", n, ret }) 70 | end 71 | 72 | ---@param n string 73 | ---@param tbl table 74 | ---@return string[] 75 | function H.create_description_item_key_to_values(n, tbl) 76 | tbl = "table" == type(tbl) and tbl or {} 77 | local ret = {} 78 | for k, v in pairs(tbl) do 79 | ret[#ret + 1] = ("- `%s`: `%s`"):format(k, v) 80 | end 81 | if 0 == #ret then 82 | return {} 83 | end 84 | return vim.tbl_flatten({ "", n, ret }) 85 | end 86 | 87 | return Util 88 | --------------------------------------------------------------------------------