Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

neovim/lua/bugabinga/pivi/partial.lua

Raw
local ESCAPES = {
  n = '\n',
  t = '\t',
  r = '\r',
  b = '\b',
  f = '\f',
  ['"'] = '"',
  ['\\'] = '\\',
  ['/'] = '/',
}

local read_string = function( text, start )
  local pieces = {}
  local index = start
  local length = #text
  while index <= length do
    local char = text:sub( index, index )
    if char == '"' then
      return table.concat( pieces ), index + 1, true
    elseif char == '\\' then
      local escape = text:sub( index + 1, index + 1 )
      if escape == '' then break end
      if escape == 'u' then
        local code = text:sub( index + 2, index + 5 )
        if #code < 4 then break end
        local number = tonumber( code, 16 )
        if number then table.insert( pieces, vim.fn.nr2char( number ) ) end
        index = index + 6
      else
        table.insert( pieces, ESCAPES[escape] or escape )
        index = index + 2
      end
    else
      table.insert( pieces, char )
      index = index + 1
    end
  end
  return table.concat( pieces ), length + 1, false
end

--- Tolerantly reads string valued fields out of streaming tool arguments.
--- The result is presentation only: an incomplete value never describes a
--- validated edit, so callers must not mutate a buffer from it.
--- @param text string accumulated argument bytes, possibly truncated mid token
--- @return table result complete flag, decoded value when parseable, and partial fields
local parse = function( text )
  if type( text ) ~= 'string' or text == '' then
    return { complete = false, value = nil, fields = {}, }
  end

  local ok, value = pcall( vim.json.decode, text, { luanil = { object = true, array = true, }, } )
  if ok and type( value ) == 'table' then
    local fields = {}
    for key, item in pairs( value ) do
      if type( item ) == 'string' then fields[key] = item end
    end
    return { complete = true, value = value, fields = fields, }
  end

  local fields = {}
  local index = 1
  local length = #text
  while index <= length do
    local key_start = text:find( '"', index, true )
    if not key_start then break end
    local key, after_key, closed = read_string( text, key_start + 1 )
    if not closed then break end
    local separator = text:find( '^%s*:%s*', after_key )
    if separator then
      local _, value_start = text:find( '^%s*:%s*', after_key )
      local first = text:sub( value_start + 1, value_start + 1 )
      if first == '"' then
        local item, after_value = read_string( text, value_start + 2 )
        fields[key] = item
        index = after_value
      else
        index = value_start + 1
      end
    else
      index = after_key
    end
  end

  return { complete = false, value = nil, fields = fields, }
end

--- Describes streaming edit intent for ghost presentation.
local intent = function( tool_name, arguments_text )
  local result = parse( arguments_text )
  local fields = result.fields
  local path = fields.path or fields.file_path or fields.filePath
  local body = fields.new_text or fields.newText or fields.content or fields.text
  return {
    complete = result.complete,
    tool = tool_name,
    path = path,
    text = body,
    value = result.value,
  }
end

return {
  parse = parse,
  intent = intent,
}