local ls = require 'luasnip' local s = ls.snippet local sn = ls.snippet_node local t = ls.text_node local c = ls.choice_node local i = ls.insert_node 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 tr(trigger, description) return { trig = trigger, desc = description, snippetType = 'autosnippet', condition = empty_line, } end local function fn_choice(index) return c(index, { sn(nil, fmta('fn(#~) => #~', { i(1), i(0) })), sn( nil, fmta( [[ function (#~) { #~ } ]], { i(1), i(0) } ) ), }) end 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 end local function class_name(args, snip) local filename = snip.env.TM_FILENAME or '' 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 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 { --------------- -- DEBUGGING -- --------------- s(tr('du ', 'Dump a variable to the dump server'), fmta('dump(#~);', { i(0) })), s(tr('ray', 'ray'), fmta('ray(#~);', { i(0) })), s(tr('dt ', 'Dump PHPStan type definition'), fmta('\\PhpStan\\dumpType(#~);', { i(0) })), s( tr('ql ', 'Log all queries'), fmta( [[ \Illuminate\Support\Facades\DB::listen(function (\Illuminate\Database\Events\QueryExecuted $e) { dump($e->sql, $e->bindings); }); #~ ]], { i(0) } ) ), -------------- -- COMMENTS -- -------------- s( tr('/**', 'Docblock comment'), fmta( [[ /** * #~ */ ]], { i(0) } ) ), s(tr('@v', '@var docblock'), fmta('/** @var #~ $#~ */', { i(1), i(0) })), s(tr('* @pr', 'Class property docblock'), fmta('* @property #~ $#~', { i(1), i(0) })), s(tr('* @pb', 'Class boolean property docblock'), fmta('* @property bool $#~', { i(0) })), s(tr('* @pi', 'Class int property docblock'), fmta('* @property int $#~', { i(0) })), s(tr('* @ps', 'Class string property docblock'), fmta('* @property string $#~', { i(0) })), s(tr('* @pc', 'Class collection property docblock'), fmta('* @property \\Illuminate\\Database\\Eloquent\\Collection<#~> $#~', { i(1), i(0) })), s(tr('* @pd', 'Class date property docblock'), fmta('* @property \\Illuminate\\Support\\Carbon $#~', { i(0) })), s( tr('@pm', 'Model magic properties docblock'), fmta( [[ /** * @property int $id * #~ * @property \Illuminate\Support\Carbon $created_at * @property \Illuminate\Support\Carbon $updated_at * * Relationships * #~ */ ]], { i(1), i(0) } ) ), ------------ -- SYNTAX -- ------------ s( tr('if ', 'foreach block'), fmta( [[ if (#~) { #~ } ]], { i(1), i(0) } ) ), s( tr('fe ', 'foreach block'), fmta( [[ foreach (#~ as #~) { #~ } ]], { i(1), i(2), i(0) } ) ), s(tr('return ', 'Add semicolon after return'), fmta('return #~;', { i(0) })), s(tr('fn ', 'Shorthand function block'), fmta('fn(#~) => #~', { i(1), i(0) })), s(tr('$ ', 'Expand $this->'), fmta('$this->#~', { i(0) })), snippet_aliases({ 'am ', 'array_map' }, 'array_map function', function() return fmta('array_map(#~, #~);', { fn_choice(0), i(1), }) end), snippet_aliases({ 'af ', 'array_filter' }, 'array_filter function', function() return fmta('array_filter(#~, #~);', { i(1), fn_choice(0), }) end), s( tr('php', 'php class'), fmta( [[ */ public function #~(): BelongsTo { return $this->belongsTo(#~::class); } ]], { rep(1), i(0), i(1) } ) ), s( tr('hm', 'hasMany Laravel relationship method'), fmta( [[ /** * @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\#~, $this> */ public function #~(): HasMany { return $this->hasOne(#~::class); } ]], { rep(1), i(0), i(1) } ) ), s( tr('ho', 'hasOne Laravel relationship method'), fmta( [[ /** * @return \Illuminate\Database\Eloquent\Relations\HasOne<\App\Models\#~, $this> */ public function #~(): HasOne { return $this->hasOne(#~::class); } ]], { rep(1), i(0), i(1) } ) ), s( tr('bm', 'belongsToMany Laravel relationship method'), fmta( [[ /** * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\App\Models\#~, $this> */ public function #~(): BelongsToMany { return $this->belongsToMany(#~::class, #~); } ]], { rep(1), i(2), i(1), i(0) } ) ), }