From dc5d2e20ff5f817b766a05473cf50ea7618a9411 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 8 Dec 2025 00:52:01 +0000 Subject: [PATCH] Snippets --- lua/keymap.lua | 27 +++ lua/plugins/ai-completion.lua | 7 +- lua/plugins/blink.lua | 4 +- lua/plugins/snippets.lua | 4 + lua/snippets/php.lua | 395 +++++++++++++++++++++++++++------- 5 files changed, 355 insertions(+), 82 deletions(-) diff --git a/lua/keymap.lua b/lua/keymap.lua index 40c3828..493a299 100644 --- a/lua/keymap.lua +++ b/lua/keymap.lua @@ -35,6 +35,33 @@ vim.keymap.set('n', '', '', { desc = 'Move focus to the right win vim.keymap.set('n', '', '', { desc = 'Move focus to the lower window' }) vim.keymap.set('n', '', '', { desc = 'Move focus to the upper window' }) +vim.keymap.set({ 'i' }, '', function() + local ls = require 'luasnip' + if ls.choice_active() then + ls.change_choice(1) + end +end, { silent = true }) +vim.keymap.set({ 'i' }, '', function() + local ls = require 'luasnip' + if ls.choice_active() then + ls.change_choice(-1) + end +end, { silent = true }) +vim.keymap.set({ 'i', 's' }, '', function() + local ls = require 'luasnip' + if ls.expandable() then + ls.expand() + elseif ls.in_snippet() then + ls.jump(1) + end +end, { silent = true }) +vim.keymap.set({ 'i', 's' }, '', function() + local ls = require 'luasnip' + if ls.in_snippet() then + ls.jump(-1) + end +end, { silent = true }) + vim.keymap.set('n', '', 'H', { desc = 'Move window to the left' }) vim.keymap.set('n', '', 'L', { desc = 'Move window to the right' }) vim.keymap.set('n', '', 'J', { desc = 'Move window to the lower' }) diff --git a/lua/plugins/ai-completion.lua b/lua/plugins/ai-completion.lua index a628793..ab621a9 100644 --- a/lua/plugins/ai-completion.lua +++ b/lua/plugins/ai-completion.lua @@ -1,6 +1,6 @@ -- AI code completion -if (vim.fn.getenv('COPILOT_API_KEY') ~= vim.NIL) then +if vim.fn.getenv 'COPILOT_API_KEY' ~= vim.NIL then return { 'zbirenbaum/copilot.lua', event = 'InsertEnter', @@ -10,7 +10,7 @@ if (vim.fn.getenv('COPILOT_API_KEY') ~= vim.NIL) then enabled = true, auto_trigger = true, keymap = { - accept = '', + accept = '', }, }, filetypes = { @@ -48,8 +48,9 @@ return { provider = 'openai_compatible', request_timeout = 3, throttle = 500, -- Increase to reduce costs and avoid rate limits - debounce = 300, -- Increase to reduce costs and avoid rate limits + debounce = 400, -- Increase to reduce costs and avoid rate limits n_completions = 1, + before_cursor_filter_length = 10, provider_options = { openai_compatible = { api_key = 'COMPLETION_OPENAI_API_KEY', diff --git a/lua/plugins/blink.lua b/lua/plugins/blink.lua index 26b3e38..0bc256f 100644 --- a/lua/plugins/blink.lua +++ b/lua/plugins/blink.lua @@ -4,7 +4,7 @@ return { event = 'VimEnter', version = '1.*', dependencies = { - 'L3MON4D3/LuaSnip', + -- 'L3MON4D3/LuaSnip', 'folke/lazydev.nvim', }, --- @module 'blink.cmp' @@ -61,7 +61,7 @@ return { }, }, - snippets = { preset = 'luasnip' }, + -- snippets = { preset = 'luasnip' }, -- Blink.cmp includes an optional, recommended rust fuzzy matcher, -- which automatically downloads a prebuilt binary when enabled. diff --git a/lua/plugins/snippets.lua b/lua/plugins/snippets.lua index 5db80d0..1f758dd 100644 --- a/lua/plugins/snippets.lua +++ b/lua/plugins/snippets.lua @@ -17,6 +17,10 @@ return { require('luasnip.loaders.from_lua').load { paths = { snippets_dir }, } + ls.setup { + update_events = 'TextChanged,TextChangedI', + enable_autosnippets = true, + } end, opts = {}, } diff --git a/lua/snippets/php.lua b/lua/snippets/php.lua index 99ab8f8..f86d98a 100644 --- a/lua/snippets/php.lua +++ b/lua/snippets/php.lua @@ -1,8 +1,56 @@ 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 '' @@ -49,81 +97,274 @@ local function class_name(args, snip) return filename:match '([^%.]+)' or 'ClassName' end -return { - s('du', { t 'dump(', i(0), t ');' }), - s('dt', { t '\\PhpStan\\dumpType(', i(0), t ');' }), - s('ql', { - t '\\Illuminate\\Support\\Facades\\DB::listen(function (\\Illuminate\\Database\\Events\\QueryExecuted $e) {', - t { '', ' dump($e->sql, $e->bindings);' }, - t { '', '});' }, - }), - s('test', { - t 'test(', - i(1), - t ', function () {', - t { '', ' ' }, - i(0), - t { '', ');' }, - }), - s('/**', { - t '/**', - t { '', ' * ' }, - i(0), - t { '', ' */' }, - }), - s('@p', { t '@property ', i(1), t ' $', i(0) }), - s('@pb', { t '@property bool $', i(0) }), - s('@ps', { t '@property string $', i(0) }), - s('@pi', { t '@property int $', i(0) }), - s('@pc', { t '@property \\Illuminate\\Support\\Collection $', i(0) }), - s('@pd', { t '@property \\Illuminate\\Support\\Carbon $', i(0) }), - s('@pp', { - t '/**', - t { '', ' * @property int $id' }, - t { '', ' * ' }, - i(1), - t { '', ' * @property \\Illuminate\\Support\\Carbon $created_at' }, - t { '', ' * @property \\Illuminate\\Support\\Carbon $updated_at' }, - t { '', ' *', ' * Relationships' }, - t { '', ' * ' }, - i(0), - t { '', ' */' }, - }), - s('php', { - t '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) } + ) + ), }