From 0cce7837f2725a6d21df63b14a60aa101df7e7da Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 8 Dec 2025 20:18:20 +0000 Subject: [PATCH] God of snippets --- lua/snippets/php.lua | 271 +++++++++++++----------------------- lua/snippets/snip_utils.lua | 131 +++++++++++++++++ 2 files changed, 226 insertions(+), 176 deletions(-) create mode 100644 lua/snippets/snip_utils.lua diff --git a/lua/snippets/php.lua b/lua/snippets/php.lua index 459edc8..0c680d4 100644 --- a/lua/snippets/php.lua +++ b/lua/snippets/php.lua @@ -9,100 +9,54 @@ local f = ls.function_node local d = ls.dynamic_node local fmt = require('luasnip.extras.fmt').fmt local rep = require('luasnip.extras').rep -local line_begin = require('luasnip.extras.conditions').line_begin local extend_decorator = require 'luasnip.util.extend_decorator' local fmta = extend_decorator.apply(fmt, { delimiters = '#~' }) -local function line_begin(line_to_cursor, matched_trigger) - -- +1 because `string.sub("abcd", 1, -2)` -> abc - return line_to_cursor:sub(1, -(#matched_trigger + 1)):match '^%s*$' -end - -local function empty_line(_, matched_trigger) - return vim.api.nvim_get_current_line():match('^%s*' .. vim.pesc(matched_trigger) .. '$') -end - -local function file_begin(line_to_cursor, matched_trigger) - local line_number = vim.fn.line '.' - return line_number == 1 and line_begin(line_to_cursor, matched_trigger) -end - -local function in_string() - local node_type = vim.treesitter.get_node():type() - return node_type == 'string_content' or node_type == 'string' -end - -local function in_comment() - local node_type = vim.treesitter.get_node():type() - return node_type == 'comment' -end - -local function not_in_string_or_comment() - return (not in_string()) and (not in_comment()) -end - -local function tr(trigger, description, condition) - condition = condition or empty_line - return { - trig = trigger, - desc = description, - snippetType = 'autosnippet', - condition = condition, - } -end - -local function ctr(trigger, description) - return tr(trigger, description, in_comment) -end -local function atr(trigger, description) - return { - trig = trigger, - desc = description, - snippetType = 'autosnippet', - regTrig = true, - wordTrig = false, - condition = not_in_string_or_comment, - } -end +local utils = require 'snippets.snip_utils' +local tr = utils.tr +local etr = utils.etr +local atr = utils.atr +local ctr = utils.ctr +local bs = utils.bs local function psr_namespace(_, snip) - -- local path = snip.env.TM_FILENAME_FULL or '' - -- -- Get the directory of the path - -- local dir = vim.fs.dirname(path) - -- -- Loop through parent directories to find composer.json - -- while dir ~= '/' and dir ~= nil do - -- local composer_json_path = dir .. '/composer.json' - -- if vim.fn.filereadable(composer_json_path) == 1 then - -- break - -- end - -- dir = vim.fs.dirname(dir) - -- end - -- -- If no composer.json found, return empty string - -- if dir == '/' or dir == nil then - -- return '' - -- end - -- - -- -- Decode composer.json and get PSR-4 autoload mappings - -- local composer = vim.json.decode(vim.iter(vim.fn.readfile(dir .. '/composer.json')):join '') - -- local psr4 = composer['autoload'] and composer['autoload']['psr-4'] - -- - -- -- If no PSR-4 mappings, return empty string - -- if not psr4 then - -- return '' - -- end - -- - -- -- Get the relative path from the composer.json directory - -- local relative_path = path:sub(#dir + 2) - -- -- Loop through PSR-4 mappings - -- for namespace, map in pairs(psr4) do - -- -- Check if the relative path matches the mapping - -- if relative_path:match('^' .. map:gsub('/', '%%/')) then - -- -- Extract the suffix of the path after the mapping, removing the filename - -- local suffix = relative_path:sub(#map + 2):match('^(.*)/[^/]+%.php$') or '' - -- local trimmed = namespace:gsub('\\$', '') - -- return trimmed .. (suffix ~= '' and ('\\' .. suffix:gsub('/', '\\')) or '') - -- end - -- end + local path = vim.fn.expand '%' + -- Get the directory of the path + local dir = vim.fs.dirname(path) + -- Loop through parent directories to find composer.json + while dir ~= '/' and dir ~= nil do + local composer_json_path = dir .. '/composer.json' + if vim.fn.filereadable(composer_json_path) == 1 then + break + end + dir = vim.fs.dirname(dir) + end + -- If no composer.json found, return empty string + if dir == '/' or dir == nil then + return '' + end + + -- Decode composer.json and get PSR-4 autoload mappings + local composer = vim.json.decode(vim.iter(vim.fn.readfile(dir .. '/composer.json')):join '') + local psr4 = composer['autoload'] and composer['autoload']['psr-4'] + + -- If no PSR-4 mappings, return empty string + if not psr4 then + return '' + end + + -- Get the relative path from the composer.json directory + local relative_path = path:sub(#dir + 2) + -- Loop through PSR-4 mappings + for namespace, map in pairs(psr4) do + -- Check if the relative path matches the mapping + if relative_path:match('^' .. map:gsub('/', '%%/')) then + -- Extract the suffix of the path after the mapping, removing the filename + local suffix = relative_path:sub(#map + 1):match '^(.*)/[^/]+%.php$' or '' + local trimmed = namespace:gsub('\\$', '') + return trimmed .. (suffix ~= '' and ('\\' .. suffix:gsub('/', '\\')) or '') + end + end end local function class_name(args, snip) @@ -110,52 +64,17 @@ local function class_name(args, snip) return filename:match '([^%.]+)' or 'ClassName' end -local function snippet_aliases(triggers, description, snippet) - local snips = {} - for key, trigger in ipairs(triggers) do - snips[key] = s(tr(trigger, description), snippet()) - end - return snips -end - -local function fmtc(snipp, nodes) - return fmta( - '#~' .. snipp, - vim.list_extend({ fn(function(args, snip) - return snip.captures[1] - end) }, nodes) - ) -end - -local function flatten(tbl) - local result = {} - local index = 1 - for _, val in ipairs(tbl) do - if type(val) == 'table' and vim.tbl_islist(val) then - for _, val2 in ipairs(val) do - result[index] = val2 - index = index + 1 - end - else - result[index] = val - index = index + 1 - end - end - - return result -end - -return flatten { +return { --------------- -- DEBUGGING -- --------------- - s(tr('du ', 'Dump a variable to the dump server'), fmta('dump(#~);', { i(0) })), - s(atr('([^%w])du ', 'Dump a variable to the dump server'), fmtc('dump(#~)', { i(0) })), - s(tr('r ', 'ray'), fmta('ray(#~);', { i(0) })), - s(atr('([^%w])r ', 'ray'), fmtc('ray(#~)', { i(0) })), - s(tr('dt ', 'Dump PHPStan type definition'), fmta('\\PhpStan\\dumpType(#~);', { i(0) })), + s(etr('du ', 'Dump a variable to the dump server'), fmta('dump(#~);', { i(0) })), + bs(atr('du ', 'Dump a variable to the dump server'), fmta('dump(#~)', { i(0) })), + s(etr('r ', 'ray'), fmta('ray(#~);', { i(0) })), + bs(atr('r ', 'ray'), fmta('ray(#~)', { i(0) })), + s(etr('dt ', 'Dump PHPStan type definition'), fmta('\\PhpStan\\dumpType(#~);', { i(0) })), s( - tr('ql ', 'Log all queries'), + etr('ql ', 'Log all queries'), fmta( [[ \Illuminate\Support\Facades\DB::listen(function (\Illuminate\Database\Events\QueryExecuted $e) { @@ -169,7 +88,7 @@ return flatten { -------------- -- COMMENTS -- -------------- - s(tr('/**', 'Docblock comment'), { + s(etr('/**', 'Docblock comment'), { c(1, { sn( nil, @@ -185,7 +104,7 @@ return flatten { sn(nil, fmta('/** #~ */', { i(1) })), }), }), - s(tr('@v', '@var docblock'), fmta('/** @var #~ $#~ */', { i(1), i(0) })), + s(etr('@v', '@var docblock'), fmta('/** @var #~ $#~ */', { i(1), i(0) })), s(ctr('@v', '@var docblock'), fmta('@var #~ $#~', { i(1), i(0) })), s(ctr('* @pr', 'Class property docblock'), fmta('* @property #~ $#~', { i(1), i(0) })), s(ctr('* @pb', 'Class boolean property docblock'), fmta('* @property bool $#~', { i(0) })), @@ -194,7 +113,7 @@ return flatten { s(ctr('* @pc', 'Class collection property docblock'), fmta('* @property \\Illuminate\\Database\\Eloquent\\Collection<#~> $#~', { i(1), i(0) })), s(ctr('* @pd', 'Class date property docblock'), fmta('* @property \\Illuminate\\Support\\Carbon $#~', { i(0) })), s( - tr('@pm', 'Model magic properties docblock'), + etr('@pm', 'Model magic properties docblock'), fmta( [[ /** @@ -214,7 +133,7 @@ return flatten { -- SYNTAX -- ------------ s( - tr('if ', 'if block'), + etr('if ', 'if block'), fmta( [[ if (#~) { @@ -225,7 +144,7 @@ return flatten { ) ), s( - tr('fe ', 'foreach block'), + etr('fe ', 'foreach block'), fmta( [[ foreach (#~ as #~) { @@ -235,9 +154,9 @@ return flatten { { i(1), i(2), i(0) } ) ), - s(tr('foreach', 'foreach block'), fmta('fe', {})), + s(etr('foreach', 'foreach block'), fmta('fe', {})), s( - tr('for ', 'for block'), + etr('for ', 'for block'), fmta( [[ for (#~; #~; #~) { @@ -247,16 +166,16 @@ return flatten { { i(1, '$i'), i(2), i(3, '$i++'), i(0) } ) ), - s(tr('return ', 'Add semicolon after return'), fmta('return #~;', { i(0) })), + s(etr('return ', 'Add semicolon after return'), fmta('return #~;', { i(0) })), s(atr(' use ', 'Add use to function'), fmta(' use (#~)', { i(0) })), - s(tr('rt ', 'return alias'), fmta('return #~;', { i(0) })), - s( - atr('([^%w])fn ', 'Shorthand function block'), + s(etr('rt ', 'return alias'), fmta('return #~;', { i(0) })), + bs( + atr('fn ', 'Shorthand function block'), c(0, { - sn(nil, fmtc('fn (#~) => #~', { i(1), i(2) })), + sn(nil, fmta('fn (#~) => #~', { i(1), i(2) })), sn( nil, - fmtc( + fmta( [[ function (#~) { #~ @@ -267,12 +186,12 @@ return flatten { ), }) ), - s( - atr('([^%w])fun ', 'Shorthand function block'), + bs( + atr('fun ', 'Shorthand function block'), c(0, { sn( nil, - fmtc( + fmta( [[ function (#~) { #~ @@ -283,7 +202,7 @@ return flatten { ), sn( nil, - fmtc( + fmta( [[ function (#~) use (#~) { #~ @@ -295,7 +214,7 @@ return flatten { }) ), s( - tr('con', 'Constructor function block'), + etr('con', 'Constructor function block'), c(0, { sn( nil, @@ -324,19 +243,19 @@ return flatten { ), }) ), - s(atr('([^%w])function', 'Shorthand function block'), fmtc('fun', {})), - s(atr('([^%w])s%$', 'string type parameter'), fmtc('string $#~', { i(0, 'var') })), - s(atr('([^%w])i%$', 'int type parameter'), fmtc('int $#~', { i(0, 'var') })), - s(atr('([^%w])b%$', 'bool type parameter'), fmtc('bool $#~', { i(0, 'var') })), - s(atr('([^%w])a%$', 'array type parameter'), fmtc('array $#~', { i(0, 'var') })), + bs(atr('function', 'Shorthand function block'), fmta('fun', {})), + bs(atr('s%$', 'string type parameter'), fmta('string $#~', { i(0, 'var') })), + bs(atr('i%$', 'int type parameter'), fmta('int $#~', { i(0, 'var') })), + bs(atr('b%$', 'bool type parameter'), fmta('bool $#~', { i(0, 'var') })), + bs(atr('a%$', 'array type parameter'), fmta('array $#~', { i(0, 'var') })), s(atr('$ ', 'Expand $this->'), fmta('$this->#~', { i(0) })), - s( - atr('([^%w])am ', 'array_map function'), + bs( + atr('am ', 'array_map function'), c(0, { - sn(nil, fmtc('array_map(fn (#~) => #~, #~)', { i(2), i(3), i(1) })), + sn(nil, fmta('array_map(fn (#~) => #~, #~)', { i(2), i(3), i(1) })), sn( nil, - fmtc( + fmta( [[ array_map(function (#~) { #~ @@ -347,14 +266,14 @@ return flatten { ), }) ), - s(atr('([^%w])array_map', 'array_map function'), fmtc('am', {})), - s( - atr('([^%w])af ', 'array_filter function'), + bs(atr('array_map', 'array_map function'), fmta('am', {})), + bs( + atr('af ', 'array_filter function'), c(0, { - sn(nil, fmtc('array_filter(#~, fn (#~) => #~)', { i(1), i(2), i(3) })), + sn(nil, fmta('array_filter(#~, fn (#~) => #~)', { i(1), i(2), i(3) })), sn( nil, - fmtc( + fmta( [[ array_filter(#~, function (#~) { #~ @@ -365,16 +284,16 @@ return flatten { ), }) ), - s(atr('([^%w])array_filter', 'array_filter function'), fmtc('af', {})), + bs(atr('([^%w])array_filter', 'array_filter function'), fmta('af', {})), s( - tr('php', 'php class'), + etr('php', 'php class'), fmta( [[