Download
1507 lines 46.7 KiBLuaModified 2018-05-03 raw
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507
local C = require("curses")
local re = require('re')
local line_matcher = re.compile('lines<-{| line ("\n" line)* |} line<-{[^\n]*}')
local ldb
local AUTO = { }
local PARENT = { }
local _error = error
local _assert = assert
local callstack_range
callstack_range = function()
  local min, max = 0, -1
  for i = 1, 999 do
    local info = debug.getinfo(i, 'f')
    if not info then
      min = i - 1
      break
    end
    if info.func == ldb.run_debugger then
      min = i + 2
      break
    end
  end
  for i = min, 999 do
    local info = debug.getinfo(i, 'f')
    if not info or info.func == ldb.guard then
      max = i - 3
      break
    end
  end
  return min, max
end
local wrap_text
wrap_text = function(text, width)
  local lines = { }
  local _list_0 = line_matcher:match(text)
  for _index_0 = 1, #_list_0 do
    local line = _list_0[_index_0]
    while #line > width do
      table.insert(lines, line:sub(1, width))
      line = line:sub(width + 1, -1)
      if #line == 0 then
        line = nil
      end
    end
    if line then
      table.insert(lines, line)
    end
  end
  return lines
end
local Color
do
  local color_index = 0
  local existing = { }
  local make_color
  make_color = function(fg, bg)
    if fg == nil then
      fg = -1
    end
    if bg == nil then
      bg = -1
    end
    local key = tostring(fg) .. "," .. tostring(bg)
    if not (existing[key]) then
      color_index = color_index + 1
      C.init_pair(color_index, fg, bg)
      existing[key] = C.color_pair(color_index)
    end
    return existing[key]
  end
  local color_lang = re.compile([[        x <- {|
            {:attrs: {| {attr} (" " {attr})* |} :}
            / (
                ({:bg: "on " {color} :} / ({:fg: color :} (" on " {:bg: color :})?))
                {:attrs: {| (" " {attr})* |} :})
        |}
        attr <- "blink" / "bold" / "dim" / "invis" / "normal" / "protect" / "reverse" / "standout" / "underline" / "altcharset"
        color <- "black" / "blue" / "cyan" / "green" / "magenta" / "red" / "white" / "yellow" / "default"
    ]])
  C.COLOR_DEFAULT = -1
  Color = function(s)
    if s == nil then
      s = "default"
    end
    local t = _assert(color_lang:match(s), "Invalid color: " .. tostring(s))
    if t.fg then
      t.fg = C["COLOR_" .. t.fg:upper()]
    end
    if t.bg then
      t.bg = C["COLOR_" .. t.bg:upper()]
    end
    local c = make_color(t.fg, t.bg)
    local _list_0 = t.attrs
    for _index_0 = 1, #_list_0 do
      local a = _list_0[_index_0]
      c = c | C["A_" .. a:upper()]
    end
    return c
  end
end
local Pad
do
  local _class_0
  local _base_0 = {
    configure_size = function(self, height, width)
      self.height, self.width = height, width
      self._height = math.max(#self.columns[1], 1)
      if self.height == AUTO then
        self.height = self._height + 2
      end
      self._width = #self.columns - 1
      for i, col in ipairs(self.columns) do
        local col_width = 0
        for _index_0 = 1, #col do
          local chunk = col[_index_0]
          col_width = math.max(col_width, #chunk)
        end
        self._width = self._width + col_width
      end
      self._width = math.max(self._width, 1)
      if self.width == AUTO then
        self.width = self._width + 2
      end
    end,
    setup_chstr = function(self, i)
      local chstr = _assert(self.chstrs[i], "Failed to find chstrs[" .. tostring(i) .. "]")
      local x = 0
      for c = 1, #self.columns do
        local attr = self.colors[c](self, i)
        local chunk = self.columns[c][i]
        chstr:set_str(x, chunk, attr)
        x = x + #chunk
        if #chunk < self.column_widths[c] then
          chstr:set_str(x, " ", attr, self.column_widths[c] - #chunk)
          x = x + (self.column_widths[c] - #chunk)
        end
        if c < #self.columns then
          chstr:set_ch(x, C.ACS_VLINE, Color("black bold"))
          x = x + 1
        end
      end
      self._pad:mvaddchstr(i - 1, 0, chstr)
      self.dirty = true
    end,
    set_active = function(self, active)
      if active == self.active then
        return 
      end
      self.active = active
      self._frame:attrset(active and self.active_frame or self.inactive_frame)
      self.dirty = true
    end,
    select = function(self, i)
      if #self.columns[1] == 0 then
        i = nil
      end
      if i == self.selected then
        return self.selected
      end
      local old_y, old_x = self.scroll_y, self.scroll_x
      if i ~= nil then
        i = math.max(1, math.min(#self.columns[1], i))
      end
      local old_selected
      old_selected, self.selected = self.selected, i
      if old_selected then
        self:setup_chstr(old_selected)
      end
      if self.selected then
        self:setup_chstr(self.selected)
        local scrolloff = 3
        if self.selected > self.scroll_y + (self.height - 2) - scrolloff then
          self.scroll_y = self.selected - (self.height - 2) + scrolloff
        elseif self.selected < self.scroll_y + scrolloff then
          self.scroll_y = self.selected - scrolloff
        end
        self.scroll_y = math.max(1, math.min(self._height, self.scroll_y))
      end
      if self.scroll_y == old_y then
        local w = math.min(self.width - 2, self._width)
        if old_selected and self.scroll_y <= old_selected and old_selected <= self.scroll_y + self.height - 2 then
          self._pad:pnoutrefresh(old_selected - 1, self.scroll_x - 1, self.y + 1 + (old_selected - self.scroll_y), self.x + 1, self.y + 1 + (old_selected - self.scroll_y) + 1, self.x + w)
        end
        if self.selected and self.scroll_y <= self.selected and self.selected <= self.scroll_y + self.height - 2 then
          self._pad:pnoutrefresh(self.selected - 1, self.scroll_x - 1, self.y + 1 + (self.selected - self.scroll_y), self.x + 1, self.y + 1 + (self.selected - self.scroll_y) + 1, self.x + w)
        end
      else
        self.dirty = true
      end
      if self.on_select then
        self:on_select(self.selected)
      end
      return self.selected
    end,
    scroll = function(self, dy, dx)
      local old_y, old_x = self.scroll_y, self.scroll_x
      if self.selected ~= nil then
        self:select(self.selected + (dy or 0))
      else
        self.scroll_y = math.max(1, math.min(self._height - (self.height - 2 - 1), self.scroll_y + (dy or 0)))
      end
      self.scroll_x = math.max(1, math.min(self._width - (self.width - 2 - 1), self.scroll_x + (dx or 0)))
      if self.scroll_y ~= old_y or self.scroll_x ~= old_x then
        self.dirty = true
      end
    end,
    refresh = function(self, force)
      if force == nil then
        force = false
      end
      if not force and not self.dirty then
        return 
      end
      self._frame:border(C.ACS_VLINE, C.ACS_VLINE, C.ACS_HLINE, C.ACS_HLINE, C.ACS_ULCORNER, C.ACS_URCORNER, C.ACS_LLCORNER, C.ACS_LRCORNER)
      if self.label then
        self._frame:mvaddstr(0, math.floor((self.width - #self.label - 2) / 2), " " .. tostring(self.label) .. " ")
      end
      self._frame:refresh()
      local h, w = math.min(self.height - 2, self._height), math.min(self.width - 2, self._width)
      self._pad:pnoutrefresh(self.scroll_y - 1, self.scroll_x - 1, self.y + 1, self.x + 1, self.y + h, self.x + w)
      self.dirty = false
    end,
    keypress = function(self, c)
      local _exp_0 = c
      if C.KEY_DOWN == _exp_0 or C.KEY_SR == _exp_0 or ("j"):byte() == _exp_0 then
        return self:scroll(1, 0)
      elseif ('J'):byte() == _exp_0 then
        return self:scroll(10, 0)
      elseif C.KEY_UP == _exp_0 or C.KEY_SF == _exp_0 or ("k"):byte() == _exp_0 then
        return self:scroll(-1, 0)
      elseif ('K'):byte() == _exp_0 then
        return self:scroll(-10, 0)
      elseif C.KEY_RIGHT == _exp_0 or ("l"):byte() == _exp_0 then
        return self:scroll(0, 1)
      elseif ("L"):byte() == _exp_0 then
        return self:scroll(0, 10)
      elseif C.KEY_LEFT == _exp_0 or ("h"):byte() == _exp_0 then
        return self:scroll(0, -1)
      elseif ("H"):byte() == _exp_0 then
        return self:scroll(0, -10)
      end
    end,
    erase = function(self)
      self.dirty = true
      self._frame:erase()
      return self._frame:refresh()
    end,
    __gc = function(self)
      self._frame:close()
      return self._pad:close()
    end
  }
  _base_0.__index = _base_0
  _class_0 = setmetatable({
    __init = function(self, label, y, x, height, width, ...)
      self.label, self.y, self.x = label, y, x
      self.scroll_y, self.scroll_x = 1, 1
      self.selected = nil
      self.columns = { }
      self.column_widths = { }
      self.active_frame = Color("yellow bold")
      self.inactive_frame = Color("blue dim")
      self.colors = { }
      for i = 1, select('#', ...) - 1, 2 do
        local col = select(i, ...)
        table.insert(self.columns, col)
        local w = 0
        for _index_0 = 1, #col do
          local chunk = col[_index_0]
          w = math.max(w, #chunk)
        end
        table.insert(self.column_widths, w)
        local color_fn = select(i + 1, ...) or (function(self, i)
          return Color()
        end)
        _assert(type(color_fn) == 'function', "Invalid color function type: " .. tostring(type(color_fn)))
        table.insert(self.colors, color_fn)
      end
      self:configure_size(height, width)
      self._frame = C.newwin(self.height, self.width, self.y, self.x)
      self._frame:immedok(true)
      self._pad = C.newpad(self._height, self._width)
      self._pad:scrollok(true)
      self:set_active(false)
      self.chstrs = { }
      for i = 1, #self.columns[1] do
        self.chstrs[i] = C.new_chstr(self._width)
        self:setup_chstr(i)
      end
      self.dirty = true
    end,
    __base = _base_0,
    __name = "Pad"
  }, {
    __index = _base_0,
    __call = function(cls, ...)
      local _self_0 = setmetatable({}, _base_0)
      cls.__init(_self_0, ...)
      return _self_0
    end
  })
  _base_0.__class = _class_0
  Pad = _class_0
end
local NumberedPad
do
  local _class_0
  local _parent_0 = Pad
  local _base_0 = { }
  _base_0.__index = _base_0
  setmetatable(_base_0, _parent_0.__base)
  _class_0 = setmetatable({
    __init = function(self, label, y, x, height, width, ...)
      self.label, self.y, self.x = label, y, x
      local col1 = select(1, ...)
      local fmt = "%" .. tostring(#tostring(#col1)) .. "d"
      local line_nums
      do
        local _accum_0 = { }
        local _len_0 = 1
        for i = 1, #col1 do
          _accum_0[_len_0] = fmt:format(i)
          _len_0 = _len_0 + 1
        end
        line_nums = _accum_0
      end
      local cols = {
        line_nums,
        (function(self, i)
          return i == self.selected and Color() or Color("yellow")
        end),
        ...
      }
      return _class_0.__parent.__init(self, self.label, self.y, self.x, height, width, unpack(cols))
    end,
    __base = _base_0,
    __name = "NumberedPad",
    __parent = _parent_0
  }, {
    __index = function(cls, name)
      local val = rawget(_base_0, name)
      if val == nil then
        local parent = rawget(cls, "__parent")
        if parent then
          return parent[name]
        end
      else
        return val
      end
    end,
    __call = function(cls, ...)
      local _self_0 = setmetatable({}, _base_0)
      cls.__init(_self_0, ...)
      return _self_0
    end
  })
  _base_0.__class = _class_0
  if _parent_0.__inherited then
    _parent_0.__inherited(_parent_0, _class_0)
  end
  NumberedPad = _class_0
end
local expansions = { }
local TOP_LOCATION, KEY, VALUE = { }, { }, { }
local locations = { }
local Location
Location = function(old_loc, kind, key)
  if old_loc == nil then
    return TOP_LOCATION
  end
  if not (locations[old_loc]) then
    locations[old_loc] = { }
  end
  if not (locations[old_loc][kind]) then
    locations[old_loc][kind] = { }
  end
  if not (locations[old_loc][kind][key]) then
    locations[old_loc][kind][key] = {
      old_loc = old_loc,
      kind = kind,
      key = key
    }
  end
  return locations[old_loc][kind][key]
end
local expand
expand = function(kind, key, location)
  expansions[Location(location, kind, key)] = true
end
local collapse
collapse = function(kind, key, location)
  expansions[Location(location, kind, key)] = nil
end
local is_key_expanded
is_key_expanded = function(location, key)
  return expansions[Location(location, KEY, key)]
end
local is_value_expanded
is_value_expanded = function(location, key)
  return expansions[Location(location, VALUE, key)]
end
local TYPE_COLORS = setmetatable({ }, {
  __index = 0
})
local colored_repr
colored_repr = function(x, width, depth)
  if depth == nil then
    depth = 2
  end
  depth = depth - 1
  local x_type = type(x)
  if x_type == 'table' then
    if next(x) == nil then
      return {
        "{}",
        TYPE_COLORS.table
      }
    end
    if depth == 0 then
      return {
        "{",
        TYPE_COLORS.table,
        "...",
        Color('white'),
        "}",
        TYPE_COLORS.table
      }
    end
    local ret = {
      "{",
      TYPE_COLORS.table
    }
    local i = 1
    for k, v in pairs(x) do
      if k == i then
        local _list_0 = colored_repr(x[i], width, depth)
        for _index_0 = 1, #_list_0 do
          local s = _list_0[_index_0]
          ret[#ret + 1] = s
        end
        i = i + 1
      else
        local _list_0 = colored_repr(k, width, depth)
        for _index_0 = 1, #_list_0 do
          local s = _list_0[_index_0]
          ret[#ret + 1] = s
        end
        ret[#ret + 1] = ' = '
        ret[#ret + 1] = Color('white')
        local _list_1 = colored_repr(v, width, depth)
        for _index_0 = 1, #_list_1 do
          local s = _list_1[_index_0]
          ret[#ret + 1] = s
        end
      end
      ret[#ret + 1] = ', '
      ret[#ret + 1] = Color('white')
    end
    if #ret > 2 then
      ret[#ret] = nil
      ret[#ret] = nil
    end
    local len = 0
    for i = 1, #ret - 1, 2 do
      len = len + #ret[i]
    end
    for i = #ret - 1, 3, -2 do
      if len <= width - 1 then
        break
      end
      if ret[i + 2] then
        ret[i + 2], ret[i + 3] = nil, nil
      end
      ret[i] = '...'
      ret[i + 1] = Color('white')
    end
    ret[#ret + 1] = '}'
    ret[#ret + 1] = TYPE_COLORS.table
    return ret
  elseif x_type == 'string' then
    local ret = {
      (x:match('^[^\t\r\v\b\a\n]*')),
      TYPE_COLORS.string
    }
    for escape, line in x:gmatch('([\t\r\v\b\a\n])([^\t\r\v\b\a\n]*)') do
      ret[#ret + 1] = '\\' .. ({
        ['\t'] = 't',
        ['\r'] = 'r',
        ['\v'] = 'v',
        ['\b'] = 'b',
        ['\a'] = 'a',
        ['\n'] = 'n'
      })[escape]
      ret[#ret + 1] = Color('white on black')
      ret[#ret + 1] = line
      ret[#ret + 1] = TYPE_COLORS.string
    end
    local len = 0
    for i = 1, #ret - 1, 2 do
      len = len + #ret[i]
    end
    for i = #ret - 1, 1, -2 do
      if len <= width then
        break
      end
      if ret[i + 2] then
        ret[i + 2], ret[i + 3] = nil, nil
      end
      len = len - #ret[i]
      if len <= width then
        ret[i] = ret[i]:sub(1, width - len - 3)
        ret[i + 2] = '...'
        ret[i + 3] = Color('blue')
        break
      end
    end
    return ret
  else
    local ok, s = pcall(tostring, x)
    if not ok then
      return {
        "tostring error: " .. s,
        Color("red")
      }
    end
    if #s > width then
      return {
        s:sub(1, width - 3),
        TYPE_COLORS[type(x)],
        '...',
        Color('blue')
      }
    else
      return {
        s,
        TYPE_COLORS[type(x)]
      }
    end
  end
end
local make_lines
make_lines = function(location, x, width)
  local _exp_0 = type(x)
  if 'string' == _exp_0 then
    local lines = { }
    local _list_0 = line_matcher:match(x)
    for _index_0 = 1, #_list_0 do
      local line = _list_0[_index_0]
      local wrapped = wrap_text(line, width - 1)
      for i, subline in ipairs(wrapped) do
        local _line = {
          location = location
        }
        if i > 1 then
          table.insert(_line, C.ACS_BULLET)
          table.insert(_line, Color('black bold altcharset'))
        end
        table.insert(_line, subline)
        table.insert(_line, Color('blue on black'))
        table.insert(lines, _line)
      end
    end
    if #lines == 0 then
      table.insert(lines, {
        location = location,
        "''",
        Color('blue')
      })
    end
    return lines
  elseif 'table' == _exp_0 then
    local prepend
    prepend = function(line, ...)
      for i = 1, select('#', ...) do
        table.insert(line, i, (select(i, ...)))
      end
    end
    local lines = { }
    for k, v in pairs(x) do
      if is_key_expanded(location, k) and is_value_expanded(location, k) then
        table.insert(lines, {
          location = Location(location, KEY, k),
          'key',
          Color('green bold'),
          '/',
          Color(),
          'value',
          Color('blue bold'),
          ':',
          Color('white')
        })
        local key_lines = make_lines(Location(location, KEY, k), k, width - 1)
        for i, key_line in ipairs(key_lines) do
          if i == 1 then
            prepend(key_line, ' ', Color(), C.ACS_DIAMOND, Color('green bold'), ' ', Color())
          else
            prepend(key_line, '   ', Color())
          end
          table.insert(lines, key_line)
        end
        local value_lines = make_lines(Location(location, VALUE, k), v, width - 2)
        for i, value_line in ipairs(value_lines) do
          if i == 1 then
            prepend(value_line, ' ', Color(), C.ACS_DIAMOND, Color('blue bold'), ' ', Color())
          else
            prepend(value_line, '   ', Color())
          end
          table.insert(lines, value_line)
        end
      elseif is_value_expanded(location, k) then
        local k_str = colored_repr(k, width - 1)
        table.insert(lines, {
          location = Location(location, KEY, k),
          '-',
          Color('red'),
          unpack(k_str)
        })
        local v_lines = make_lines(Location(location, VALUE, k), v, width - 1)
        prepend(v_lines[1], '  ', Color())
        for i = 2, #v_lines do
          prepend(v_lines[i], '  ', Color())
        end
        for _index_0 = 1, #v_lines do
          local v_line = v_lines[_index_0]
          table.insert(lines, v_line)
        end
      elseif is_key_expanded(location, k) then
        local k_lines = make_lines(Location(location, KEY, k), k, width - 4)
        for i = 1, #k_lines do
          prepend(k_lines[i], '    ', Color())
        end
        for _index_0 = 1, #k_lines do
          local k_line = k_lines[_index_0]
          table.insert(lines, k_line)
        end
        local v_str = colored_repr(v, width - 2)
        table.insert(lines, {
          location = Location(location, VALUE, k),
          '  ',
          Color(),
          unpack(v_str)
        })
      else
        local k_space = math.floor((width - 4) / 3)
        local k_str = colored_repr(k, k_space)
        local v_space = (width - 4) - #k_str
        local v_str = colored_repr(v, v_space)
        local line = {
          location = Location(location, VALUE, k),
          '+',
          Color('green'),
          unpack(k_str)
        }
        table.insert(line, ' = ')
        table.insert(line, Color('white'))
        for _index_0 = 1, #v_str do
          local s = v_str[_index_0]
          table.insert(line, s)
        end
        table.insert(lines, line)
      end
    end
    if #lines == 0 then
      table.insert(lines, {
        location = location,
        '{}',
        TYPE_COLORS.table
      })
    end
    return lines
  else
    if getmetatable(x) and getmetatable(x).__pairs then
      local lines = make_lines(location, (function()
        local _tbl_0 = { }
        for k, v in pairs(x) do
          _tbl_0[k] = v
        end
        return _tbl_0
      end)(), width)
      if getmetatable(x).__tostring then
        local s_lines = { }
        local ok, s = pcall(tostring, x)
        if not ok then
          s = "tostring error: " .. s
        end
        local _list_0 = line_matcher:match(s)
        for _index_0 = 1, #_list_0 do
          local line = _list_0[_index_0]
          local wrapped = wrap_text(line, width)
          for i, subline in ipairs(wrapped) do
            table.insert(s_lines, {
              location = location,
              subline,
              ok and Color('yellow') or Color('red')
            })
          end
        end
        for i = 1, #s_lines do
          table.insert(lines, i, s_lines[i])
        end
      end
      return lines
    end
    local str = tostring(x)
    if #str > width then
      str = str:sub(1, width - 3) .. '...'
    end
    return {
      {
        location = location,
        str,
        TYPE_COLORS[type(x)]
      }
    }
  end
end
local DataViewer
do
  local _class_0
  local _parent_0 = Pad
  local _base_0 = {
    setup_chstr = function(self, i) end,
    configure_size = function(self, height, width)
      self.height, self.width = height, width
      self._height, self._width = #self.chstrs, self.width - 2
    end,
    select = function(self, i)
      if #self.chstrs == 0 then
        i = nil
      end
      if i == self.selected then
        return self.selected
      end
      local old_y, old_x = self.scroll_y, self.scroll_x
      if i ~= nil then
        i = math.max(1, math.min(#self.chstrs, i))
      end
      local old_selected
      old_selected, self.selected = self.selected, i
      if old_selected and self.chstrs[old_selected] then
        self.chstrs[old_selected]:set_str(0, ' ', Color('yellow bold'))
        self._pad:mvaddchstr(old_selected - 1, 0, self.chstrs[old_selected])
      end
      if self.selected then
        self.chstrs[self.selected]:set_ch(0, C.ACS_RARROW, Color('yellow bold'))
        self._pad:mvaddchstr(self.selected - 1, 0, self.chstrs[self.selected])
        local scrolloff = 3
        if self.selected > self.scroll_y + (self.height - 2) - scrolloff then
          self.scroll_y = self.selected - (self.height - 2) + scrolloff
        elseif self.selected < self.scroll_y + scrolloff then
          self.scroll_y = self.selected - scrolloff
        end
        self.scroll_y = math.max(1, math.min(self._height, self.scroll_y))
      end
      if self.scroll_y == old_y then
        local w = math.min(self.width - 2, self._width)
        if old_selected and self.scroll_y <= old_selected and old_selected <= self.scroll_y + self.height - 2 then
          self._pad:pnoutrefresh(old_selected - 1, self.scroll_x - 1, self.y + 1 + (old_selected - self.scroll_y), self.x + 1, self.y + 1 + (old_selected - self.scroll_y) + 1, self.x + w)
        end
        if self.selected and self.scroll_y <= self.selected and self.selected <= self.scroll_y + self.height - 2 then
          self._pad:pnoutrefresh(self.selected - 1, self.scroll_x - 1, self.y + 1 + (self.selected - self.scroll_y), self.x + 1, self.y + 1 + (self.selected - self.scroll_y) + 1, self.x + w)
        end
      else
        self.dirty = true
      end
      if self.on_select then
        self:on_select(self.selected)
      end
      return self.selected
    end,
    keypress = function(self, c)
      local _exp_0 = c
      if C.KEY_DOWN == _exp_0 or C.KEY_SR == _exp_0 or ("j"):byte() == _exp_0 then
        return self:scroll(1, 0)
      elseif ('J'):byte() == _exp_0 then
        return self:scroll(10, 0)
      elseif C.KEY_UP == _exp_0 or C.KEY_SF == _exp_0 or ("k"):byte() == _exp_0 then
        return self:scroll(-1, 0)
      elseif ('K'):byte() == _exp_0 then
        return self:scroll(-10, 0)
      elseif C.KEY_RIGHT == _exp_0 or ("l"):byte() == _exp_0 then
        expansions[self.chstr_locations[self.selected]] = true
        return self:full_refresh()
      elseif ("L"):byte() == _exp_0 then
        expansions[self.chstr_locations[self.selected]] = true
        return self:full_refresh()
      elseif C.KEY_LEFT == _exp_0 or ("h"):byte() == _exp_0 then
        local loc = self.chstr_locations[self.selected]
        if expansions[loc] == nil then
          loc = Location(loc.old_loc, (loc.kind == KEY and VALUE or KEY), loc.key)
        end
        while loc and expansions[loc] == nil do
          loc = loc.old_loc
        end
        if loc then
          expansions[loc] = nil
        end
        self:full_refresh()
        if loc and self.chstr_locations[self.selected] ~= loc then
          for i, chstr_loc in ipairs(self.chstr_locations) do
            if chstr_loc == loc then
              self:select(i)
              break
            end
          end
        elseif not loc then
          return self:select(1)
        end
      elseif ("H"):byte() == _exp_0 then
        local loc = self.chstr_locations[self.selected]
        if expansions[loc] == nil then
          loc = Location(loc.old_loc, (loc.kind == KEY and VALUE or KEY), loc.key)
        end
        while loc and expansions[loc] == nil do
          loc = loc.old_loc
        end
        if loc then
          expansions[loc] = nil
        end
        self:full_refresh()
        if loc and self.chstr_locations[self.selected] ~= loc then
          for i, chstr_loc in ipairs(self.chstr_locations) do
            if chstr_loc == loc then
              self:select(i)
              break
            end
          end
        elseif not loc then
          return self:select(1)
        end
      end
    end
  }
  _base_0.__index = _base_0
  setmetatable(_base_0, _parent_0.__base)
  _class_0 = setmetatable({
    __init = function(self, data, label, y, x, height, width)
      self.data, self.label, self.y, self.x = data, label, y, x
      self.scroll_y, self.scroll_x = 1, 1
      self.selected = nil
      self.active_frame = Color("yellow bold")
      self.inactive_frame = Color("blue dim")
      self.full_refresh = function()
        local old_location = self.selected and self.chstr_locations and self.chstr_locations[self.selected]
        self.chstrs, self.chstr_locations = { }, { }
        local W = width - 3
        local lines = make_lines(TOP_LOCATION, self.data, W)
        for i, line in ipairs(lines) do
          local chstr = C.new_chstr(W)
          if i == self.selected then
            chstr:set_ch(0, C.ACS_RARROW, Color('yellow bold'))
          else
            chstr:set_str(0, ' ', Color('yellow bold'))
          end
          local offset = 1
          for j = 1, #line - 1, 2 do
            local chunk, attrs = line[j], line[j + 1]
            if type(chunk) == 'number' then
              chstr:set_ch(offset, chunk, attrs)
              offset = offset + 1
            else
              chstr:set_str(offset, chunk, attrs)
              offset = offset + #chunk
            end
          end
          if offset < W then
            chstr:set_str(offset, ' ', attrs, W - offset)
          end
          table.insert(self.chstrs, chstr)
          table.insert(self.chstr_locations, line.location)
        end
        self._height, self._width = #self.chstrs, self.width - 2
        self._pad:resize(self._height, self._width)
        for i, chstr in ipairs(self.chstrs) do
          self._pad:mvaddchstr(i - 1, 0, chstr)
        end
        self.dirty = true
        if old_location then
          for i, loc in ipairs(self.chstr_locations) do
            if loc == old_location then
              self:select(i)
              break
            end
          end
        end
      end
      self.height, self.width = height, width
      self._frame = C.newwin(self.height, self.width, self.y, self.x)
      self._frame:immedok(true)
      self._pad = C.newpad(self.height - 2, self.width - 2)
      self._pad:scrollok(true)
      self:set_active(false)
      self:full_refresh()
      return self:select(1)
    end,
    __base = _base_0,
    __name = "DataViewer",
    __parent = _parent_0
  }, {
    __index = function(cls, name)
      local val = rawget(_base_0, name)
      if val == nil then
        local parent = rawget(cls, "__parent")
        if parent then
          return parent[name]
        end
      else
        return val
      end
    end,
    __call = function(cls, ...)
      local _self_0 = setmetatable({}, _base_0)
      cls.__init(_self_0, ...)
      return _self_0
    end
  })
  _base_0.__class = _class_0
  if _parent_0.__inherited then
    _parent_0.__inherited(_parent_0, _class_0)
  end
  DataViewer = _class_0
end
local ok, to_lua = pcall(function()
  return require('moonscript.base').to_lua
end)
if not ok then
  to_lua = function()
    return nil
  end
end
local file_cache = setmetatable({ }, {
  __index = function(self, filename)
    local file = io.open(filename)
    if not file then
      return nil
    end
    local contents = file:read("a"):sub(1, -2)
    self[filename] = contents
    return contents
  end
})
local line_tables = setmetatable({ }, {
  __index = function(self, filename)
    local file = file_cache[filename]
    if not file then
      return nil
    end
    local line_table
    ok, line_table = to_lua(file)
    if ok then
      self[filename] = line_table
      return line_table
    end
  end
})
local err_hand
err_hand = function(err)
  C.endwin()
  print("Error in debugger.")
  print(debug.traceback(err, 2))
  return os.exit(2)
end
ldb = {
  run_debugger = function(err_msg)
    local select_pad
    err_msg = err_msg or ''
    if type(err_msg) ~= 'string' then
      err_msg = tostring(err_msg)
    end
    local stdscr = C.initscr()
    local SCREEN_H, SCREEN_W = stdscr:getmaxyx()
    C.cbreak()
    C.echo(false)
    C.nl(false)
    C.curs_set(0)
    C.start_color()
    C.use_default_colors()
    do
      TYPE_COLORS.string = Color('blue on black')
      TYPE_COLORS.number = Color('magenta')
      TYPE_COLORS.boolean = Color('cyan')
      TYPE_COLORS["nil"] = Color('cyan')
      TYPE_COLORS.table = Color('yellow')
      TYPE_COLORS["function"] = Color('green')
      TYPE_COLORS.userdata = Color('cyan bold')
      TYPE_COLORS.thread = Color('blue')
    end
    do
      stdscr:wbkgd(Color("yellow on red bold"))
      stdscr:clear()
      stdscr:refresh()
      local lines = wrap_text("ERROR!\n \n " .. err_msg .. "\n \npress any key...", math.floor(SCREEN_W - 2))
      local max_line = 0
      for _index_0 = 1, #lines do
        local line = lines[_index_0]
        max_line = math.max(max_line, #line)
      end
      for i, line in ipairs(lines) do
        if i == 1 or i == #lines then
          stdscr:mvaddstr(math.floor(SCREEN_H / 2 - #lines / 2) + i, math.floor((SCREEN_W - #line) / 2), line)
        else
          stdscr:mvaddstr(math.floor(SCREEN_H / 2 - #lines / 2) + i, math.floor((SCREEN_W - max_line) / 2), line)
        end
      end
      stdscr:refresh()
      C.doupdate()
      stdscr:getch()
    end
    stdscr:keypad()
    stdscr:wbkgd(Color())
    stdscr:clear()
    stdscr:refresh()
    local pads = { }
    do
      local err_msg_lines = wrap_text(err_msg, SCREEN_W - 4)
      for i, line in ipairs(err_msg_lines) do
        err_msg_lines[i] = (" "):rep(2) .. line
      end
      local height = math.min(#err_msg_lines + 2, 7)
      pads.err = Pad("(E)rror Message", 0, 0, height, SCREEN_W, err_msg_lines, function(self, i)
        return Color("red bold")
      end)
    end
    local err_lines = { }
    local stack_sources = { }
    local stack_locations = { }
    local watch_exprs = setmetatable({ }, {
      __index = function(self, k)
        local t = { }
        self[k] = t
        return t
      end
    })
    do
      local stack_names = { }
      local max_filename, max_fn_name = 0, 0
      local stack_min, stack_max = callstack_range()
      for i = stack_min, stack_max do
        local info = debug.getinfo(i)
        if not info then
          break
        end
        local fn_name = info.name
        if not (fn_name) then
          if info.istailcall then
            fn_name = "<tail call>"
          else
            fn_name = "<anonymous>"
          end
        end
        table.insert(stack_names, fn_name)
        local line
        if info.short_src then
          local line_table = line_tables[info.short_src]
          if line_table then
            local char = line_table[info.currentline]
            local line_num = 1
            local file = file_cache[info.short_src] or info.source
            for _ in file:sub(1, char):gmatch("\n") do
              line_num = line_num + 1
            end
            line = tostring(info.short_src) .. ":" .. tostring(line_num)
          else
            line = info.short_src .. ":" .. info.currentline
          end
        else
          line = "???"
        end
        err_lines[line] = true
        table.insert(stack_locations, line)
        table.insert(stack_sources, info.source)
        max_filename = math.max(max_filename, #line)
        max_fn_name = math.max(max_fn_name, #fn_name)
      end
      max_fn_name, max_filename = 0, 0
      for i = 1, #stack_names do
        max_fn_name = math.max(max_fn_name, #stack_names[i])
        max_filename = math.max(max_filename, #stack_locations[i])
      end
      local stack_h = math.floor(SCREEN_H * .6)
      local stack_w = math.min(max_fn_name + 3 + max_filename, math.floor(1 / 3 * SCREEN_W))
      pads.stack = Pad("(C)allstack", pads.err.height, SCREEN_W - stack_w, stack_h, stack_w, stack_names, (function(self, i)
        return (i == self.selected) and Color("black on green") or Color("green bold")
      end), stack_locations, (function(self, i)
        return (i == self.selected) and Color("black on cyan") or Color("cyan bold")
      end))
    end
    local show_src
    show_src = function(filename, line_no, file_contents)
      if file_contents == nil then
        file_contents = nil
      end
      if pads.src then
        if pads.src.filename == filename then
          pads.src:select(line_no)
          pads.src.colors[2] = function(self, i)
            if i == line_no and i == self.selected then
              return Color("yellow on red bold")
            elseif i == line_no then
              return Color("yellow on red")
            elseif err_lines[tostring(filename) .. ":" .. tostring(i)] == true then
              return Color("red on black bold")
            elseif i == self.selected then
              return Color("reverse")
            else
              return Color()
            end
          end
          for line, _ in pairs(err_lines) do
            local _filename, i = line:match("([^:]*):(%d*).*")
            if _filename == filename and tonumber(i) then
              pads.src:setup_chstr(tonumber(i))
            end
          end
          pads.src:select(line_no)
          return 
        else
          pads.src:erase()
        end
      end
      file_contents = file_contents or file_cache[filename]
      if file_contents then
        local src_lines = line_matcher:match(file_contents)
        pads.src = NumberedPad("(S)ource Code", pads.err.height, 0, pads.stack.height, pads.stack.x, src_lines, function(self, i)
          if i == line_no and i == self.selected then
            return Color("yellow on red bold")
          elseif i == line_no then
            return Color("yellow on red")
          elseif err_lines[tostring(filename) .. ":" .. tostring(i)] == true then
            return Color("red on black bold")
          elseif i == self.selected then
            return Color("reverse")
          else
            return Color()
          end
        end)
        pads.src:select(line_no)
      else
        local lines = { }
        for i = 1, math.floor(pads.stack.height / 2) - 1 do
          table.insert(lines, "")
        end
        local s = "<no source code found>"
        s = (" "):rep(math.floor((pads.stack.x - 2 - #s) / 2)) .. s
        table.insert(lines, s)
        pads.src = Pad("(S)ource Code", pads.err.height, 0, pads.stack.height, pads.stack.x, lines, function()
          return Color("red")
        end)
      end
      pads.src.filename = filename
    end
    local stack_env
    local show_vars
    show_vars = function(stack_index)
      if pads.vars then
        pads.vars:erase()
      end
      if pads.data then
        pads.data:erase()
      end
      local callstack_min, _ = callstack_range()
      local var_names, values = { }, { }
      stack_env = setmetatable({ }, {
        __index = _G
      })
      for loc = 1, 999 do
        local name, value = debug.getlocal(callstack_min + stack_index - 1, loc)
        if name == nil then
          break
        end
        table.insert(var_names, tostring(name))
        table.insert(values, value)
        stack_env[name] = value
      end
      local num_locals = #var_names
      local info = debug.getinfo(callstack_min + stack_index - 1, "uf")
      for upval = 1, info.nups do
        local _continue_0 = false
        repeat
          local name, value = debug.getupvalue(info.func, upval)
          if name == "_ENV" then
            _continue_0 = true
            break
          end
          table.insert(var_names, tostring(name))
          table.insert(values, value)
          stack_env[name] = value
          _continue_0 = true
        until true
        if not _continue_0 then
          break
        end
      end
      local _list_0 = watch_exprs[stack_index]
      for _index_0 = 1, #_list_0 do
        local _continue_0 = false
        repeat
          local watch = _list_0[_index_0]
          if stack_env[watch.expr] ~= nil then
            _continue_0 = true
            break
          end
          table.insert(var_names, watch.expr)
          table.insert(values, watch.value)
          stack_env[watch.expr] = watch.value
          _continue_0 = true
        until true
        if not _continue_0 then
          break
        end
      end
      local var_y = pads.stack.y + pads.stack.height
      local var_x = 0
      local height = SCREEN_H - (pads.err.height + pads.stack.height)
      pads.vars = Pad("(V)ars", var_y, var_x, height, AUTO, var_names, function(self, i)
        local color
        if i <= num_locals then
          color = Color()
        elseif i <= num_locals + info.nups - 1 then
          color = Color("blue")
        else
          color = Color("green")
        end
        if i == self.selected then
          color = color + C.A_REVERSE
        end
        return color
      end)
      pads.vars.keypress = function(self, key)
        if key == ('l'):byte() or key == C.KEY_RIGHT then
          return select_pad(pads.data)
        else
          return Pad.keypress(self, key)
        end
      end
      pads.vars.on_select = function(self, var_index)
        if var_index == nil then
          return 
        end
        local value_x = pads.vars.x + pads.vars.width
        local value_w = SCREEN_W - (value_x)
        local value = stack_env[var_names[var_index]]
        local type_str = tostring(type(value))
        pads.data = DataViewer(value, "(D)ata [" .. tostring(type_str) .. "]", var_y, value_x, pads.vars.height, value_w)
        pads.data.keypress = function(self, key)
          if (key == ('h'):byte() or key == C.KEY_LEFT) and self.selected == 1 then
            return select_pad(pads.vars)
          else
            return DataViewer.keypress(self, key)
          end
        end
        collectgarbage()
        return collectgarbage()
      end
      return pads.vars:select(1)
    end
    pads.stack.on_select = function(self, stack_index)
      local filename, line_no = pads.stack.columns[2][stack_index]:match("^(.*):(%d*)$")
      line_no = tonumber(line_no)
      show_src(filename, line_no, filename and file_cache[filename] or stack_sources[stack_index])
      return show_vars(stack_index)
    end
    pads.stack:select(1)
    local selected_pad = nil
    select_pad = function(pad)
      if selected_pad ~= pad then
        if selected_pad then
          selected_pad:set_active(false)
          selected_pad:refresh()
        end
        selected_pad = pad
        if selected_pad then
          selected_pad:set_active(true)
          return selected_pad:refresh()
        end
      end
    end
    select_pad(pads.stack)
    while true do
      for _, p in pairs(pads) do
        p:refresh()
      end
      local s = " press 'q' to quit "
      stdscr:mvaddstr(math.floor(SCREEN_H - 1), math.floor((SCREEN_W - #s)), s)
      local c = stdscr:getch()
      local _exp_0 = c
      if (':'):byte() == _exp_0 or ('>'):byte() == _exp_0 or ('?'):byte() == _exp_0 then
        C.echo(true)
        local print_nil = false
        local user_input
        local code = ''
        if c == ('?'):byte() then
          stdscr:mvaddstr(SCREEN_H - 1, 0, "? " .. (' '):rep(SCREEN_W - 1))
          stdscr:move(SCREEN_H - 1, 2)
          user_input = stdscr:getstr()
          code = 'return ' .. user_input
          print_nil = true
        elseif c == (':'):byte() or c == ('>'):byte() then
          local numlines = 1
          stdscr:mvaddstr(SCREEN_H - 1, 0, "> " .. (' '):rep(SCREEN_W - 1))
          stdscr:move(SCREEN_H - 1, 2)
          while true do
            local line = stdscr:getstr()
            if line == '' then
              break
            end
            code = code .. (line .. '\n')
            numlines = numlines + 1
            stdscr:mvaddstr(SCREEN_H - numlines, 0, "> " .. ((' '):rep(SCREEN_W) .. '\n'):rep(numlines))
            stdscr:mvaddstr(SCREEN_H - numlines, 2, code)
            stdscr:mvaddstr(SCREEN_H - 1, 0, (' '):rep(SCREEN_W))
            stdscr:move(SCREEN_H - 1, 0)
          end
        end
        C.echo(false)
        local output = ""
        if not stack_env then
          stack_env = setmetatable({ }, {
            __index = _G
          })
        end
        stack_env.print = function(...)
          for i = 1, select('#', ...) do
            if i > 1 then
              output = output .. '\t'
            end
            output = output .. tostring(select(i, ...))
          end
          output = output .. "\n"
        end
        for _, p in pairs(pads) do
          p:refresh(true)
        end
        local run_fn
        run_fn, err_msg = load(code, 'user input', 't', stack_env)
        if not run_fn then
          stdscr:attrset(Color('red bold'))
          stdscr:addstr(err_msg)
          stdscr:attrset(Color())
        else
          local ret
          ok, ret = pcall(run_fn)
          if not ok then
            stdscr:attrset(Color('red bold'))
            stdscr:addstr(ret)
            stdscr:attrset(Color())
          elseif ret ~= nil or print_nil then
            local value_bits = {
              '= ',
              Color('yellow'),
              unpack(colored_repr(ret, SCREEN_W - 2, 4))
            }
            local numlines = 1
            for i = 1, #value_bits - 1, 2 do
              for nl in value_bits[i]:gmatch('\n') do
                numlines = numlines + 1
              end
            end
            for nl in output:gmatch('\n') do
              numlines = numlines + 1
            end
            local y, x = SCREEN_H - numlines, 0
            if output ~= "" then
              stdscr:mvaddstr(SCREEN_H - numlines, 0, output)
              for nl in output:gmatch('\n') do
                y = y + 1
              end
            end
            for i = 1, #value_bits - 1, 2 do
              stdscr:attrset(value_bits[i + 1])
              local first_line = value_bits[i]:match('^[^\n]*')
              stdscr:mvaddstr(y, x, first_line)
              x = x + #first_line
              for line in value_bits[i]:gmatch('\n([^\n]*)') do
                stdscr:mvaddstr(y, x, (' '):rep(SCREEN_W - x))
                y = y + 1
                x = 0
                stdscr:mvaddstr(y, x, line)
              end
            end
            stdscr:attrset(Color())
            stdscr:mvaddstr(y, x, (' '):rep(SCREEN_W - x))
            if c == ("?"):byte() and ret ~= nil then
              local replacing = false
              local watch_index = nil
              local watches = watch_exprs[pads.stack.selected]
              for i, w in ipairs(watches) do
                if w.expr == user_input then
                  w.value = ret
                  watch_index = i
                  break
                end
              end
              if not (watch_index) then
                table.insert(watches, {
                  expr = user_input,
                  value = ret
                })
                watch_index = #watches
              end
              show_vars(pads.stack.selected)
              for i, s in ipairs(pads.vars.columns[1]) do
                if s == user_input then
                  pads.vars:select(i)
                  break
                end
              end
              select_pad(pads.data)
            end
          else
            local numlines = 0
            for nl in output:gmatch('\n') do
              numlines = numlines + 1
            end
            stdscr:mvaddstr(SCREEN_H - numlines, 0, output)
          end
        end
      elseif ('o'):byte() == _exp_0 then
        local file = stack_locations[pads.stack.selected]
        local filename, line_no = file:match("([^:]*):(.*)")
        line_no = tostring(pads.src.selected)
        C.endwin()
        os.execute((os.getenv("EDITOR") or "nano") .. " +" .. line_no .. " " .. filename)
        stdscr = C.initscr()
        C.cbreak()
        C.echo(false)
        C.nl(false)
        C.curs_set(0)
        C.start_color()
        C.use_default_colors()
        stdscr:clear()
        stdscr:refresh()
        for _, pad in pairs(pads) do
          pad:refresh(true)
        end
      elseif C.KEY_RESIZE == _exp_0 then
        SCREEN_H, SCREEN_W = stdscr:getmaxyx()
        stdscr:clear()
        stdscr:refresh()
        for _, pad in pairs(pads) do
          pad:refresh(true)
        end
        C.doupdate()
      elseif ('q'):byte() == _exp_0 or ("Q"):byte() == _exp_0 then
        pads = { }
        C.endwin()
        return 
      elseif ('c'):byte() == _exp_0 then
        select_pad(pads.stack)
      elseif ('s'):byte() == _exp_0 then
        select_pad(pads.src)
      elseif ('v'):byte() == _exp_0 then
        select_pad(pads.vars)
      elseif ('d'):byte() == _exp_0 then
        select_pad(pads.data)
      elseif ('e'):byte() == _exp_0 then
        select_pad(pads.err)
      elseif C.KEY_DC == _exp_0 or C.KEY_DL == _exp_0 or C.KEY_BACKSPACE == _exp_0 then
        if selected_pad == pads.vars then
          local watches = watch_exprs[pads.stack.selected]
          local expr = pads.vars.columns[1][pads.vars.selected]
          for i, w in ipairs(watches) do
            if w.expr == expr then
              table.remove(watches, i)
              show_vars(pads.stack.selected)
              select_pad(pads.vars)
              break
            end
          end
        end
      else
        if selected_pad then
          selected_pad:keypress(c)
        end
      end
    end
    return C.endwin()
  end,
  guard = function(fn, ...)
    local handler
    handler = function(err_msg)
      print(debug.traceback(err_msg, 2))
      return xpcall(ldb.run_debugger, err_hand, err_msg)
    end
    return xpcall(fn, handler, ...)
  end,
  breakpoint = function()
    return xpcall(ldb.run_debugger, err_hand, "Breakpoint triggered!")
  end,
  hijack = function()
    error = function(err_msg)
      print(debug.traceback(err_msg, 2))
      xpcall(ldb.run_debugger, err_hand, err_msg)
      return os.exit(2)
    end
    assert = function(condition, err_msg)
      if not condition then
        err_msg = err_msg or 'Assertion failed!'
        print(debug.traceback(err_msg, 2))
        xpcall(ldb.run_debugger, err_hand, err_msg)
        os.exit(2)
      end
      return condition
    end
  end
}
return ldb