Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lua/tabnine/binary.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ local TabnineBinary = {}
local config = require("tabnine.config")

local api_version = "4.4.223"
local binaries_path = utils.script_path() .. "/binaries"
local binaries_path = utils.module_dir() .. "/binaries"

local function arch_and_platform()
local os_uname = uv.os_uname()
Expand Down
2 changes: 1 addition & 1 deletion lua/tabnine/chat/binary.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ local function binary_name()
end
end

local binary_path = utils.script_path() .. "/../chat/target/release/" .. binary_name()
local binary_path = utils.module_dir() .. "/chat/target/release/" .. binary_name()

function ChatBinary:available()
return vim.fn.executable(binary_path) == 1
Expand Down
4 changes: 2 additions & 2 deletions lua/tabnine/chat/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ local config = require("tabnine.config")

local M = { enabled = false }

local CHAT_STATE_FILE = utils.script_path() .. "/../chat_state.json"
local CHAT_SETTINGS_FILE = utils.script_path() .. "/../chat_settings.json"
local CHAT_STATE_FILE = utils.module_dir() .. "/chat_state.json"
local CHAT_SETTINGS_FILE = utils.module_dir() .. "/chat_settings.json"

local chat_state = nil
local chat_settings = nil
Expand Down
2 changes: 1 addition & 1 deletion lua/tabnine/status.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local fn = vim.fn
local utils = require("tabnine.utils")

local M = {}
local DISABLED_FILE = utils.script_path() .. "/.disabled"
local DISABLED_FILE = utils.module_dir() .. "/.disabled"
local config = require("tabnine.config")
local state = require("tabnine.state")
local tabnine_binary = require("tabnine.binary")
Expand Down
142 changes: 88 additions & 54 deletions lua/tabnine/utils.lua
Original file line number Diff line number Diff line change
@@ -1,105 +1,161 @@
local fn = vim.fn
local api = vim.api

local unpack = table.unpack or unpack
local pack = table.pack or vim.F.pack_len
local M = {}
local last_changedtick = vim.b.changedtick

---@param func fun(...: unknown): any the callback
---@param delay integer delay in milliseconds
---@return fun(...: unknown)
function M.debounce(func, delay)
local timer_id
return function(...)
if timer_id then fn.timer_stop(timer_id) end
local args = { ... }
local args = pack(...)
timer_id = fn.timer_start(delay, function()
func(unpack(args))
return func(unpack(args, 1, args.n))
end)
end
end

---@param str string
---@return string[]
function M.str_to_lines(str)
return fn.split(str, "\n")
end

---@param lines string[]
---@return string
function M.lines_to_str(lines)
return fn.join(lines, "\n")
end

---@param str string
---@param suffix string
---@return string
function M.remove_matching_suffix(str, suffix)
if not M.ends_with(str, suffix) then return str end
return str:sub(1, -#suffix - 1)
end

---@param str string
---@param prefix string
---@return string
function M.remove_matching_prefix(str, prefix)
if not M.starts_with(str, prefix) then return str end
return str:sub(#prefix)
end

---@generic T
---@param tbl T[] | {n?: integer} The table to get a subset from
---@param from integer? defaults to 1
---@param to integer? defaults to tbl.n or #tbl.
---@return T[]
function M.subset(tbl, from, to)
to = to or tbl.n or #tbl -- support table.pack values if no end given
-- We can't use table.pack here because nvim_buf_set_extmark will error if non-integer keys are present
-- Ideally, implementations would ignore string keys in an 'array'.
return { unpack(tbl, from, to) }
end

function M.script_path()
---returns the directory of the running script
---@return string
function M.script_dir()
local str = debug.getinfo(2, "S").source:sub(2)
return str:match("(.*/)") .. "../.."
return str:match("(.*/)") or "./"
end

---returns the directory of the root of the module
---@return string
function M.module_dir()
-- HACK: This only works if this file is not moved!
return M.script_dir() .. "../.."
end

function M.prequire(...)
local status, lib = pcall(require, ...)
---pcall require. Returns nil if the module is not found.
---Note: use @module to manage types.
---@param modname string
---@return unknown?
function M.prequire(modname)
local status, lib = pcall(require, modname)
if status then return lib end
return nil
end

---@return boolean
function M.pumvisible()
local cmp = M.prequire("cmp")
if cmp then
return cmp.visible()
else
return vim.fn.pumvisible() > 0
end
if cmp then return cmp.visible() end
return vim.fn.pumvisible() > 0
end

---Return the position of the cursor
---@return number line, number column
function M.current_position()
return { fn.line("."), fn.col(".") }
---@diagnostic disable-next-line: return-type-mismatch -- This is fine, they return non-nil values
return fn.line("."), fn.col(".")
end

---Returns true if str ends with suffix.
---@param str string
---@param suffix string
---@return boolean
function M.ends_with(str, suffix)
if str == "" then return true end

return str:sub(-#suffix) == suffix
end

---Returns true if str starts with prefix.
---@param str string
---@param prefix string
---@return boolean
function M.starts_with(str, prefix)
if str == "" then return true end

return str:sub(1, #prefix) == prefix
end

---Returns true if the current cursor position is the end of the current line
---@return boolean
function M.is_end_of_line()
return fn.col(".") == fn.col("$")
end

---Returns the text after the current cursor position to the end of the current line
---@return string
function M.end_of_line()
return api.nvim_buf_get_text(0, fn.line(".") - 1, fn.col(".") - 1, fn.line(".") - 1, fn.col("$"), {})[1]
end

function M.document_changed()
local current_changedtick = last_changedtick
last_changedtick = vim.b.changedtick
return last_changedtick > current_changedtick
do
---The last time we checked for a document change
local last_changedtick = vim.b.changedtick
---Returns true if the document has changed since the last call
---@return boolean
function M.document_changed()
local current_changedtick = last_changedtick
last_changedtick = vim.b.changedtick
return last_changedtick > current_changedtick
end
end

---Returns the currently selected text. If no in visual mode, returns the empty string.
---@return string
function M.selected_text()
local mode = vim.fn.mode()
local mode = vim.fn.mode() ---@type string
if mode ~= "v" and mode ~= "V" and mode ~= "" then return "" end
local a_orig = vim.fn.getreg("a")
local a_orig = vim.fn.getreg("a", 1)
vim.cmd([[silent! normal! "aygv]])
local text = vim.fn.getreg("a")
---@diagnostic disable-next-line: assign-type-mismatch -- This is fine. it returns a string
local text = vim.fn.getreg("a") ---@type string
vim.fn.setreg("a", a_orig)
return text
end

---gets the unique values from the array
---@generic T
---@param array T[]
---@return T[]
function M.set(array)
local set = {}
local uniqueValues = {}
local set = {} ---@type table<unknown, true>
local uniqueValues = {} ---@type unknown[]

for _, value in ipairs(array) do
if not set[value] then
Expand All @@ -111,39 +167,17 @@ function M.set(array)
return uniqueValues
end

function M.select_range(range)
local start_row, start_col, end_row, end_col = range[1][1], range[1][2], range[2][1], range[2][2]

local v_table = { charwise = "v", linewise = "V", blockwise = "<C-v>" }
selection_mode = selection_mode or "charwise"

-- Normalise selection_mode
if vim.tbl_contains(vim.tbl_keys(v_table), selection_mode) then selection_mode = v_table[selection_mode] end

-- enter visual mode if normal or operator-pending (no) mode
-- Why? According to https://learnvimscriptthehardway.stevelosh.com/chapters/15.html
-- If your operator-pending mapping ends with some text visually selected, Vim will operate on that text.
-- Otherwise, Vim will operate on the text between the original cursor position and the new position.
local mode = api.nvim_get_mode()
if mode.mode ~= selection_mode then
-- Call to `nvim_replace_termcodes()` is needed for sending appropriate command to enter blockwise mode
selection_mode = vim.api.nvim_replace_termcodes(selection_mode, true, true, true)
api.nvim_cmd({ cmd = "normal", bang = true, args = { selection_mode } }, {})
end

api.nvim_win_set_cursor(0, { start_row, start_col - 1 })
vim.cmd("normal! o")
api.nvim_win_set_cursor(0, { end_row, end_col - 1 })
end

function M.select_range(range)
---Selects a given range of text
---@param range table
---@param selection_mode? 'charwise'|'linewise'|'blockwise'|'v'|'V'|'<C-v>'
function M.select_range(range, selection_mode)
local start_row, start_col, end_row, end_col = range[1][1], range[1][2], range[2][1], range[2][2]

local v_table = { charwise = "v", linewise = "V", blockwise = "<C-v>" }
selection_mode = selection_mode or "charwise"

-- Normalise selection_mode
if vim.tbl_contains(vim.tbl_keys(v_table), selection_mode) then selection_mode = v_table[selection_mode] end
selection_mode = v_table[selection_mode] or selection_mode

-- enter visual mode if normal or operator-pending (no) mode
-- Why? According to https://learnvimscriptthehardway.stevelosh.com/chapters/15.html
Expand Down