neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

format_string.lua (4105B)


      1 local luaassert = require('luassert')
      2 
      3 local M = {}
      4 
      5 local SUBTBL = {
      6  '\\000',
      7  '\\001',
      8  '\\002',
      9  '\\003',
     10  '\\004',
     11  '\\005',
     12  '\\006',
     13  '\\007',
     14  '\\008',
     15  '\\t',
     16  '\\n',
     17  '\\011',
     18  '\\012',
     19  '\\r',
     20  '\\014',
     21  '\\015',
     22  '\\016',
     23  '\\017',
     24  '\\018',
     25  '\\019',
     26  '\\020',
     27  '\\021',
     28  '\\022',
     29  '\\023',
     30  '\\024',
     31  '\\025',
     32  '\\026',
     33  '\\027',
     34  '\\028',
     35  '\\029',
     36  '\\030',
     37  '\\031',
     38 }
     39 
     40 --- @param v any
     41 --- @return string
     42 local function format_float(v)
     43  -- On windows exponent appears to have three digits and not two
     44  local ret = ('%.6e'):format(v)
     45  local l, f, es, e = ret:match('^(%-?%d)%.(%d+)e([+%-])0*(%d%d+)$')
     46  return l .. '.' .. f .. 'e' .. es .. e
     47 end
     48 
     49 -- Formats Lua value `v`.
     50 --
     51 -- TODO(justinmk): redundant with vim.inspect() ?
     52 --
     53 -- "Nice table formatting similar to screen:snapshot_util()".
     54 -- Commit: 520c0b91a528
     55 function M.format_luav(v, indent, opts)
     56  opts = opts or {}
     57  local linesep = '\n'
     58  local next_indent_arg = nil
     59  local indent_shift = opts.indent_shift or '  '
     60  local next_indent
     61  local nl = '\n'
     62  if indent == nil then
     63    indent = ''
     64    linesep = ''
     65    next_indent = ''
     66    nl = ' '
     67  else
     68    next_indent_arg = indent .. indent_shift
     69    next_indent = indent .. indent_shift
     70  end
     71  local ret = ''
     72  if type(v) == 'string' then
     73    if opts.literal_strings then
     74      ret = v
     75    else
     76      local quote = opts.dquote_strings and '"' or "'"
     77      ret = quote
     78        .. tostring(v)
     79          :gsub(opts.dquote_strings and '["\\]' or "['\\]", '\\%0')
     80          :gsub('[%z\1-\31]', function(match)
     81            return SUBTBL[match:byte() + 1]
     82          end)
     83        .. quote
     84    end
     85  elseif type(v) == 'table' then
     86    if v == vim.NIL then
     87      ret = 'REMOVE_THIS'
     88    else
     89      local processed_keys = {}
     90      ret = '{' .. linesep
     91      local non_empty = false
     92      local format_luav = M.format_luav
     93      for i, subv in ipairs(v) do
     94        ret = ('%s%s%s,%s'):format(ret, next_indent, format_luav(subv, next_indent_arg, opts), nl)
     95        processed_keys[i] = true
     96        non_empty = true
     97      end
     98      for k, subv in pairs(v) do
     99        if not processed_keys[k] then
    100          if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then
    101            ret = ret .. next_indent .. k .. ' = '
    102          else
    103            ret = ('%s%s[%s] = '):format(ret, next_indent, format_luav(k, nil, opts))
    104          end
    105          ret = ret .. format_luav(subv, next_indent_arg, opts) .. ',' .. nl
    106          non_empty = true
    107        end
    108      end
    109      if nl == ' ' and non_empty then
    110        ret = ret:sub(1, -3)
    111      end
    112      ret = ret .. indent .. '}'
    113    end
    114  elseif type(v) == 'number' then
    115    if v % 1 == 0 then
    116      ret = ('%d'):format(v)
    117    else
    118      ret = format_float(v)
    119    end
    120  elseif type(v) == 'nil' then
    121    ret = 'nil'
    122  elseif type(v) == 'boolean' then
    123    ret = (v and 'true' or 'false')
    124  else
    125    print(type(v))
    126    -- Not implemented yet
    127    luaassert(false)
    128  end
    129  return ret
    130 end
    131 
    132 -- Like Python repr(), "{!r}".format(s)
    133 --
    134 -- Commit: 520c0b91a528
    135 function M.format_string(fmt, ...)
    136  local i = 0
    137  local args = { ... }
    138  local function getarg()
    139    i = i + 1
    140    return args[i]
    141  end
    142  local ret = fmt:gsub('%%[0-9*]*%.?[0-9*]*[cdEefgGiouXxqsr%%]', function(match)
    143    local subfmt = match:gsub('%*', function()
    144      return tostring(getarg())
    145    end)
    146    local arg = nil
    147    if subfmt:sub(-1) ~= '%' then
    148      arg = getarg()
    149    end
    150    if subfmt:sub(-1) == 'r' or subfmt:sub(-1) == 'q' then
    151      -- %r is like built-in %q, but it is supposed to single-quote strings and
    152      -- not double-quote them, and also work not only for strings.
    153      -- Builtin %q is replaced here as it gives invalid and inconsistent with
    154      -- luajit results for e.g. "\e" on lua: luajit transforms that into `\27`,
    155      -- lua leaves as-is.
    156      arg = M.format_luav(arg, nil, { dquote_strings = (subfmt:sub(-1) == 'q') })
    157      subfmt = subfmt:sub(1, -2) .. 's'
    158    end
    159    if subfmt == '%e' then
    160      return format_float(arg)
    161    else
    162      return subfmt:format(arg)
    163    end
    164  end)
    165  return ret
    166 end
    167 
    168 return M