mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-07 21:15:03 +00:00

Some checks failed
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / build-vsc-assets (push) Has been cancelled
tinymist::ci / build-vscode (push) Has been cancelled
tinymist::ci / build-vscode-others (push) Has been cancelled
tinymist::ci / publish-vscode (push) Has been cancelled
tinymist::ci / build-binary (push) Has been cancelled
tinymist::ci / E2E Tests (darwin-arm64 on macos-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-22.04) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-2019) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-latest) (push) Has been cancelled
* fix: bad link * feat(neovim): init lsp * feat(neovim): add bootstrap script * build: add notice
294 lines
9.2 KiB
Lua
294 lines
9.2 KiB
Lua
local Tab = require 'std.nvim.tab'
|
|
local Window = require 'std.nvim.window'
|
|
local assert = require 'luassert'
|
|
local text = require 'std.text'
|
|
local fixtures = require 'spec.fixtures'
|
|
|
|
-- local progress = require 'lean.progress'
|
|
-- local util = require 'lean._util'
|
|
|
|
---@class LeanClientCapabilities : lsp.ClientCapabilities
|
|
---@field silentDiagnosticSupport? boolean Whether the client supports `DiagnosticWith.isSilent = true`.
|
|
|
|
---@class LeanClientConfig : vim.lsp.ClientConfig
|
|
---@field lean? LeanClientCapabilities
|
|
|
|
---Find the `vim.lsp.Client` attached to the given buffer.
|
|
---@param bufnr? number
|
|
---@return vim.lsp.Client?
|
|
function client_for(bufnr)
|
|
local clients = vim.lsp.get_clients { name = 'tinymist', bufnr = bufnr or 0 }
|
|
return clients[1]
|
|
end
|
|
|
|
local helpers = { _clean_buffer_counter = 1 }
|
|
|
|
---Feed some keystrokes into the current buffer, replacing termcodes.
|
|
function helpers.feed(contents, feed_opts)
|
|
feed_opts = feed_opts or 'mtx'
|
|
local to_feed = vim.api.nvim_replace_termcodes(contents, true, false, true)
|
|
vim.api.nvim_feedkeys(to_feed, feed_opts, true)
|
|
end
|
|
|
|
---Insert some text into the current buffer.
|
|
function helpers.insert(contents, feed_opts)
|
|
feed_opts = feed_opts or 'x'
|
|
helpers.feed('i' .. contents, feed_opts)
|
|
end
|
|
|
|
function helpers.all_lean_extmarks(buffer, start, end_)
|
|
local extmarks = {}
|
|
for namespace, ns_id in pairs(vim.api.nvim_get_namespaces()) do
|
|
if namespace:match '^lean.' then
|
|
vim.list_extend(
|
|
extmarks,
|
|
vim.api.nvim_buf_get_extmarks(buffer, ns_id, start, end_, { details = true })
|
|
)
|
|
end
|
|
end
|
|
return extmarks
|
|
end
|
|
|
|
---Move the cursor to a new location.
|
|
---
|
|
---Ideally this function wouldn't exist, and one would call `set_cursor`
|
|
---directly, but it does not fire `CursorMoved` autocmds.
|
|
---This function exists therefore to make tests which have slightly
|
|
---less implementation details in them (the manual firing of that autocmd).
|
|
---
|
|
---@param opts MoveCursorOpts
|
|
function helpers.move_cursor(opts)
|
|
local window = opts.window or Window:current()
|
|
window:move_cursor(opts.to)
|
|
end
|
|
|
|
---Search forward in the buffer for the given text.
|
|
---
|
|
---Fires `CursorMoved` if the cursor moves and fails if it does not.
|
|
function helpers.search(string)
|
|
local cursor = vim.api.nvim_win_get_cursor(0)
|
|
vim.fn.search(string)
|
|
assert.are_not.same(cursor, vim.api.nvim_win_get_cursor(0), 'Cursor did not move!')
|
|
vim.api.nvim_exec_autocmds('CursorMoved', {})
|
|
end
|
|
|
|
function helpers.wait_for_ready_lsp()
|
|
local succeeded, _ = vim.wait(15000, function()
|
|
local client = client_for(0)
|
|
return client and client.initialized or false
|
|
end)
|
|
assert.message('LSP server was never ready.').True(succeeded)
|
|
end
|
|
|
|
---Wait until a window that isn't one of the known ones shows up.
|
|
---@param known table
|
|
function helpers.wait_for_new_window(known)
|
|
local ids = vim
|
|
.iter(known)
|
|
:map(function(window)
|
|
return window.id
|
|
end)
|
|
:totable()
|
|
|
|
local new_window
|
|
local succeeded = vim.wait(1000, function()
|
|
new_window = vim.iter(vim.api.nvim_tabpage_list_wins(0)):find(function(window)
|
|
return not vim.tbl_contains(ids, window)
|
|
end)
|
|
return new_window
|
|
end)
|
|
assert.message('Never found a new window').is_true(succeeded)
|
|
return Window:from_id(new_window)
|
|
end
|
|
|
|
-- Even though we can delete a buffer, so should be able to reuse names,
|
|
-- we do this to ensure if a test fails, future ones still get new "files".
|
|
local function set_unique_name_so_we_always_have_a_separate_fake_file(bufnr)
|
|
local counter = helpers._clean_buffer_counter
|
|
helpers._clean_buffer_counter = helpers._clean_buffer_counter + 1
|
|
local unique_name = fixtures.project.child(('unittest-%d.typ'):format(counter))
|
|
vim.api.nvim_buf_set_name(bufnr, unique_name)
|
|
end
|
|
|
|
---Create a clean Lean buffer with the given contents.
|
|
---
|
|
---Waits for the LSP to be ready before proceeding with a given callback.
|
|
--
|
|
---Yes c(lean) may be a double entendre, and no I don't feel bad.
|
|
function helpers.clean_buffer(contents, callback)
|
|
local lines
|
|
|
|
-- Support a 1-arg version where we assume the contents is an empty buffer.
|
|
if callback == nil then
|
|
callback = contents
|
|
lines = {}
|
|
else
|
|
lines = vim.split(text.dedent(contents:gsub('^\n', '')):gsub('\n$', ''), '\n')
|
|
end
|
|
|
|
return function()
|
|
local bufnr = vim.api.nvim_create_buf(false, false)
|
|
set_unique_name_so_we_always_have_a_separate_fake_file(bufnr)
|
|
|
|
vim.bo[bufnr].swapfile = false
|
|
vim.bo[bufnr].filetype = 'typst'
|
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
|
|
|
-- isn't it fun how fragile the order of the below lines is, and how
|
|
-- BufWinEnter seems automatically called by `nvim_set_current_buf`, but
|
|
-- `BufEnter` seems not automatically called by `nvim_buf_call` so we
|
|
-- manually trigger it?
|
|
vim.api.nvim_set_current_buf(bufnr)
|
|
vim.api.nvim_exec_autocmds('BufEnter', { buffer = bufnr })
|
|
vim.api.nvim_buf_call(bufnr, callback)
|
|
|
|
-- FIXME: Deleting buffers seems good for keeping our tests clean, but is
|
|
-- broken on 0.11 with impossible to diagnose invalid buffer errors.
|
|
-- vim.api.nvim_buf_delete(bufnr, { force = true })
|
|
end
|
|
end
|
|
|
|
---Wait a few seconds for line diagnostics, erroring if none arrive.
|
|
function helpers.wait_for_line_diagnostics()
|
|
local params = vim.lsp.util.make_position_params(0, 'utf-16')
|
|
local succeeded, _ = vim.wait(15000, function()
|
|
if progress.at(params) == progress.Kind.processing then
|
|
return false
|
|
end
|
|
local diagnostics = util.lean_lsp_diagnostics { lnum = params.position.line }
|
|
return #diagnostics > 0
|
|
end)
|
|
assert.message('Waited for line diagnostics but none came.').True(succeeded)
|
|
end
|
|
|
|
function helpers.wait_for_filetype()
|
|
local result, _ = vim.wait(15000, function()
|
|
return vim.bo.filetype == 'typst'
|
|
end)
|
|
assert.message('filetype was never set').is_truthy(result)
|
|
end
|
|
|
|
---Assert about the current word.
|
|
local function has_current_word(_, arguments)
|
|
assert.is.equal(arguments[1], vim.fn.expand '<cword>')
|
|
return true
|
|
end
|
|
assert:register('assertion', 'current_word', has_current_word)
|
|
|
|
---Assert about the current line.
|
|
local function has_current_line(_, arguments)
|
|
assert.is.equal(arguments[1], vim.api.nvim_get_current_line())
|
|
return true
|
|
end
|
|
assert:register('assertion', 'current_line', has_current_line)
|
|
|
|
---Assert about the current cursor location.
|
|
local function has_current_cursor(_, arguments)
|
|
local window = arguments[1].window
|
|
if not window then
|
|
window = Window:current()
|
|
elseif type(window) == 'number' then
|
|
window = Window:from_id(window)
|
|
end
|
|
local got = window:cursor()
|
|
|
|
local column = arguments[1][2] or arguments[1].column or 0
|
|
local expected = { arguments[1][1] or got[1], column }
|
|
|
|
assert.are.same(expected, got)
|
|
return true
|
|
end
|
|
assert:register('assertion', 'current_cursor', has_current_cursor)
|
|
|
|
---Assert about the current tabpage.
|
|
local function has_current_tabpage(_, arguments)
|
|
assert.are.same(Tab:current(), arguments[1])
|
|
return true
|
|
end
|
|
assert:register('assertion', 'current_tabpage', has_current_tabpage)
|
|
|
|
---Assert about the current window.
|
|
local function has_current_window(_, arguments)
|
|
assert.are.same(Window:current(), arguments[1])
|
|
return true
|
|
end
|
|
assert:register('assertion', 'current_window', has_current_window)
|
|
|
|
local function _expected(arguments)
|
|
local expected = arguments[1][1] or arguments[1]
|
|
-- Handle cases where we're indeed checking for a real trailing newline.
|
|
local dedented = text.dedent(expected)
|
|
if dedented ~= expected then
|
|
expected = dedented:gsub('\n$', '')
|
|
end
|
|
return expected
|
|
end
|
|
|
|
---Assert about the entire buffer contents.
|
|
local function has_buf_contents(_, arguments)
|
|
local bufnr = arguments[1].bufnr or 0
|
|
local got = table.concat(vim.api.nvim_buf_get_lines(bufnr, 0, -1, false), '\n')
|
|
assert.is.equal(_expected(arguments), got)
|
|
return true
|
|
end
|
|
assert:register('assertion', 'contents', has_buf_contents)
|
|
|
|
assert:register('assertion', 'diff_contents', has_diff_contents)
|
|
|
|
local function has_highlighted_text(_, arguments)
|
|
local inspected = vim.inspect_pos(0)
|
|
local highlight = vim.iter(inspected.extmarks):find(function(mark)
|
|
return mark.opts.hl_group == 'widgetElementHighlight'
|
|
end)
|
|
|
|
assert.is_not_nil(highlight, ('No highlighted text found in %s'):format(vim.inspect(inspected)))
|
|
|
|
local got = vim.api.nvim_buf_get_text(
|
|
0,
|
|
highlight.row,
|
|
highlight.col,
|
|
highlight.end_row,
|
|
highlight.end_col,
|
|
{}
|
|
)[1]
|
|
assert.are.same(arguments[1], got)
|
|
return true
|
|
end
|
|
|
|
assert:register('assertion', 'highlighted_text', has_highlighted_text)
|
|
|
|
local function has_all(_, arguments)
|
|
local contents = arguments[1]
|
|
|
|
if type(contents) == 'table' then
|
|
contents = table.concat(contents, '\n')
|
|
end
|
|
local expected = arguments[2]
|
|
for _, string in pairs(expected) do
|
|
assert.has_match(string, contents, nil, true)
|
|
end
|
|
return true
|
|
end
|
|
|
|
assert:register('assertion', 'has_all', has_all)
|
|
|
|
---Assert a tabpage has the given windows open in it.
|
|
local function has_open_windows(_, arguments)
|
|
local expected
|
|
if arguments.n == 1 and type(arguments[1]) == 'table' then
|
|
expected = arguments[1]
|
|
expected.n = #expected
|
|
else
|
|
expected = arguments
|
|
end
|
|
local got = vim.api.nvim_tabpage_list_wins(0)
|
|
got.n = #got
|
|
table.sort(expected)
|
|
table.sort(got)
|
|
assert.are.same(expected, got)
|
|
return true
|
|
end
|
|
|
|
assert:register('assertion', 'windows', has_open_windows)
|
|
|
|
return helpers
|