|
| 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 |
0 commit comments