How to send errors from Neovim LSP to ALE - neovim

ALE has an API for sending errors to it from other sources. I'm using this like shown below and it works for the first error. More specifically, if I make one edit that results in an LSP error, the error will be displayed by ALE in the location list. If I make any further keystrokes, the location list is emptied again.
I can also trigger this behavior if I disable LSP, load ALE, manually call ShowResults and then press any other key in insert mode.
My hypothesis is that ALEs linting in insert mode (per default) kicks in. If LSP is disabled and there are no linters registered for the current file type, it doesn't find any errors (obviously, there's nothing that could report any) and so it empties my location list again. So the steps are: open buffer without LSP, call ShowResults, location list opens, press i, press any key, location list is empty
Now I thought that it was because I wasn't implementing the full ALE API. So I added a hook (2nd snippet). I can verify that this hook is called and that it generates valid messages. If I keep the location list open I can even see all the expected errors flickering across the loclist. I can navigate to those errors with :lolder, but I don't know why ALE always adds another, empty location list, after the LSP is done doing its job.
Or maybe I'm doing something wrong here.
vim.lsp.handlers["textDocument/publishDiagnostics"] = function(_, _, params, client_id, _, config)
local uri = params.uri
local bufnr = vim.uri_to_bufnr(uri)
if not bufnr then
return
end
local diagnostics = params.diagnostics
if not diagnostics or vim.tbl_isempty(diagnostics) then
vim.fn['ale#other_source#ShowResults'](bufnr, "nvim-lsp", {})
return
end
-- Important so we can pull diagnostics from this table when ALE asks for
-- them
vim.lsp.diagnostic.save(diagnostics, bufnr, client_id)
local messages = {}
for _, event in ipairs(diagnostics) do
-- :h ale-localist-format
local msg = {}
msg.text = event.message
msg.lnum = event.range.start.line
msg.end_lnum = event.range["end"].line
msg.col = event.range.start.character
msg.end_col = event.range["end"].character
msg.bufnr = bufnr
msg.nr = event.severity
table.insert(messages, msg)
end
vim.fn['ale#other_source#ShowResults'](bufnr, "nvim-lsp", messages)
end
Second snippet which is called from the 'on_attach' function of the Neovim LSP
function ALEHook(bufnr)
vim.fn['ale#other_source#StartChecking'](bufnr, "nvim-lsp")
local diagnostics = vim.lsp.diagnostic.get(bufnr, client.id)
if not diagnostics or vim.tbl_isempty(diagnostics) then
vim.fn['ale#other_source#ShowResults'](bufnr, "nvim-lsp", {})
return
end
local messages = {}
for _, event in ipairs(diagnostics) do
local msg = {}
msg.text = event.message
msg.lnum = event.range.start.line
msg.end_lnum = event.range["end"].line
msg.col = event.range.start.character
msg.end_col = event.range["end"].character
msg.bufnr = bufnr
msg.nr = event.severity
table.insert(messages, msg)
end
vim.fn['ale#other_source#ShowResults'](bufnr, "nvim-lsp", messages)
end
api.nvim_command('augroup ALEHookLSP')
api.nvim_command('autocmd!')
api.nvim_command('autocmd User ALEWantResults call v:lua.ALEHook(g:ale_want_results_buffer)')
api.nvim_command('augroup END')

Related

Create a temporary, readonly buffer for test output

I want to create a Neovim plugin that automatically runs a test suite whenever a file is saved. Here's an overview:
If I save a Rust file, run cargo test
If there's not a buffer opened for the test output, automatically create the buffer
Mark that buffer as readonly
If I save another file, reuse the existing buffer
If the buffer is quit (e.g., :q), create a new buffer on the next test run
However, I'm currently facing three issues:
I cannot detect if when I quit the buffer
When the test output is longer than the buffer height, the buffer will not scroll down
When I want to quit Neovim, it asks me to save all these temporary buffers (which I don't want to do)
How can I resolve these issues? For reference, here's my code:
local buffer_number = -1
local function log(_, data)
if data then
vim.api.nvim_buf_set_lines(buffer_number, -1, -1, true, data)
end
end
local function open_buffer()
if buffer_number == -1 then
vim.api.nvim_command('botright vnew')
buffer_number = vim.api.nvim_get_current_buf()
end
end
local function autotest(pattern, command)
vim.api.nvim_create_autocmd("BufWritePost", {
group = vim.api.nvim_create_augroup("autotest", { clear = true }),
pattern = pattern,
callback = function()
open_buffer()
vim.api.nvim_buf_set_lines(buffer_number, 0, -1, true, {})
vim.fn.jobstart(command, {
stdout_buffered = true,
on_stdout = log,
on_stderr = log,
})
end
})
end
autotest("*.rs", { "cargo", "test" })
P.S. I know there are several existing plugins for testing. I'm creating my own because I want to learn how to write neovim plugins.
I gave it my best shot. I believe this addresses your questions and does what you're aiming to do.
local buffer_number = -1
local function log(_, data)
if data then
-- Make it temporarily writable so we don't have warnings.
vim.api.nvim_buf_set_option(buffer_number, "readonly", false)
-- Append the data.
vim.api.nvim_buf_set_lines(buffer_number, -1, -1, true, data)
-- Make readonly again.
vim.api.nvim_buf_set_option(buffer_number, "readonly", true)
-- Mark as not modified, otherwise you'll get an error when
-- attempting to exit vim.
vim.api.nvim_buf_set_option(buffer_number, "modified", false)
-- Get the window the buffer is in and set the cursor position to the bottom.
local buffer_window = vim.api.nvim_call_function("bufwinid", { buffer_number })
local buffer_line_count = vim.api.nvim_buf_line_count(buffer_number)
vim.api.nvim_win_set_cursor(buffer_window, { buffer_line_count, 0 })
end
end
local function open_buffer()
-- Get a boolean that tells us if the buffer number is visible anymore.
--
-- :help bufwinnr
local buffer_visible = vim.api.nvim_call_function("bufwinnr", { buffer_number }) ~= -1
if buffer_number == -1 or not buffer_visible then
-- Create a new buffer with the name "AUTOTEST_OUTPUT".
-- Same name will reuse the current buffer.
vim.api.nvim_command("botright vsplit AUTOTEST_OUTPUT")
-- Collect the buffer's number.
buffer_number = vim.api.nvim_get_current_buf()
-- Mark the buffer as readonly.
vim.opt_local.readonly = true
end
end
function autotest(pattern, command)
vim.api.nvim_create_autocmd("BufWritePost", {
pattern = pattern,
callback = function()
-- Open our buffer, if we need to.
open_buffer()
-- Clear the buffer's contents incase it has been used.
vim.api.nvim_buf_set_lines(buffer_number, 0, -1, true, {})
-- Run the command.
vim.fn.jobstart(command, {
stdout_buffered = true,
on_stdout = log,
on_stderr = log,
})
end
})
end
autotest("*.rs", { "cargo", "test" })
I added some comments to help explain each step but specifically for each of your problems:
I cannot detect if when I quit the buffer
Since you already have the buffer number, you can query to see if it's still visible. Just by using the same name when creating the new buffer we an reuse any existing ones.
When the test output is longer than the buffer height, the buffer will not scroll down
This was tricky, and I'm not sure if it's the best way, but I was able to query how many lines the buffer had and set the cursor of the buffer's window to the bottom.
When I want to quit Neovim, it asks me to save all these temporary buffers (which I don't want to do)
This is because the buffers are modified. Neovim by default doesn't want to make it easy to accidentally lose your work if you have active modifications. To get around that we can easily mark that it isn't modified!
Hope that helps.

After updating neovim to 0.81 and all plugins to latest release, autocmd to highlight words on cursor hold is not working on certain languages

I have an auto command highlight words on cursor hold and this autocmd gets attached to buffer when the LSP server has the capability of documentFormattingProvider.
After updating neovim to 0.81 and all the plugins and language servers. The behaviour of this autocmd is buggy.
on svelte files, it does not work and actually outputs error: method textDocument/documentHighlight is not supported by any of the servers registered for the current buffer (lsp:svelte)
on python, no highlighting, no errors, but running lua vim.lsp.buf.document_highlight() on cmd line while cursor on repetitive word, it does highlight (lsp:pyright,black)
on go, it is working! (lsp:gopls)
on typescript files, it is working (lsp:tsserver)
on dart it is working (lsp:dartls)
Here is my dotfiles
local function lsp_highlight_document(client)
-- Set autocommands conditional on server_capabilities
if client.server_capabilities.documentFormattingProvider then
vim.api.nvim_exec(
[[
augroup lsp_document_highlight
autocmd! * <buffer>
autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
augroup END
]],
false
)
end
end
I also tried (also not working):
local function lsp_highlight_document(client)
-- Set autocommands conditional on server_capabilities
if client.server_capabilities.documentFormattingProvider then
vim.api.nvim_create_augroup("lsp_document_highlight", { clear = true })
vim.api.nvim_clear_autocmds({ buffer = bufnr, group = "lsp_document_highlight" })
vim.api.nvim_create_autocmd("CursorHold", {
callback = vim.lsp.buf.document_highlight,
buffer = bufnr,
group = "lsp_document_highlight",
desc = "Document Highlight",
})
vim.api.nvim_create_autocmd("CursorMoved", {
callback = vim.lsp.buf.clear_references,
buffer = bufnr,
group = "lsp_document_highlight",
desc = "Clear All the References",
})
end
end
Both of these functions gets called here
options.on_attach = function(client, bufnr)
if client.name == "tsserver" then
client.server_capabilities.documentFormattingProvider = true
end
if client.name == "sumneko_lua" then
client.server_capabilities.documentFormattingProvider = false
end
lsp_highlight_document(client)
lsp_keymaps(bufnr)
end
local capabilities = vim.lsp.protocol.make_client_capabilities()
local status_ok, cmp_nvim_lsp = pcall(require, "cmp_nvim_lsp")
if not status_ok then
return
end
options.capabilities = cmp_nvim_lsp.default_capabilities(capabilities)
return options
Prior to this update, it was working fine in every language. Not sure what is missing.
Here is my config, which works for pyright, pylsp, lua-language-server etc.:
local api = vim.api
local lsp = vim.lsp
if client.server_capabilities.documentHighlightProvider then
vim.cmd([[
hi! link LspReferenceRead Visual
hi! link LspReferenceText Visual
hi! link LspReferenceWrite Visual
]])
local gid = api.nvim_create_augroup("lsp_document_highlight", { clear = true })
api.nvim_create_autocmd("CursorHold" , {
group = gid,
buffer = bufnr,
callback = function ()
lsp.buf.document_highlight()
end
})
api.nvim_create_autocmd("CursorMoved" , {
group = gid,
buffer = bufnr,
callback = function ()
lsp.buf.clear_references()
end
})
end
Works well on nvim 0.8.1. Try it to see if it works. If it does not work, I think there is something wrong with your config, you need to dig deeper to find why.

I can't get my Data Store V2 to work, It always returns nil

I read the Data Store V2 documentation on the Dev Hub and tried to make a similar code, but I can't get it to work. It doesn't have any errors other than Attempt to index nil with Version because it can't get the Data. It returns Succes = true, and CurrentNumber, KeyInfo = nil. There are no errors when saving the data and the Succes variable is always returned true. I enabled the security configuration to allow data stores, the game is public and I tested it both on studio and the actual game(on Roblox). The code is in a script in the Server Script Service Here's the code:
local DataStoreService = game:GetService('DataStoreService')
local Players = game:GetService('Players')
-- Data Stores:
-- Enables Data Stores V2
local Options = Instance.new('DataStoreOptions')
Options:SetExperimentalFeatures({["v2"] = true})
local NumberStore:DataStore = DataStoreService:GetDataStore("PlayerNumber", "global", Options)
local SetOptions = Instance.new('DataStoreSetOptions')
SetOptions:SetMetadata({['PlayerNumberType'] = 'Int'})
local function SaveNumberData(Player:Player)
local Number = 1
local PlayerKey = 'Player_1234'
local Sucess, Errormessage = pcall(function()
NumberStore:SetAsync(PlayerKey, Number, {Player.UserId}, SetOptions)
end)
if not Sucess then
print(Errormessage)
else
print('Data Saved')
end
end
local function LoadPlayerData(Player:Player)
local PlayerKey = 'Player_1234'
local Sucess, CurrentNumber, KeyInfo:DataStoreKeyInfo = pcall(function()
NumberStore:GetAsync(PlayerKey)
end)
if Sucess then
print(CurrentNumber)
print(KeyInfo.Version)
print(KeyInfo.CreatedTime)
print(KeyInfo.UpdatedTime)
print(KeyInfo:GetUserIds())
print(KeyInfo:GetMetadata())
else
print(CurrentNumber)
end
end
Players.PlayerRemoving:Connect(SaveNumberData)
Players.PlayerAdded:Connect(LoadPlayerData)
I found the answer, I forgot to put return before NumberStore:GetAsync(PlayerKey)

MIKMIDI: writing events on a track produces a warning

I'm trying to write a program to shift the key of a midi file. Basically, I just need to shift every note event by a given amount and live the rest unchanged. I found it easy to use MIKMIDI to read, parse, modify and write back the stream.
Unfortunately, I have a problem that I'm unable to solve. I've a loop in which I select the note events and add/subtract the desired shift value, but when I append the event in the output track I get a message from the MIKMIDI library:
"Warning: attempted to insert a NULL event".
The code I wrote is the following:
for event in inputTrack.events {
if event.eventType == .midiNoteMessage {
var tmpData = event.data
if (event.data[0] != 9) { // skip percussion channel
tmpData[1] = event.data[1] - shift
}
let outEvent = MIKMIDIEvent(timeStamp: event.timeStamp, midiEventType: .midiNoteMessage, data: tmpData)!
outputSeq.tracks[i].events.append(outEvent)
}
else {
outSeq.tracks[i].events.append(event)
}
}
BTW, the code works perfectly (the midi file is plays as expected), it is just that it takes minutes to execute in debugging mode due to the infinite sequence of warning messages printed in the debug screen.
Thanks!

How to mail a screen captured image using corona SDK

I am new to corona SDK, but I've managed to capture my app scene using the following code:
local function captureArea()
local myCaptureImage = display.captureBounds(display.currentStage.contentBounds, true)
myCaptureImage:removeSelf()
myCaptureImage = nil
end
bg:addEventListener("tap",captureArea)
This works perfectly.
Now I need to send the captured image(with a specific name, say: screen_1.png) to my friend via email. I've used Composing E-mail and SMS for refference, but I fail to understand how I can add this saved image in the attachment field of mail options.
Please give me a proper solution that how can I attach and send the above saved image via email.
display.captureBounds is good for saving the whole screen to the directory. But it usually saves the file with increase in last index. So it may be difficult to read them correctly. So I prefer display.save. But it is not a straight way.
For doing this, you have to:
First create a localgroup.
Then add the screen objects to that group.
Return the display group
Use display.save to save the entire group displayed.
Create mail option and add attachment image from baseDirectory
Call mail Popup
I am giving a sample here:
-- creating the display group --
local localGroup = display.newGroup()
-- creating display objects and adding it to the group --
local bg = display.newRect(0,0,_w,_h)
bg.x = 160
bg.y = 240
bg:setFillColor(150)
localGroup:insert(bg)
local rect = display.newRect(0,0,50,50)
rect.x = 30+math.random(260)
rect.y = 30+math.random(420)
localGroup:insert(rect)
-- Then do as follows --
local function takePhoto_andSendMail()
-- take screen shot to baseDirectory --
local baseDir = system.DocumentsDirectory
display.save( localGroup, "myScreenshot.jpg", baseDir )
-- Create mail options --
local options =
{
to = { "krishnarajsalim#gmail.com",},
subject = "My Level",
body = "Add this...",
attachment =
{
{ baseDir=system.DocumentsDirectory, filename="myScreenshot.jpg", type="image" },
},
}
-- Send mail --
native.showPopup("mail", options)
end
rect:addEventListener("tap",takePhoto_andSendMail)
This will do it...
Keep coding........ :)