diff options
| author | Christian Clason <christian.clason@uni-due.de> | 2020-06-04 20:23:03 +0200 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-06-04 14:23:03 -0400 | 
| commit | b7f3f11049c6847a2b0c4bbd89e8339036e00da6 (patch) | |
| tree | 3fafc3d82b4c9e86f0918dcce136b34f208b1894 /runtime/lua/vim/lsp/util.lua | |
| parent | 6f4f38cd54693ca99c887756e6179cc0775377d0 (diff) | |
| download | rneovim-b7f3f11049c6847a2b0c4bbd89e8339036e00da6.tar.gz rneovim-b7f3f11049c6847a2b0c4bbd89e8339036e00da6.tar.bz2 rneovim-b7f3f11049c6847a2b0c4bbd89e8339036e00da6.zip  | |
lsp: compute height of floating preview correctly for wrapped lines (#12380)
* take wrapping into account when computing float height
* factor out size calculation
* add test
* accept and pass through opts.wrap_at in floating_preview
* make padding configurable
* slightly refactor fancy_floating_markdown to make use of make_position
* padding using string.format
* move trim and pad to separate function
* nit
Co-authored-by: Hirokazu Hata <h.hata.ai.t@gmail.com>
* remove mention of backward compat
* make lint happy
Co-authored-by: Hirokazu Hata <h.hata.ai.t@gmail.com>
Diffstat (limited to 'runtime/lua/vim/lsp/util.lua')
| -rw-r--r-- | runtime/lua/vim/lsp/util.lua | 152 | 
1 files changed, 118 insertions, 34 deletions
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 02d233fb7b..49e2557c16 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -289,7 +289,7 @@ local function get_completion_word(item)    return item.label  end --- Some lanuguage servers return complementary candidates whose prefixes do not match are also returned. +-- Some language servers return complementary candidates whose prefixes do not match are also returned.  -- So we exclude completion candidates whose prefix does not match.  local function remove_unmatch_completion_items(items, prefix)    return vim.tbl_filter(function(item) @@ -614,13 +614,53 @@ function M.focusable_preview(unique_name, fn)    end)  end --- Convert markdown into syntax highlighted regions by stripping the code --- blocks and converting them into highlighted code. --- This will by default insert a blank line separator after those code block --- regions to improve readability. +--- Trim empty lines from input and pad left and right with spaces +--- +--@param contents table of lines to trim and pad +--@param opts dictionary with optional fields +--             - pad_left  amount of columns to pad contents at left (default 1) +--             - pad_right amount of columns to pad contents at right (default 1) +--@return contents table of trimmed and padded lines +function M._trim_and_pad(contents, opts) +  validate { +    contents = { contents, 't' }; +    opts = { opts, 't', true }; +  } +  opts = opts or {} +  local left_padding = (" "):rep(opts.pad_left or 1) +  local right_padding = (" "):rep(opts.pad_right or 1) +  contents = M.trim_empty_lines(contents) +  for i, line in ipairs(contents) do +    contents[i] = string.format('%s%s%s', left_padding, line:gsub("\r", ""), right_padding) +  end +  return contents +end + + + +--- Convert markdown into syntax highlighted regions by stripping the code +--- blocks and converting them into highlighted code. +--- This will by default insert a blank line separator after those code block +--- regions to improve readability. +--- The result is shown in a floating preview +--- TODO: refactor to separate stripping/converting and make use of open_floating_preview +--- +--@param contents table of lines to show in window +--@param opts dictionary with optional fields +--             - height    of floating window +--             - width     of floating window +--             - wrap_at   character to wrap at for computing height +--             - pad_left  amount of columns to pad contents at left +--             - pad_right amount of columns to pad contents at right +--             - separator insert separator after code block +--@return width,height size of float  function M.fancy_floating_markdown(contents, opts) -  local pad_left = opts and opts.pad_left -  local pad_right = opts and opts.pad_right +  validate { +    contents = { contents, 't' }; +    opts = { opts, 't', true }; +  } +  opts = opts or {} +    local stripped = {}    local highlights = {}    do @@ -654,31 +694,27 @@ function M.fancy_floating_markdown(contents, opts)        end      end    end -  local width = 0 -  for i, v in ipairs(stripped) do -    v = v:gsub("\r", "") -    if pad_left then v = (" "):rep(pad_left)..v end -    if pad_right then v = v..(" "):rep(pad_right) end -    stripped[i] = v -    width = math.max(width, #v) -  end -  if opts and opts.max_width then -    width = math.min(opts.max_width, width) -  end -  -- TODO(ashkan): decide how to make this customizable. -  local insert_separator = true +  -- Clean up and add padding +  stripped = M._trim_and_pad(stripped, opts) + +  -- Compute size of float needed to show (wrapped) lines +  opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0)) +  local width, height = M._make_floating_popup_size(stripped, opts) + +  -- Insert blank line separator after code block +  local insert_separator = opts.separator or true    if insert_separator then      for i, h in ipairs(highlights) do        h.start = h.start + i - 1        h.finish = h.finish + i - 1        if h.finish + 1 <= #stripped then          table.insert(stripped, h.finish + 1, string.rep("─", width)) +        height = height + 1        end      end    end    -- Make the floating window. -  local height = #stripped    local bufnr = api.nvim_create_buf(false, true)    local winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts))    vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped) @@ -719,33 +755,81 @@ function M.close_preview_autocmd(events, winnr)    api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)")  end -function M.open_floating_preview(contents, filetype, opts) +--- Compute size of float needed to show contents (with optional wrapping) +--- +--@param contents table of lines to show in window +--@param opts dictionary with optional fields +--             - height  of floating window +--             - width   of floating window +--             - wrap_at character to wrap at for computing height +--@return width,height size of float +function M._make_floating_popup_size(contents, opts)    validate {      contents = { contents, 't' }; -    filetype = { filetype, 's', true };      opts = { opts, 't', true };    }    opts = opts or {} -  -- Trim empty lines from the end. -  contents = M.trim_empty_lines(contents) -    local width = opts.width -  local height = opts.height or #contents +  local height = opts.height +  local line_widths = {} +    if not width then      width = 0      for i, line in ipairs(contents) do -      -- Clean up the input and add left pad. -      line = " "..line:gsub("\r", "")        -- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced. -      local line_width = vim.fn.strdisplaywidth(line) -      width = math.max(line_width, width) -      contents[i] = line +      line_widths[i] = vim.fn.strdisplaywidth(line) +      width = math.max(line_widths[i], width)      end -    -- Add right padding of 1 each. -    width = width + 1    end +  if not height then +    height = #contents +    local wrap_at = opts.wrap_at +    if wrap_at and width > wrap_at then +      height = 0 +      if vim.tbl_isempty(line_widths) then +        for _, line in ipairs(contents) do +          local line_width = vim.fn.strdisplaywidth(line) +          height = height + math.ceil(line_width/wrap_at) +        end +      else +        for i = 1, #contents do +          height = height + math.ceil(line_widths[i]/wrap_at) +        end +      end +    end +  end + +  return width, height +end + +--- Show contents in a floating window +--- +--@param contents table of lines to show in window +--@param filetype string of filetype to set for opened buffer +--@param opts dictionary with optional fields +--             - height    of floating window +--             - width     of floating window +--             - wrap_at   character to wrap at for computing height +--             - pad_left  amount of columns to pad contents at left +--             - pad_right amount of columns to pad contents at right +--@return bufnr,winnr buffer and window number of floating window or nil +function M.open_floating_preview(contents, filetype, opts) +  validate { +    contents = { contents, 't' }; +    filetype = { filetype, 's', true }; +    opts = { opts, 't', true }; +  } +  opts = opts or {} + +  -- Clean up input: trim empty lines from the end, pad +  contents = M._trim_and_pad(contents, opts) + +  -- Compute size of float needed to show (wrapped) lines +  opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0)) +  local width, height = M._make_floating_popup_size(contents, opts) +    local floating_bufnr = api.nvim_create_buf(false, true)    if filetype then      api.nvim_buf_set_option(floating_bufnr, 'filetype', filetype)  | 
