-- Copyright 2007-2020 Mitchell mitchell.att.foicica.com. See LICENSE.
-- Abbreviated environment and commands from Jay Gould.

local M = ui.command_entry

--[[ This comment is for LuaDoc.
---
-- Textadept's Command Entry.
-- It supports multiple modes that each have their own functionality, such as
-- running Lua code, searching for text incrementally, and filtering text
-- through shell commands.
-- @field height (number)
--   The height in pixels of the command entry.
module('ui.command_entry')]]

---
-- A metatable with typical platform-specific key bindings for text entries.
-- This metatable may be used to add basic editing and movement keys to command
-- entry modes. It is automatically added to command entry modes unless a
-- metatable was previously set.
-- @usage setmetatable(mode_keys, ui.command_entry.editing_keys)
-- @class table
-- @name editing_keys
M.editing_keys = {__index = {
  -- Note: cannot use `M.cut`, `M.copy`, etc. since M is never considered the
  -- global buffer.
  [not OSX and 'cx' or 'mx'] = function() M:cut() end,
  [not OSX and 'cc' or 'mc'] = function() M:copy() end,
  [not OSX and 'cv' or 'mv'] = function() M:paste() end,
  [not OSX and not CURSES and 'ca' or 'ma'] = function() M:select_all() end,
  [not OSX and 'cz' or 'mz'] = function() M:undo() end,
  [not OSX and 'cZ' or 'mZ'] = function() M:redo() end,
  [not OSX and 'cy' or '\0'] = function() M:redo() end,
  -- Movement keys.
  [(OSX or CURSES) and 'cf' or '\0'] = function() M:char_right() end,
  [(OSX or CURSES) and 'cb' or '\0'] = function() M:char_left() end,
  [(OSX or CURSES) and 'ca' or '\0'] = function() M:vc_home() end,
  [(OSX or CURSES) and 'ce' or '\0'] = function() M:line_end() end,
  [(OSX or CURSES) and 'cd' or '\0'] = function() M:clear() end
}}

-- Environment for abbreviated Lua commands.
-- @class table
-- @name env
local env = setmetatable({}, {
  __index = function(_, k)
    local f = buffer[k]
    if f and type(f) == 'function' then
      f = function(...) return buffer[k](buffer, ...) end
    elseif f == nil then
      f = view[k] or ui[k] or _G[k]
    end
    return f
  end,
  __newindex = function(self, k, v)
    local ok, value = pcall(function() return buffer[k] end)
    if ok and value ~= nil or not ok and value:find('write-only property') then
      buffer[k] = v return
    end
    if view[k] ~= nil then view[k] = v return end
    if ui[k] ~= nil then ui[k] = v return end
    rawset(self, k, v)
  end,
})

-- Executes string *code* as Lua code that is subject to an "abbreviated"
-- environment.
-- In this environment, the contents of the `buffer`, `view`, and `ui` tables
-- are also considered as global functions and fields.
-- Prints the results of expressions like in the Lua prompt. Also invokes bare
-- functions as commands.
-- @param code The Lua code to execute.
local function run_lua(code)
  if code:find('^=') then code = code:sub(2) end -- for compatibility
  local f, errmsg = load('return '..code, nil, 't', env)
  if not f then f, errmsg = load(code, nil, 't', env) end
  local result = assert(f, errmsg)()
  if type(result) == 'function' then result = result() end
  if type(result) == 'table' then
    local items = {}
    for k, v in pairs(result) do
      items[#items + 1] = tostring(k)..' = '..tostring(v)
    end
    table.sort(items)
    result = '{'..table.concat(items, ', ')..'}'
    if buffer.edge_column > 0 and #result > buffer.edge_column then
      local indent = string.rep(' ', buffer.tab_width)
      result = '{\n'..indent..table.concat(items, ',\n'..indent)..'\n}'
    end
  end
  if result ~= nil or code:find('^return ') then ui.print(result) end
  events.emit(events.UPDATE_UI)
end
args.register('-e', '--execute', 1, run_lua, 'Execute Lua code')

-- Shows a set of Lua code completions for the entry's text, subject to an
-- "abbreviated" environment where the `buffer`, `view`, and `ui` tables are
-- also considered as globals.
local function complete_lua()
  local line, pos = M:get_cur_line()
  local symbol, op, part = line:sub(1, pos):match('([%w_.]-)([%.:]?)([%w_]*)$')
  local ok, result = pcall((load('return ('..symbol..')', nil, 't', env)))
  if (not ok or type(result) ~= 'table') and symbol ~= '' then return end
  local cmpls = {}
  part = '^'..part
  if not ok or symbol == 'buffer' then
    local pool
    if not ok then
      -- Consider `buffer`, `view`, `ui` as globals too.
      pool = {buffer, view, ui, _G, _SCINTILLA.functions, _SCINTILLA.properties}
    else
      pool = op == ':' and {_SCINTILLA.functions} or
                           {_SCINTILLA.properties, _SCINTILLA.constants}
    end
    for i = 1, #pool do
      for k in pairs(pool[i]) do
        if type(k) == 'string' and k:find(part) then cmpls[#cmpls + 1] = k end
      end
    end
  end
  if ok then
    for k, v in pairs(result) do
      if type(k) == 'string' and k:find(part) and
         (op == '.' or type(v) == 'function') then
        cmpls[#cmpls + 1] = k
      end
    end
  end
  table.sort(cmpls)
  M:auto_c_show(#part - 1, table.concat(cmpls, ' '))
end

-- Key mode for entering Lua commands.
-- @class table
-- @name lua_mode_keys
local lua_mode_keys = {['\t'] = complete_lua}

---
-- Opens the command entry, subjecting it to any key bindings defined in table
-- *mode_keys*, highlighting text with lexer name *lexer*, and displaying
-- *height* number of lines at a time, and then when the `Enter` key is pressed,
-- closes the command entry and calls function *f* (if non-`nil`) with the
-- command entry's text as an argument.
-- By default with no arguments given, opens a Lua command entry.
-- The command entry does not respond to Textadept's default key bindings, but
-- instead to the key bindings defined in *mode_keys* and in
-- `ui.command_entry.editing_keys`.
-- @param f Optional function to call upon pressing `Enter` in the command
--   entry, ending the mode. It should accept the command entry text as an
--   argument.
-- @param mode_keys Optional table of key bindings to respond to. This is in
--   addition to the basic editing and movement keys defined in
--   `ui.command_entry.editing_keys`.
--   `Esc` and `Enter` are automatically defined to cancel and finish the
--   command entry, respectively.
--   This parameter may be omitted completely.
-- @param lexer Optional string lexer name to use for command entry text. The
--   default value is `'text'`.
-- @param height Optional number of lines to display in the command entry. The
--   default value is `1`.
-- @see editing_keys
-- @usage ui.command_entry.run(ui.print)
-- @name run
function M.run(f, mode_keys, lexer, height)
  if M:auto_c_active() then M:auto_c_cancel() end -- may happen in curses
  if not f and not mode_keys then
    f, mode_keys, lexer = run_lua, lua_mode_keys, 'lua'
  elseif type(mode_keys) == 'string' then
    mode_keys, lexer, height = {}, mode_keys, lexer
  elseif not mode_keys then
    mode_keys = {}
  end
  if not mode_keys['esc'] then mode_keys['esc'] = M.focus end -- hide
  mode_keys['\n'] = mode_keys['\n'] or function()
    if M:auto_c_active() then return false end -- allow Enter to autocomplete
    M.focus() -- hide
    if f then f((M:get_text())) end
  end
  if not getmetatable(mode_keys) then
    setmetatable(mode_keys, M.editing_keys)
  end
  M:select_all()
  M.focus()
  M:set_lexer(lexer or 'text')
  M.height = M:text_height(0) * (height or 1)
  keys._command_entry, keys.MODE = mode_keys, '_command_entry'
end

-- Redefine ui.command_entry.focus() to clear any current key mode on hide/show.
local orig_focus = M.focus
M.focus = function()
  keys.MODE = nil
  orig_focus()
end

-- Configure the command entry's default properties.
-- Also find the key binding for `textadept.editing.show_documentation` and use
-- it to show Lua documentation in the Lua command entry.
events.connect(events.INITIALIZED, function()
  M.h_scroll_bar, M.v_scroll_bar = false, false
  M.margin_width_n[0], M.margin_width_n[1], M.margin_width_n[2] = 0, 0, 0
  M.call_tip_position = true
  for key, f in pairs(keys) do
    if f == textadept.editing.show_documentation then
      lua_mode_keys[key] = function()
        -- Temporarily change _G.buffer since ui.command_entry is the "active"
        -- buffer.
        local orig_buffer = _G.buffer
        _G.buffer = ui.command_entry
        textadept.editing.show_documentation()
        _G.buffer = orig_buffer
      end
      break
    end
  end
end)

--[[ The function below is a Lua C function.

---
-- Opens the command entry.
-- @class function
-- @name focus
local focus
]]
