local format = require 'bugabinga.cmdline.format' local position = require 'bugabinga.cmdline.position' local cursor = require 'bugabinga.cmdline.cursor' local highlight = require 'bugabinga.cmdline.highlight' local ns = vim.api.nvim_create_namespace 'bugabinga.cmdline' local hl_ns = vim.api.nvim_create_namespace 'bugabinga.cmdline.hl' local prompt_ns = vim.api.nvim_create_namespace 'bugabinga.cmdline.prompt' local state = { buffer = -1, window = -1, current_fmt = nil, text = '', firstc = '', prompt = '', } local function get_buf() if vim.api.nvim_buf_is_valid( state.buffer ) then return state.buffer end state.buffer = vim.api.nvim_create_buf( false, true ) vim.bo[state.buffer].buflisted = false vim.bo[state.buffer].filetype = 'bugabinga_cmdline' return state.buffer end local function get_win( buf, width, fmt ) if vim.api.nvim_win_is_valid( state.window ) then return state.window end local screen_pos = position.at_cursor( width or vim.o.columns ) state.window = vim.api.nvim_open_win( buf, false, { relative = 'editor', width = width or vim.o.columns, height = 1, row = screen_pos.row, col = 0, style = 'minimal', border = vim.g.border_style, focusable = false, zindex = 50, } ) vim.wo[state.window].cursorline = false vim.wo[state.window].winbar = '' vim.wo[state.window].foldenable = false vim.wo[state.window].winhighlight = format.winhighlight( fmt.type, 'NormalFloat' ) return state.window end local function apply_icon( buf, fmt ) vim.api.nvim_buf_clear_namespace( buf, ns, 0, -1 ) local icon_hl = format.icon_highlight( fmt.type ) vim.api.nvim_buf_set_extmark( buf, ns, 0, 0, { virt_text = { { fmt.icon, icon_hl } }, virt_text_pos = 'overlay', } ) end local function show( cmdline_state ) local text = cmdline_state:text() local firstc = cmdline_state.firstc local prompt = cmdline_state.prompt state.text = text state.firstc = firstc state.prompt = prompt local fmt = format.detect( firstc, text, prompt ) local fmt_changed = not state.current_fmt or fmt.type ~= state.current_fmt.type state.current_fmt = fmt local display_text = ' ' .. text .. ' ' local width = vim.o.columns local buf = get_buf() local win = get_win( buf, width, fmt ) local is_new = not position.get_cmdline_anchor() if is_new then local screen_pos = position.at_cursor( width ) vim.api.nvim_win_set_config( win, { relative = 'editor', width = width, height = 1, row = screen_pos.row, col = 0, } ) local anchor = { screenpos = { row = screen_pos.row, col = 0 }, cursor = 0, buf = buf, win = win, bufpos = { row = 0, col = 0 }, } position.set_cmdline_anchor( anchor ) vim.g.ui_cmdline_pos = { anchor.screenpos.row, anchor.screenpos.col } end if fmt_changed then vim.wo[win].winhighlight = format.winhighlight( fmt.type, 'NormalFloat' ) end vim.api.nvim_buf_set_lines( buf, 0, -1, false, { display_text } ) apply_icon( buf, fmt ) highlight.apply( buf, hl_ns, text, fmt.lang, 0, 2 ) if fmt.type == 'input' and prompt and prompt ~= '' then vim.api.nvim_buf_clear_namespace( buf, prompt_ns, 0, -1 ) local max_prompt_width = math.floor( vim.o.columns * format.PROMPT_MAX_RATIO ) local prompt_is_single_line = not prompt:match '\n' local prompt_fits = vim.fn.strdisplaywidth( prompt ) <= max_prompt_width if prompt_is_single_line and prompt_fits then local title_text = ' ' .. prompt .. ' ' vim.api.nvim_win_set_config( win, { border = vim.g.border_style, title = { { title_text, 'CmdlineBorderInput' } }, title_pos = 'left', } ) else local prompt_chunks = format.prompt_lines( prompt, max_prompt_width ) local virt_line_tuples = {} for _, chunk in ipairs( prompt_chunks ) do vim.notify( 'adding prompt chunk', chunk ) table.insert( virt_line_tuples, { { chunk, 'NormalFloat' } } ) end vim.api.nvim_buf_set_extmark( buf, prompt_ns, 0, 0, { virt_lines = virt_line_tuples, virt_lines_above = true, } ) vim.api.nvim_win_set_config( win, { border = vim.g.border_style, title = { { ' ', 'Normal' } }, title_pos = 'left', height = #prompt_chunks + 1, } ) end end local cursor_byte = cmdline_state:cursor_byte() local cursor_col = 2 + cursor_byte local anchor = position.get_cmdline_anchor() if anchor then anchor.cursor = cursor_col anchor.bufpos.col = cursor_col position.set_cmdline_anchor( anchor ) end cursor.fix( win, cursor_col ) end local function hide() if vim.api.nvim_win_is_valid( state.window ) then vim.api.nvim_buf_clear_namespace( state.buffer, prompt_ns, 0, -1 ) vim.api.nvim_win_hide( state.window ) end state.window = -1 state.current_fmt = nil state.text = '' state.firstc = '' state.prompt = '' position.set_cmdline_anchor( nil ) vim.g.ui_cmdline_pos = nil end local function update_cursor( cmdline_state ) if not vim.api.nvim_win_is_valid( state.window ) then return end local cursor_byte = cmdline_state:cursor_byte() local cursor_col = 2 + cursor_byte local anchor = position.get_cmdline_anchor() if anchor then anchor.cursor = cursor_col position.set_cmdline_anchor( anchor ) end cursor.fix( state.window, cursor_col ) end local function get_format_type() return state.current_fmt and state.current_fmt.type or 'cmdline' end local function get_text() return state.text end return { show = show, hide = hide, update_cursor = update_cursor, get_format_type = get_format_type, get_text = get_text, }