Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature request: status notification during format operation #250

Closed
1 task done
rtgiskard opened this issue Dec 23, 2023 · 13 comments
Closed
1 task done

feature request: status notification during format operation #250

rtgiskard opened this issue Dec 23, 2023 · 13 comments
Labels
enhancement New feature or request P2 Not a priority. PRs welcome

Comments

@rtgiskard
Copy link
Contributor

Did you check existing requests?

  • I have searched the existing issues

Describe the feature

It's better to have a notification on the UI during the format operation, just like null-ls:

null-ls format

Find that the format operation may fail silently with only the first failed format get informed.

Provide background

No response

What is the significance of this feature?

nice to have

Additional details

No response

@rtgiskard rtgiskard added the enhancement New feature or request label Dec 23, 2023
@stevearc
Copy link
Owner

The first formatter failure will notify, but successive failures do not notify to avoid a noisy UI. Once the formatter successfully runs, it will reset the status and the next failure will display an error again. I haven't yet found a situation where I want to be notified of the same failure multiple times, which seems to be what you're asking for. Could you explain more about your use case and what the value is from more notifications?

@stevearc stevearc added the question Further information is requested label Dec 24, 2023
@rtgiskard
Copy link
Contributor Author

rtgiskard commented Dec 24, 2023

The first formatter failure will notify, but successive failures do not notify to avoid a noisy UI. Once the formatter successfully runs, it will reset the status and the next failure will display an error again. I haven't yet found a situation where I want to be notified of the same failure multiple times, which seems to be what you're asking for. Could you explain more about your use case and what the value is from more notifications?

I have a key binding for the format and disabled auto format. The formatter may fail for different reasons while I'm coding:

  1. debug the external formatter, maybe related to the formatter's config or system environment.
  2. edit the code (and there may be errors, for example: script with shfmt) and retry format

For UI notification like null-ls, it would be useful for long-running format operation that may take several seconds to finish. And with async mode, edit during formatting may invalidate the format task, with the indicator on UI, user will know the status of the format operation.

Currently, without formatting indicator, it'd be hard to tell that:
. Whether it fails silently or success
. Whether it's not configured for current filetype
. Whether it's finished or not

@github-actions github-actions bot removed the question Further information is requested label Dec 24, 2023
@stevearc
Copy link
Owner

I don't think this is something that should be built-in to conform, which is trying to be more of the low-level formatting API. It's something that could be built on top fairly easily, because the conform.format command takes a callback and the callback receives an error message. For example, you could do something like

local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { "Formatting..." })
local winid = vim.api.nvim_open_win(bufnr, false, {
  relative = "editor",
  anchor = "SE",
  row = vim.o.lines - 2,
  col = vim.o.columns,
  width = 20,
  height = 1,
  style = "minimal",
  border = "rounded",
  focusable = false,
  noautocmd = true,
})
vim.bo[bufnr].bufhidden = "wipe"
require("conform").format({ async = true, quiet = true, lsp_fallback = true }, function(err)
  vim.api.nvim_win_close(winid, true)
  if err then
    vim.notify(err, vim.log.levels.WARN)
  end
end)

@stevearc stevearc added the P2 Not a priority. PRs welcome label Dec 24, 2023
@rtgiskard
Copy link
Contributor Author

rtgiskard commented Dec 25, 2023

I'm trying to add a related wrapper over the format() call, the window solution you provide do works well, but not looks as pretty as null-ls (lsp message with noice mini view) 😅. To implement that, seems that I need to customize the route of notifications on noice, to be continued ..

@rtgiskard
Copy link
Contributor Author

rtgiskard commented Dec 28, 2023

I'm having a wrapper of format() like this

function M.format()
	local format_args = require('init.options').plugins.format_args
	format_args = vim.deepcopy(format_args)

	local have_fmt, fmt_util = pcall(require, 'conform')
	if have_fmt then
		-- get current formatter names
		local formatters = fmt_util.list_formatters()
		local fmt_names = {}

		if not vim.tbl_isempty(formatters) then
			fmt_names = vim.tbl_map(function(f)
				return f.name
			end, formatters)
		elseif fmt_util.will_fallback_lsp(format_args) then
			fmt_names = { 'lsp' }
		else
			return
		end

		local fmt_title = 'conform: ' .. table.concat(fmt_names, '/')
		local fmt_msg = fmt_title .. ' format ..'

		-- TODO: only show 2 echo msg before must enter command mode and exit??
		vim.api.nvim_echo({ { fmt_msg, 'Special' } }, false, {})

		-- format with auto close popup, and notify if err
		fmt_util.format(format_args, function(err)
			if err then
				vim.notify(err, vim.log.levels.WARN, { title = fmt_title })
			end
		end)
	else
		vim.lsp.buf.format(format_args)
	end
end

and have a route configured for noice:

routes = {
	{ -- show format msg in the mini view
		filter = {
			event = 'msg_show',
			kind = 'echo',
			find = 'conform: ',
		},
		view = 'mini',
	},
},

But I find no way to hide the message once format is done, the message disappears only on timeout.
Maybe the right way to do that is to register a callback for lsp progress, I'll check that later.

@rtgiskard
Copy link
Contributor Author

rtgiskard commented Dec 29, 2023

With fidget, finally I get what I want!

function M.init_msg_progress(title, msg)
	return require('fidget.progress').handle.create({
		title = title,
		message = msg,
		lsp_client = { name = '>>' }, -- the fake lsp client name
		percentage = nil, -- skip percentage field
	})
end

function M.format()
	local format_args = require('init.options').plugins.format_args
	format_args = vim.deepcopy(format_args)

	local have_fmt, fmt_util = pcall(require, 'conform')
	if have_fmt then
		-- get current formatter names
		local formatters = fmt_util.list_formatters()
		local fmt_names = {}

		if not vim.tbl_isempty(formatters) then
			fmt_names = vim.tbl_map(function(f)
				return f.name
			end, formatters)
		elseif fmt_util.will_fallback_lsp(format_args) then
			fmt_names = { 'lsp' }
		else
			return
		end

		local fmt_info = 'fmt: ' .. table.concat(fmt_names, '/')
		local msg_handle = M.init_msg_progress(fmt_info)

		-- format with auto close popup, and notify if err
		fmt_util.format(format_args, function(err)
			msg_handle:finish()
			if err then
				vim.notify(err, vim.log.levels.WARN, { title = fmt_info })
			end
		end)
	else
		vim.lsp.buf.format(format_args)
	end
end

Effects:

cc

css

@isak102
Copy link

isak102 commented Jan 1, 2024

Would this be possible to achieve with format_on_save too?

format_on_save = function(_)
    local conform = require("conform")
    local formatters = conform.list_formatters()
    local fmt_names = {}
    
    if not vim.tbl_isempty(formatters) then
        fmt_names = vim.tbl_map(function(f)
            return f.name
        end, formatters)
    elseif conform.will_fallback_lsp(format_opts) then
        fmt_names = { "lsp" }
    else
        return
    end
    
    local fmt_info = "fmt: " .. table.concat(fmt_names, "/")
    local msg_handle = init_msg_progress(fmt_info)
    
    return format_options,
        function(err)
            msg_handle:finish()
        end
end,

This is what I have right now, using the same init_msg_progress function as the reply above. But the start message and the done message come at the same time

@rtgiskard
Copy link
Contributor Author

rtgiskard commented Jan 2, 2024

Now it's done with the following wrapper and the the PR for noice

function M.format()
	local format_args = require('init.options').plugins.format_args
	format_args = vim.deepcopy(format_args)

	local have_fmt, fmt_util = pcall(require, 'conform')
	if have_fmt then
		-- get current formatter names
		local formatters = fmt_util.list_formatters()
		local fmt_names = {}

		if not vim.tbl_isempty(formatters) then
			fmt_names = vim.tbl_map(function(f)
				return f.name
			end, formatters)
		elseif fmt_util.will_fallback_lsp(format_args) then
			fmt_names = { 'lsp' }
		else
			return
		end

		-- notify with noice progress api
		local noice_progress = require('noice.lsp.progress')

		local fmt_info = 'fmt: ' .. table.concat(fmt_names, '/')
		local msg_id = noice_progress.progress_msg(fmt_info)

		-- format with auto close popup, and notify if err
		fmt_util.format(format_args, function(err)
			noice_progress.progress_msg_end(msg_id)
			if err then
				vim.notify(err, vim.log.levels.WARN, { title = fmt_info })
			end
		end)
	else
		vim.lsp.buf.format(format_args)
	end
end

@rtgiskard
Copy link
Contributor Author

rtgiskard commented Jan 2, 2024

@stevearc

This is what I have right now, using the same init_msg_progress function as the reply above. But the start message and the done message come at the same time

What do you mean by "come at the same time", two different messages or any screenshot?

The format operation generally finished very fast, and the begin message will be updated with the end message (it's progress message), if there is only one format operation, you should get just one message, either begin or end.

Now the progress message is done with noice (the PR is not merged at the time), just like null-ls before, for which all the status info appears in a single line.

This is two independent format operations, one is done, another is in process:
two independent format

@isak102
Copy link

isak102 commented Jan 2, 2024

What I meant was that when using your wrapper around manual formatting it works as expected. First a fidget notification is shown that formatting started and then once its completed it shows the check mark. But when using the code I sent (I want to see the fidget notification when using format_on_save), the start message is shown when the formatting is completed, and then the check mark comes instantly after. This is usually not noticable but when formatting takes a bit of time it is

@rtgiskard
Copy link
Contributor Author

rtgiskard commented Jan 3, 2024

For anyone reading this and prepare to use the wrapper above, there is a critical bug in the old wrapper which cause #260 , I have just updated (the 3rd line: vim.deepcopy()) the wrapper above in case it causes trouble.

@rtgiskard
Copy link
Contributor Author

rtgiskard commented Jan 3, 2024

What I meant was that when using your wrapper around manual formatting it works as expected. First a fidget notification is shown that formatting started and then once its completed it shows the check mark. But when using the code I sent (I want to see the fidget notification when using format_on_save), the start message is shown when the formatting is completed, and then the check mark comes instantly after. This is usually not noticable but when formatting takes a bit of time it is

Not sure for the case, not sure if I'm reading it correctly, if possible you may provide a screen recording :)
Quite busy at the time 😂

@stevearc
Copy link
Owner

stevearc commented Jan 6, 2024

Would this be possible to achieve with format_on_save too?

If you define format_on_save you can actually have it return a callback as its second return value and it will function the same as the callback parameter passed to conform.format. Modifying my example above:

local function open_progress_win()
  local bufnr = vim.api.nvim_create_buf(false, true)
  vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { "Formatting..." })
  local winid = vim.api.nvim_open_win(bufnr, false, {
    relative = "editor",
    anchor = "SE",
    row = vim.o.lines - 2,
    col = vim.o.columns,
    width = 20,
    height = 1,
    style = "minimal",
    border = "rounded",
    focusable = false,
    noautocmd = true,
  })
  vim.bo[bufnr].bufhidden = "wipe"
  return winid
end

local function format()
  require("conform").format({ async = true, quiet = true, lsp_fallback = true }, function(err)
    local winid = open_progress_win()
    vim.api.nvim_win_close(winid, true)
    if err then
      vim.notify(err, vim.log.levels.WARN)
    end
  end)
end

require("conform").setup({
  format_on_save = function()
    local winid = open_progress_win()
    return {
      lsp_fallback = true,
    }, function(err)
      vim.api.nvim_win_close(winid, true)
      if err then
        vim.notify(err, vim.log.levels.WARN)
      end
    end
  end,
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request P2 Not a priority. PRs welcome
Projects
None yet
Development

No branches or pull requests

3 participants