Skip to content

Commit ae4aeae

Browse files
committed
fix: plugin paths
1 parent 1b28288 commit ae4aeae

File tree

4 files changed

+593
-0
lines changed

4 files changed

+593
-0
lines changed

lua/blink/cmp/cmp.lua

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
local M = {}
2+
3+
M.kind_icons = {
4+
Text = '󰉿',
5+
Method = '󰊕',
6+
Function = '󰊕',
7+
Constructor = '󰒓',
8+
9+
Field = '󰜢',
10+
Variable = '󰆦',
11+
Property = '󰖷',
12+
13+
Class = '󱡠',
14+
Interface = '󱡠',
15+
Struct = '󱡠',
16+
Module = '󰅩',
17+
18+
Unit = '󰪚',
19+
Value = '󰦨',
20+
Enum = '󰦨',
21+
EnumMember = '󰦨',
22+
23+
Keyword = '󰻾',
24+
Constant = '󰏿',
25+
26+
Snippet = '󱄽',
27+
Color = '󰏘',
28+
File = '󰈔',
29+
Reference = '󰬲',
30+
Folder = '󰉋',
31+
Event = '󱐋',
32+
Operator = '󰪚',
33+
TypeParameter = '󰬛',
34+
}
35+
M.filtered_items = {}
36+
37+
M.fuzzy = require('blink.fuzzy').fuzzy
38+
M.lsp = require('blink.cmp.lsp')
39+
40+
M.accept = function(cmp_win)
41+
local bufnr = vim.api.nvim_get_current_buf()
42+
local current_line, start_col, end_col = M.get_query_to_replace(bufnr)
43+
44+
-- Get the item from the filtered items based on the cursorline position
45+
local item = M.filtered_items[vim.api.nvim_win_get_cursor(cmp_win.id)[1]]
46+
47+
-- Apply text edit
48+
vim.api.nvim_buf_set_text(bufnr, current_line, start_col, current_line, end_col, { item.word })
49+
vim.api.nvim_win_set_cursor(0, { current_line + 1, start_col + #item.word })
50+
51+
-- Apply additional text edits
52+
-- LSPs can either include these in the initial response or require a resolve
53+
-- These are used for things like auto-imports
54+
-- todo: check capabilities to know ahead of time
55+
if item.additionalTextEdits ~= nil then
56+
M.apply_additional_text_edits(item.client_id, item)
57+
else
58+
M.lsp.resolve(item, function(client_id, resolved_item) M.apply_additional_text_edits(client_id, resolved_item) end)
59+
end
60+
end
61+
62+
M.select_next = function(cmp_win)
63+
local current_line = vim.api.nvim_win_get_cursor(cmp_win.id)[1]
64+
vim.api.nvim_win_set_cursor(cmp_win.id, { current_line + 1, 0 })
65+
end
66+
67+
M.select_prev = function(cmp_win)
68+
local current_line = vim.api.nvim_win_get_cursor(cmp_win.id)[1]
69+
vim.api.nvim_win_set_cursor(cmp_win.id, { current_line - 1, 0 })
70+
end
71+
72+
M.update = function(cmp_win, doc_win, items, opts)
73+
local start_time = vim.loop.hrtime()
74+
75+
local query = M.get_query()
76+
77+
-- get the items based on the user's query
78+
local filtered_items = M.filter_items(query, items)
79+
80+
-- guards for cases where we shouldn't show the completion window
81+
local no_items = #filtered_items == 0
82+
local is_exact_match = #filtered_items == 1 and filtered_items[1].word == query and opts.force ~= true
83+
local not_in_insert = vim.api.nvim_get_mode().mode ~= 'i'
84+
local no_query = query == '' and opts.force ~= true
85+
if no_items or is_exact_match or not_in_insert or no_query then
86+
cmp_win:close()
87+
doc_win:close()
88+
return
89+
end
90+
cmp_win:open()
91+
92+
-- update completion window
93+
vim.api.nvim_buf_set_lines(cmp_win.buf, 0, -1, true, {})
94+
for idx, item in ipairs(filtered_items) do
95+
local max_length = 40
96+
local kind_hl = 'CmpItemKind' .. item.kind
97+
local kind_icon = M.kind_icons[item.kind] or M.kind_icons.Field
98+
local kind = item.kind
99+
100+
local utf8len = vim.fn.strdisplaywidth
101+
local other_content_length = utf8len(kind_icon) + utf8len(kind) + 5
102+
local remaining_length = math.max(0, max_length - other_content_length - utf8len(item.abbr))
103+
local abbr = string.sub(item.abbr, 1, max_length - other_content_length) .. string.rep(' ', remaining_length)
104+
105+
local line = string.format(' %s %s %s ', kind_icon, abbr, kind)
106+
vim.api.nvim_buf_set_lines(cmp_win.buf, idx - 1, idx, false, { line })
107+
vim.api.nvim_buf_add_highlight(cmp_win.buf, -1, kind_hl, idx - 1, 0, #kind_icon + 2)
108+
109+
if idx > cmp_win.config.max_height then break end
110+
end
111+
112+
-- set height
113+
vim.api.nvim_win_set_height(cmp_win.id, math.min(#filtered_items, cmp_win.config.max_height))
114+
115+
-- select first line
116+
vim.api.nvim_win_set_cursor(cmp_win.id, { 1, 0 })
117+
118+
-- documentation
119+
local first_item = filtered_items[1]
120+
if first_item ~= nil then
121+
M.lsp.resolve(first_item, function(_, resolved_item)
122+
if resolved_item.detail == nil then
123+
doc_win:close()
124+
return
125+
end
126+
local doc_lines = {}
127+
for s in resolved_item.detail:gmatch('[^\r\n]+') do
128+
table.insert(doc_lines, s)
129+
end
130+
vim.api.nvim_buf_set_lines(doc_win.buf, 0, -1, true, doc_lines)
131+
doc_win:open()
132+
end)
133+
end
134+
135+
M.filtered_items = filtered_items
136+
137+
local end_time = vim.loop.hrtime()
138+
local time_in_ms = (end_time - start_time) / 1e6
139+
print('Time taken to filter ' .. #items .. ' completions: ' .. time_in_ms .. 'ms')
140+
end
141+
142+
---------- UTILS ------------
143+
144+
M.filter_items = function(query, items)
145+
if query == '' then return items end
146+
147+
-- convert to table of strings
148+
local words = {}
149+
for _, item in ipairs(items) do
150+
table.insert(words, item.word)
151+
end
152+
153+
-- perform fuzzy search
154+
local filtered_items = {}
155+
local selected_indices = M.fuzzy(query, words)
156+
for _, selected_index in ipairs(selected_indices) do
157+
table.insert(filtered_items, items[selected_index + 1])
158+
end
159+
160+
return filtered_items
161+
end
162+
163+
M.get_query = function()
164+
local bufnr = vim.api.nvim_get_current_buf()
165+
local current_line = vim.api.nvim_win_get_cursor(0)[1] - 1
166+
local current_col = vim.api.nvim_win_get_cursor(0)[2] - 1
167+
local line = vim.api.nvim_buf_get_lines(bufnr, current_line, current_line + 1, false)[1]
168+
local query = string.sub(line, 1, current_col + 1):match('[%w_\\-]+$') or ''
169+
return query
170+
end
171+
172+
M.get_query_to_replace = function(bufnr)
173+
local current_line = vim.api.nvim_win_get_cursor(0)[1]
174+
local current_col = vim.api.nvim_win_get_cursor(0)[2] + 1
175+
local line = vim.api.nvim_buf_get_lines(bufnr, current_line - 1, current_line, false)[1]
176+
177+
-- Search forward/backward for the start/end of the word
178+
local start_col = current_col
179+
while start_col > 1 do
180+
local char = line:sub(start_col - 1, start_col - 1)
181+
if char:match('[%w_\\-]') == nil then break end
182+
start_col = start_col - 1
183+
end
184+
185+
local end_col = current_col
186+
while end_col < #line do
187+
local char = line:sub(end_col + 1, end_col + 1)
188+
if char:match('[%w_\\-]') == nil then break end
189+
end_col = end_col + 1
190+
end
191+
192+
-- convert to 0-index
193+
return current_line - 1, start_col - 1, end_col - 1
194+
end
195+
196+
M.apply_additional_text_edits = function(client_id, item) M.apply_text_edits(client_id, item.additionalTextEdits or {}) end
197+
198+
M.apply_text_edits = function(client_id, edits)
199+
local offset_encoding = vim.lsp.get_client_by_id(client_id).offset_encoding
200+
vim.lsp.util.apply_text_edits(edits, vim.api.nvim_get_current_buf(), offset_encoding)
201+
end
202+
203+
return M

lua/blink/cmp/init.lua

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
local M = {}
2+
3+
M.items = {}
4+
5+
M.setup = function(config)
6+
M.cmp_win = require('blink.cmp.win').new({
7+
cursorline = true,
8+
winhighlight = 'Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None',
9+
})
10+
M.doc_win = require('blink.cmp.win').new({
11+
width = 60,
12+
max_height = 20,
13+
relative = M.cmp_win,
14+
wrap = true,
15+
filetype = 'typescript', -- todo: set dynamically
16+
padding = true,
17+
})
18+
M.lsp = require('blink.cmp.lsp')
19+
M.cmp = require('blink.cmp.cmp')
20+
21+
local last_char = ''
22+
vim.api.nvim_create_autocmd('InsertCharPre', {
23+
callback = function() last_char = vim.v.char end,
24+
})
25+
26+
-- decide if we should show the completion window
27+
vim.api.nvim_create_autocmd('TextChangedI', {
28+
callback = function()
29+
if M.cmp_win.id ~= nil then return end
30+
-- todo: if went from prefix to no prefix, clear the items
31+
if last_char ~= '' and last_char ~= ' ' and last_char ~= '\n' then M.update() end
32+
end,
33+
})
34+
35+
-- update the completion window
36+
vim.api.nvim_create_autocmd('CursorMovedI', {
37+
callback = function()
38+
if M.cmp_win.id ~= nil then M.update() end
39+
end,
40+
})
41+
42+
-- show completion windows
43+
vim.api.nvim_create_autocmd({ 'InsertLeave', 'BufLeave' }, {
44+
callback = function()
45+
M.lsp.cancel_completions()
46+
M.cmp_win:close()
47+
M.doc_win:close()
48+
M.items = {}
49+
end,
50+
})
51+
52+
-- keybindings
53+
-- todo: SCUFFED
54+
local keymap = function(mode, key, callback)
55+
vim.api.nvim_set_keymap(mode, key, '', {
56+
expr = true,
57+
noremap = true,
58+
silent = true,
59+
callback = function()
60+
if M.cmp_win.id == nil then return vim.api.nvim_replace_termcodes(key, true, false, true) end
61+
vim.schedule(callback)
62+
end,
63+
})
64+
end
65+
keymap('i', '<Tab>', M.accept)
66+
keymap('i', '<C-j>', M.select_next)
67+
keymap('i', '<C-k>', M.select_prev)
68+
keymap('i', '<Up>', M.select_prev)
69+
keymap('i', '<Down>', M.select_next)
70+
vim.api.nvim_set_keymap('i', '<C-space>', '', {
71+
noremap = true,
72+
silent = true,
73+
callback = function() M.update({ force = true }) end,
74+
})
75+
end
76+
77+
M.update = function(opts)
78+
opts = opts or { force = false }
79+
80+
-- immediately update the results
81+
M.cmp.update(M.cmp_win, M.doc_win, M.items, opts)
82+
M.cmp_win:update()
83+
M.doc_win:update()
84+
-- trigger the lsp and update the results after retrieving
85+
M.lsp.completions(function(items)
86+
M.items = items
87+
M.cmp.update(M.cmp_win, M.doc_win, M.items, opts)
88+
M.cmp_win:update()
89+
M.doc_win:update()
90+
end)
91+
end
92+
93+
M.accept = function()
94+
if M.cmp_win.id ~= nil then M.cmp.accept(M.cmp_win) end
95+
end
96+
97+
M.select_prev = function()
98+
if M.cmp_win.id == nil then return end
99+
100+
local current_line = vim.api.nvim_win_get_cursor(M.cmp_win.id)[1]
101+
vim.api.nvim_win_set_cursor(M.cmp_win.id, { math.max(1, current_line - 1), 0 })
102+
end
103+
104+
M.select_next = function()
105+
if M.cmp_win.id == nil then return end
106+
107+
local current_line = vim.api.nvim_win_get_cursor(M.cmp_win.id)[1]
108+
local line_count = vim.api.nvim_buf_line_count(M.cmp_win.buf)
109+
vim.api.nvim_win_set_cursor(M.cmp_win.id, { math.min(line_count, current_line + 1), 0 })
110+
end
111+
112+
return M

0 commit comments

Comments
 (0)