Skip to content

Commit 5c14b9a

Browse files
committed
blink support
1 parent 5c990f0 commit 5c14b9a

File tree

21 files changed

+1243
-587
lines changed

21 files changed

+1243
-587
lines changed

lua/obsidian/commands/debug.lua

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ local util = require "obsidian.util"
55
local VERSION = require "obsidian.version"
66

77
---@return { available: boolean, refs: boolean|?, tags: boolean|?, new: boolean|?, sources: string[]|? }
8-
local function check_completion()
8+
local function check_completion_with_nvim_cmp()
99
local ok, cmp = pcall(require, "cmp")
1010
if not ok then
1111
return { available = false }
@@ -30,6 +30,28 @@ local function check_completion()
3030
return { available = true, refs = cmp_refs, tags = cmp_tags, new = cmp_new, sources = sources }
3131
end
3232

33+
---@return { available: boolean, refs: boolean|?, tags: boolean|?, new: boolean|?, sources: string[]|? }
34+
local function check_completion_with_blink()
35+
local ok, blink_sources_lib = pcall(require, "blink.cmp.sources.lib")
36+
if not ok then
37+
return { available = false }
38+
end
39+
40+
local cmp_refs = pcall(blink_sources_lib.get_provider_by_id, "obsidian")
41+
local cmp_tags = pcall(blink_sources_lib.get_provider_by_id, "obsidian_tags")
42+
local cmp_new = pcall(blink_sources_lib.get_provider_by_id, "obsidian_new")
43+
44+
local sources = {}
45+
local ok, providers = pcall(blink_sources_lib.get_all_providers)
46+
if ok then
47+
vim.tbl_map(function(provider)
48+
table.insert(sources, provider.name)
49+
end, providers)
50+
end
51+
52+
return { available = true, refs = cmp_refs ~= nil, tags = cmp_tags, new = cmp_new, sources = sources }
53+
end
54+
3355
---@param client obsidian.Client
3456
return function(client, data)
3557
data = data or {}
@@ -51,7 +73,8 @@ return function(client, data)
5173
end
5274

5375
log.lazy_info "Dependencies:"
54-
for _, plugin in ipairs { "plenary.nvim", "nvim-cmp", "telescope.nvim", "fzf-lua", "mini.pick" } do
76+
77+
for _, plugin in ipairs { "plenary.nvim", "nvim-cmp", "blink.cmp", "telescope.nvim", "fzf-lua", "mini.pick" } do
5578
local plugin_info = util.get_plugin_info(plugin)
5679
if plugin_info ~= nil then
5780
log.lazy_info(" ✓ %s: %s", plugin, plugin_info.commit or "unknown")
@@ -62,18 +85,37 @@ return function(client, data)
6285
log.lazy_info(" ✓ picker: %s", client:picker())
6386

6487
if client.opts.completion.nvim_cmp then
65-
local cmp_status = check_completion()
66-
if cmp_status.available then
88+
local nvim_cmp_status = check_completion_with_nvim_cmp()
89+
if nvim_cmp_status.available then
6790
log.lazy_info(
6891
" ✓ completion: enabled (nvim-cmp) %s refs, %s tags, %s new",
69-
cmp_status.refs and "" or "",
70-
cmp_status.tags and "" or "",
71-
cmp_status.new and "" or ""
92+
nvim_cmp_status.refs and "" or "",
93+
nvim_cmp_status.tags and "" or "",
94+
nvim_cmp_status.new and "" or ""
95+
)
96+
97+
if nvim_cmp_status.sources then
98+
log.lazy_info " all sources:"
99+
for _, source in ipairs(nvim_cmp_status.sources) do
100+
log.lazy_info(" • %s", source)
101+
end
102+
end
103+
else
104+
log.lazy_info " ✓ completion: unavailable"
105+
end
106+
elseif client.opts.completion.blink then
107+
local blink_status = check_completion_with_blink()
108+
if blink_status.available then
109+
log.lazy_info(
110+
" ✓ completion: enabled (blink) %s refs, %s tags, %s new",
111+
blink_status.refs and "" or "",
112+
blink_status.tags and "" or "",
113+
blink_status.new and "" or ""
72114
)
73115

74-
if cmp_status.sources then
116+
if blink_status.sources then
75117
log.lazy_info " all sources:"
76-
for _, source in ipairs(cmp_status.sources) do
118+
for _, source in ipairs(blink_status.sources) do
77119
log.lazy_info(" • %s", source)
78120
end
79121
end
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
local util = require "obsidian.util"
2+
local obsidian = require "obsidian"
3+
4+
local M = {}
5+
6+
M.injected_once = false
7+
8+
M.providers = {
9+
{ name = "obsidian", module = "obsidian.completion.sources.blink.refs" },
10+
{ name = "obsidian_tags", module = "obsidian.completion.sources.blink.tags" },
11+
{ name = "obsidian_new", module = "obsidian.completion.sources.blink.new" },
12+
}
13+
14+
local function add_provider(blink, provider_name, proivder_module)
15+
blink.add_provider(provider_name, {
16+
name = provider_name,
17+
module = proivder_module,
18+
async = true,
19+
opts = {},
20+
enabled = function()
21+
-- Enable only in markdown buffers.
22+
return vim.tbl_contains({ "markdown" }, vim.bo.filetype)
23+
and vim.bo.buftype ~= "prompt"
24+
and vim.b.completion ~= false
25+
end,
26+
})
27+
end
28+
29+
-- Ran once on the plugin startup
30+
function M.register_providers()
31+
local blink = require "blink.cmp"
32+
33+
for _, provider in pairs(M.providers) do
34+
add_provider(blink, provider.name, provider.module)
35+
end
36+
end
37+
38+
local function add_element_to_list_if_not_exists(list, element)
39+
if not vim.tbl_contains(list, element) then
40+
table.insert(list, 1, element)
41+
end
42+
end
43+
44+
local function should_return_if_not_in_workspace()
45+
local current_file_path = vim.api.nvim_buf_get_name(0)
46+
local buf_dir = vim.fs.dirname(current_file_path)
47+
48+
local obsidian_client = assert(obsidian.get_client())
49+
local workspace = obsidian.Workspace.get_workspace_for_dir(buf_dir, obsidian_client.opts.workspaces)
50+
if not workspace then
51+
return true
52+
else
53+
return false
54+
end
55+
end
56+
57+
local function log_unexpected_type(config_path, unexpected_type, expected_type)
58+
vim.notify(
59+
"blink.cmp's `"
60+
.. config_path
61+
.. "` configuration appears to be an '"
62+
.. unexpected_type
63+
.. "' type, but it "
64+
.. "should be '"
65+
.. expected_type
66+
.. "'. Obsidian won't update this configuration, and "
67+
.. "completion won't work with blink.cmp",
68+
vim.log.levels.ERROR
69+
)
70+
end
71+
72+
---Attempts to inject the Obsidian sources into per_filetype if that's what the user seems to use for markdown
73+
---@param blink_sources_per_filetype table<string, (fun():string[])|(string[])>
74+
---@return boolean true if it obsidian sources were injected into the sources.per_filetype
75+
local function try_inject_blink_sources_into_per_filetype(blink_sources_per_filetype)
76+
-- If the per_filetype is an empty object, then it's probably not utilized by the user
77+
if vim.deep_equal(blink_sources_per_filetype, {}) then
78+
return false
79+
end
80+
81+
local markdown_config = blink_sources_per_filetype["markdown"]
82+
83+
-- If the markdown key is not used, then per_filetype it's probably not utilized by the user
84+
if markdown_config == nil then
85+
return false
86+
end
87+
88+
local markdown_config_type = type(markdown_config)
89+
if markdown_config_type == "table" and util.tbl_is_array(markdown_config) then
90+
for _, provider in pairs(M.providers) do
91+
add_element_to_list_if_not_exists(markdown_config, provider.name)
92+
end
93+
return true
94+
elseif markdown_config_type == "function" then
95+
local original_func = markdown_config
96+
markdown_config = function()
97+
local original_results = original_func()
98+
99+
if should_return_if_not_in_workspace() then
100+
return original_results
101+
end
102+
103+
for _, provider in pairs(M.providers) do
104+
add_element_to_list_if_not_exists(original_results, provider.name)
105+
end
106+
return original_results
107+
end
108+
109+
-- Overwrite the original config function with the newly generated one
110+
require("blink.cmp.config").sources.per_filetype["markdown"] = markdown_config
111+
return true
112+
else
113+
log_unexpected_type(
114+
".sources.per_filetype['markdown']",
115+
markdown_config_type,
116+
"a list or a function that returns a list of sources"
117+
)
118+
return true -- logged the error, returns as if this was successful to avoid further errors
119+
end
120+
end
121+
122+
---Attempts to inject the Obsidian sources into default if that's what the user seems to use for markdown
123+
---@param blink_sources_default (fun():string[])|(string[])
124+
---@return boolean true if it obsidian sources were injected into the sources.default
125+
local function try_inject_blink_sources_into_default(blink_sources_default)
126+
local blink_default_type = type(blink_sources_default)
127+
if blink_default_type == "function" then
128+
local original_func = blink_sources_default
129+
blink_sources_default = function()
130+
local original_results = original_func()
131+
132+
if should_return_if_not_in_workspace() then
133+
return original_results
134+
end
135+
136+
for _, provider in pairs(M.providers) do
137+
add_element_to_list_if_not_exists(original_results, provider.name)
138+
end
139+
return original_results
140+
end
141+
142+
-- Overwrite the original config function with the newly generated one
143+
require("blink.cmp.config").sources.default = blink_sources_default
144+
return true
145+
elseif blink_default_type == "table" and util.tbl_is_array(blink_sources_default) then
146+
for _, provider in pairs(M.providers) do
147+
add_element_to_list_if_not_exists(blink_sources_default, provider.name)
148+
end
149+
150+
return true
151+
elseif blink_default_type == "table" then
152+
log_unexpected_type(".sources.default", blink_default_type, "a list")
153+
return true -- logged the error, returns as if this was successful to avoid further errors
154+
else
155+
log_unexpected_type(".sources.default", blink_default_type, "a list or a function that returns a list")
156+
return true -- logged the error, returns as if this was successful to avoid further errors
157+
end
158+
end
159+
160+
-- Triggered for each opened markdown buffer that's in a workspace. nvm_cmp had the capability to configure the sources
161+
-- per buffer, but blink.cmp doesn't have that capability. Instead, we have to inject the sources into the global
162+
-- configuration and set a boolean on the module to return early the next time this function is called.
163+
--
164+
-- In-case the user used functions to configure their sources, the completion will properly work just for the markdown
165+
-- files that are in a workspace. Otherwise, the completion will work for all markdown files.
166+
function M.inject_sources()
167+
if M.injected_once then
168+
return
169+
end
170+
171+
M.injected_once = true
172+
173+
local blink_config = require "blink.cmp.config"
174+
-- 'per_filetype' sources has priority over 'default' sources.
175+
-- 'per_filetype' can be a table or a function which returns a table (["filetype"] = { "a", "b" })
176+
-- 'per_filetype' has the default value of {} (even if it's not configured by the user)
177+
local blink_sources_per_filetype = blink_config.sources.per_filetype
178+
if try_inject_blink_sources_into_per_filetype(blink_sources_per_filetype) then
179+
return
180+
end
181+
182+
-- 'default' can be a list/array or a function which returns a list/array ({ "a", "b"})
183+
local blink_sources_default = blink_config.sources["default"]
184+
if try_inject_blink_sources_into_default(blink_sources_default) then
185+
return
186+
end
187+
end
188+
189+
return M
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
local M = {}
2+
3+
-- Ran once on the plugin startup
4+
function M.register_sources()
5+
local cmp = require "cmp"
6+
7+
cmp.register_source("obsidian", require("obsidian.completion.sources.nvim_cmp.refs").new())
8+
cmp.register_source("obsidian_new", require("obsidian.completion.sources.nvim_cmp.new").new())
9+
cmp.register_source("obsidian_tags", require("obsidian.completion.sources.nvim_cmp.tags").new())
10+
end
11+
12+
-- Triggered for each opened markdown buffer that's in a workspace and configures nvim_cmp sources for the current buffer.
13+
function M.inject_sources()
14+
local cmp = require "cmp"
15+
16+
local sources = {
17+
{ name = "obsidian" },
18+
{ name = "obsidian_new" },
19+
{ name = "obsidian_tags" },
20+
}
21+
for _, source in pairs(cmp.get_config().sources) do
22+
if source.name ~= "obsidian" and source.name ~= "obsidian_new" and source.name ~= "obsidian_tags" then
23+
table.insert(sources, source)
24+
end
25+
end
26+
---@diagnostic disable-next-line: missing-fields
27+
cmp.setup.buffer { sources = sources }
28+
end
29+
30+
return M

0 commit comments

Comments
 (0)