Initial working on REPL
This commit is contained in:
parent
abad5cce0f
commit
7dda8143e4
|
@ -0,0 +1,330 @@
|
|||
local Object = require "core.object"
|
||||
local Highlighter = require "core.doc.highlighter"
|
||||
local syntax = require "core.syntax"
|
||||
local config = require "core.config"
|
||||
local common = require "core.common"
|
||||
|
||||
|
||||
local InputDoc = Object:extend()
|
||||
|
||||
|
||||
local function split_lines(text)
|
||||
local res = {}
|
||||
for line in (text .. "\n"):gmatch("(.-)\n") do
|
||||
table.insert(res, line)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function splice(t, at, remove, insert)
|
||||
insert = insert or {}
|
||||
local offset = #insert - remove
|
||||
local old_len = #t
|
||||
if offset < 0 then
|
||||
for i = at - offset, old_len - offset do
|
||||
t[i + offset] = t[i]
|
||||
end
|
||||
elseif offset > 0 then
|
||||
for i = old_len, at, -1 do
|
||||
t[i + offset] = t[i]
|
||||
end
|
||||
end
|
||||
for i, item in ipairs(insert) do
|
||||
t[at + i - 1] = item
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function InputDoc:new(syntax)
|
||||
self.syntax = syntax
|
||||
self:reset()
|
||||
end
|
||||
|
||||
|
||||
function InputDoc:reset()
|
||||
self.lines = { "\n" }
|
||||
self.selection = { a = { line=1, col=1 }, b = { line=1, col=1 } }
|
||||
self.undo_stack = { idx = 1 }
|
||||
self.redo_stack = { idx = 1 }
|
||||
-- self.clean_change_id = 1
|
||||
self.highlighter = Highlighter(self)
|
||||
end
|
||||
|
||||
function InputDoc:get_change_id()
|
||||
return self.undo_stack.idx
|
||||
end
|
||||
|
||||
|
||||
function InputDoc:set_selection(line1, col1, line2, col2, swap)
|
||||
assert(not line2 == not col2, "expected 2 or 4 arguments")
|
||||
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
|
||||
line1, col1 = self:sanitize_position(line1, col1)
|
||||
line2, col2 = self:sanitize_position(line2 or line1, col2 or col1)
|
||||
self.selection.a.line, self.selection.a.col = line1, col1
|
||||
self.selection.b.line, self.selection.b.col = line2, col2
|
||||
end
|
||||
|
||||
|
||||
local function sort_positions(line1, col1, line2, col2)
|
||||
if line1 > line2
|
||||
or line1 == line2 and col1 > col2 then
|
||||
return line2, col2, line1, col1, true
|
||||
end
|
||||
return line1, col1, line2, col2, false
|
||||
end
|
||||
|
||||
|
||||
function InputDoc:get_selection(sort)
|
||||
local a, b = self.selection.a, self.selection.b
|
||||
if sort then
|
||||
return sort_positions(a.line, a.col, b.line, b.col)
|
||||
end
|
||||
return a.line, a.col, b.line, b.col
|
||||
end
|
||||
|
||||
|
||||
function InputDoc:has_selection()
|
||||
local a, b = self.selection.a, self.selection.b
|
||||
return not (a.line == b.line and a.col == b.col)
|
||||
end
|
||||
|
||||
|
||||
function InputDoc:sanitize_selection()
|
||||
self:set_selection(self:get_selection())
|
||||
end
|
||||
|
||||
|
||||
function InputDoc:sanitize_position(line, col)
|
||||
line = common.clamp(line, 1, #self.lines)
|
||||
col = common.clamp(col, 1, #self.lines[line])
|
||||
return line, col
|
||||
end
|
||||
|
||||
|
||||
local function position_offset_func(self, line, col, fn, ...)
|
||||
line, col = self:sanitize_position(line, col)
|
||||
return fn(self, line, col, ...)
|
||||
end
|
||||
|
||||
|
||||
local function position_offset_byte(self, line, col, offset)
|
||||
line, col = self:sanitize_position(line, col)
|
||||
col = col + offset
|
||||
while line > 1 and col < 1 do
|
||||
line = line - 1
|
||||
col = col + #self.lines[line]
|
||||
end
|
||||
while line < #self.lines and col > #self.lines[line] do
|
||||
col = col - #self.lines[line]
|
||||
line = line + 1
|
||||
end
|
||||
return self:sanitize_position(line, col)
|
||||
end
|
||||
|
||||
|
||||
local function position_offset_linecol(self, line, col, lineoffset, coloffset)
|
||||
return self:sanitize_position(line + lineoffset, col + coloffset)
|
||||
end
|
||||
|
||||
|
||||
function Doc:position_offset(line, col, ...)
|
||||
if type(...) ~= "number" then
|
||||
return position_offset_func(self, line, col, ...)
|
||||
elseif select("#", ...) == 1 then
|
||||
return position_offset_byte(self, line, col, ...)
|
||||
elseif select("#", ...) == 2 then
|
||||
return position_offset_linecol(self, line, col, ...)
|
||||
else
|
||||
error("bad number of arguments")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_text(line1, col1, line2, col2)
|
||||
line1, col1 = self:sanitize_position(line1, col1)
|
||||
line2, col2 = self:sanitize_position(line2, col2)
|
||||
line1, col1, line2, col2 = sort_positions(line1, col1, line2, col2)
|
||||
if line1 == line2 then
|
||||
return self.lines[line1]:sub(col1, col2 - 1)
|
||||
end
|
||||
local lines = { self.lines[line1]:sub(col1) }
|
||||
for i = line1 + 1, line2 - 1 do
|
||||
table.insert(lines, self.lines[i])
|
||||
end
|
||||
table.insert(lines, self.lines[line2]:sub(1, col2 - 1))
|
||||
return table.concat(lines)
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_char(line, col)
|
||||
line, col = self:sanitize_position(line, col)
|
||||
return self.lines[line]:sub(col, col)
|
||||
end
|
||||
|
||||
|
||||
local function push_undo(undo_stack, time, type, ...)
|
||||
undo_stack[undo_stack.idx] = { type = type, time = time, ... }
|
||||
undo_stack[undo_stack.idx - config.max_undos] = nil
|
||||
undo_stack.idx = undo_stack.idx + 1
|
||||
end
|
||||
|
||||
|
||||
local function pop_undo(self, undo_stack, redo_stack)
|
||||
-- pop command
|
||||
local cmd = undo_stack[undo_stack.idx - 1]
|
||||
if not cmd then return end
|
||||
undo_stack.idx = undo_stack.idx - 1
|
||||
|
||||
-- handle command
|
||||
if cmd.type == "insert" then
|
||||
local line, col, text = table.unpack(cmd)
|
||||
self:raw_insert(line, col, text, redo_stack, cmd.time)
|
||||
|
||||
elseif cmd.type == "remove" then
|
||||
local line1, col1, line2, col2 = table.unpack(cmd)
|
||||
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
|
||||
|
||||
elseif cmd.type == "selection" then
|
||||
self.selection.a.line, self.selection.a.col = cmd[1], cmd[2]
|
||||
self.selection.b.line, self.selection.b.col = cmd[3], cmd[4]
|
||||
end
|
||||
|
||||
-- if next undo command is within the merge timeout then treat as a single
|
||||
-- command and continue to execute it
|
||||
local next = undo_stack[undo_stack.idx - 1]
|
||||
if next and math.abs(cmd.time - next.time) < config.undo_merge_timeout then
|
||||
return pop_undo(self, undo_stack, redo_stack)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||
-- split text into lines and merge with line at insertion point
|
||||
local lines = split_lines(text)
|
||||
local before = self.lines[line]:sub(1, col - 1)
|
||||
local after = self.lines[line]:sub(col)
|
||||
for i = 1, #lines - 1 do
|
||||
lines[i] = lines[i] .. "\n"
|
||||
end
|
||||
lines[1] = before .. lines[1]
|
||||
lines[#lines] = lines[#lines] .. after
|
||||
|
||||
-- splice lines into line array
|
||||
splice(self.lines, line, 1, lines)
|
||||
|
||||
-- push undo
|
||||
local line2, col2 = self:position_offset(line, col, #text)
|
||||
push_undo(undo_stack, time, "selection", self:get_selection())
|
||||
push_undo(undo_stack, time, "remove", line, col, line2, col2)
|
||||
|
||||
-- update highlighter and assure selection is in bounds
|
||||
self.highlighter:invalidate(line)
|
||||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
|
||||
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||
-- push undo
|
||||
local text = self:get_text(line1, col1, line2, col2)
|
||||
push_undo(undo_stack, time, "selection", self:get_selection())
|
||||
push_undo(undo_stack, time, "insert", line1, col1, text)
|
||||
|
||||
-- get line content before/after removed text
|
||||
local before = self.lines[line1]:sub(1, col1 - 1)
|
||||
local after = self.lines[line2]:sub(col2)
|
||||
|
||||
-- splice line into line array
|
||||
splice(self.lines, line1, line2 - line1 + 1, { before .. after })
|
||||
|
||||
-- update highlighter and assure selection is in bounds
|
||||
self.highlighter:invalidate(line1)
|
||||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
|
||||
function Doc:insert(line, col, text)
|
||||
self.redo_stack = { idx = 1 }
|
||||
line, col = self:sanitize_position(line, col)
|
||||
self:raw_insert(line, col, text, self.undo_stack, system.get_time())
|
||||
end
|
||||
|
||||
|
||||
function Doc:remove(line1, col1, line2, col2)
|
||||
self.redo_stack = { idx = 1 }
|
||||
line1, col1 = self:sanitize_position(line1, col1)
|
||||
line2, col2 = self:sanitize_position(line2, col2)
|
||||
line1, col1, line2, col2 = sort_positions(line1, col1, line2, col2)
|
||||
self:raw_remove(line1, col1, line2, col2, self.undo_stack, system.get_time())
|
||||
end
|
||||
|
||||
|
||||
function Doc:undo()
|
||||
pop_undo(self, self.undo_stack, self.redo_stack)
|
||||
end
|
||||
|
||||
|
||||
function Doc:redo()
|
||||
pop_undo(self, self.redo_stack, self.undo_stack)
|
||||
end
|
||||
|
||||
|
||||
function Doc:text_input(text)
|
||||
if self:has_selection() then
|
||||
self:delete_to()
|
||||
end
|
||||
local line, col = self:get_selection()
|
||||
self:insert(line, col, text)
|
||||
self:move_to(#text)
|
||||
end
|
||||
|
||||
|
||||
function Doc:replace(fn)
|
||||
local line1, col1, line2, col2, swap
|
||||
local had_selection = self:has_selection()
|
||||
if had_selection then
|
||||
line1, col1, line2, col2, swap = self:get_selection(true)
|
||||
else
|
||||
line1, col1, line2, col2 = 1, 1, #self.lines, #self.lines[#self.lines]
|
||||
end
|
||||
local old_text = self:get_text(line1, col1, line2, col2)
|
||||
local new_text, n = fn(old_text)
|
||||
if old_text ~= new_text then
|
||||
self:insert(line2, col2, new_text)
|
||||
self:remove(line1, col1, line2, col2)
|
||||
if had_selection then
|
||||
line2, col2 = self:position_offset(line1, col1, #new_text)
|
||||
self:set_selection(line1, col1, line2, col2, swap)
|
||||
end
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
|
||||
function Doc:delete_to(...)
|
||||
local line, col = self:get_selection(true)
|
||||
if self:has_selection() then
|
||||
self:remove(self:get_selection())
|
||||
else
|
||||
local line2, col2 = self:position_offset(line, col, ...)
|
||||
self:remove(line, col, line2, col2)
|
||||
line, col = sort_positions(line, col, line2, col2)
|
||||
end
|
||||
self:set_selection(line, col)
|
||||
end
|
||||
|
||||
|
||||
function Doc:move_to(...)
|
||||
local line, col = self:get_selection()
|
||||
self:set_selection(self:position_offset(line, col, ...))
|
||||
end
|
||||
|
||||
|
||||
function Doc:select_to(...)
|
||||
local line, col, line2, col2 = self:get_selection()
|
||||
line, col = self:position_offset(line, col, ...)
|
||||
self:set_selection(line, col, line2, col2)
|
||||
end
|
||||
|
||||
|
||||
return Doc
|
|
@ -0,0 +1,383 @@
|
|||
local core = require "core"
|
||||
local keymap = require "core.keymap"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
config.console_size = 250 * SCALE
|
||||
config.max_console_lines = 200
|
||||
config.autoscroll_console = true
|
||||
|
||||
local files = {
|
||||
script = core.temp_filename(PLATFORM == "Windows" and ".bat"),
|
||||
script2 = core.temp_filename(PLATFORM == "Windows" and ".bat"),
|
||||
output = core.temp_filename(),
|
||||
complete = core.temp_filename(),
|
||||
}
|
||||
|
||||
local console = {}
|
||||
|
||||
local views = {}
|
||||
local pending_threads = {}
|
||||
local thread_active = false
|
||||
local output = nil
|
||||
local output_id = 0
|
||||
local visible = false
|
||||
|
||||
function console.clear()
|
||||
output = { { text = "", time = 0 } }
|
||||
end
|
||||
|
||||
|
||||
local function read_file(filename, offset)
|
||||
local fp = io.open(filename, "rb")
|
||||
fp:seek("set", offset or 0)
|
||||
local res = fp:read("*a")
|
||||
fp:close()
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function write_file(filename, text)
|
||||
local fp = io.open(filename, "w")
|
||||
fp:write(text)
|
||||
fp:close()
|
||||
end
|
||||
|
||||
|
||||
local function lines(text)
|
||||
return (text .. "\n"):gmatch("(.-)\n")
|
||||
end
|
||||
|
||||
|
||||
local function push_output(str, opt)
|
||||
local first = true
|
||||
for line in lines(str) do
|
||||
if first then
|
||||
line = table.remove(output).text .. line
|
||||
end
|
||||
line = line:gsub("\x1b%[[%d;]+m", "") -- strip ANSI colors
|
||||
table.insert(output, {
|
||||
text = line,
|
||||
time = os.time(),
|
||||
icon = line:find(opt.error_pattern) and "!"
|
||||
or line:find(opt.warning_pattern) and "i",
|
||||
file_pattern = opt.file_pattern,
|
||||
})
|
||||
if #output > config.max_console_lines then
|
||||
table.remove(output, 1)
|
||||
for view in pairs(views) do
|
||||
view:on_line_removed()
|
||||
end
|
||||
end
|
||||
first = false
|
||||
end
|
||||
output_id = output_id + 1
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
|
||||
local function init_opt(opt)
|
||||
local res = {
|
||||
command = "",
|
||||
file_pattern = "[^?:%s]+%.[^?:%s]+",
|
||||
error_pattern = "error",
|
||||
warning_pattern = "warning",
|
||||
on_complete = function() end,
|
||||
}
|
||||
for k, v in pairs(res) do
|
||||
res[k] = opt[k] or v
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
function console.run(opt)
|
||||
opt = init_opt(opt)
|
||||
|
||||
local function thread()
|
||||
-- init script file(s)
|
||||
if PLATFORM == "Windows" then
|
||||
write_file(files.script, opt.command .. "\n")
|
||||
write_file(files.script2, string.format([[
|
||||
@echo off
|
||||
call %q >%q 2>&1
|
||||
echo "" >%q
|
||||
exit
|
||||
]], files.script, files.output, files.complete))
|
||||
system.exec(string.format("call %q", files.script2))
|
||||
else
|
||||
write_file(files.script, string.format([[
|
||||
%s
|
||||
touch %q
|
||||
]], opt.command, files.complete))
|
||||
system.exec(string.format("bash %q >%q 2>&1", files.script, files.output))
|
||||
end
|
||||
|
||||
-- checks output file for change and reads
|
||||
local last_size = 0
|
||||
local function check_output_file()
|
||||
if PLATFORM == "Windows" then
|
||||
local fp = io.open(files.output)
|
||||
if fp then fp:close() end
|
||||
end
|
||||
local info = system.get_file_info(files.output)
|
||||
if info and info.size > last_size then
|
||||
local text = read_file(files.output, last_size)
|
||||
push_output(text, opt)
|
||||
last_size = info.size
|
||||
end
|
||||
end
|
||||
|
||||
-- read output file until we get a file indicating completion
|
||||
while not system.get_file_info(files.complete) do
|
||||
check_output_file()
|
||||
coroutine.yield(0.1)
|
||||
end
|
||||
check_output_file()
|
||||
if output[#output].text ~= "" then
|
||||
push_output("\n", opt)
|
||||
end
|
||||
push_output("!DIVIDER\n", opt)
|
||||
|
||||
-- clean up and finish
|
||||
for _, file in pairs(files) do
|
||||
os.remove(file)
|
||||
end
|
||||
opt.on_complete()
|
||||
|
||||
-- handle pending thread
|
||||
local pending = table.remove(pending_threads, 1)
|
||||
if pending then
|
||||
core.add_thread(pending)
|
||||
else
|
||||
thread_active = false
|
||||
end
|
||||
end
|
||||
|
||||
-- push/init thread
|
||||
if thread_active then
|
||||
table.insert(pending_threads, thread)
|
||||
else
|
||||
core.add_thread(thread)
|
||||
thread_active = true
|
||||
end
|
||||
|
||||
-- make sure static console is visible if it's the only ConsoleView
|
||||
local count = 0
|
||||
for _ in pairs(views) do count = count + 1 end
|
||||
if count == 1 then visible = true end
|
||||
end
|
||||
|
||||
|
||||
|
||||
local ConsoleView = View:extend()
|
||||
|
||||
function ConsoleView:new()
|
||||
ConsoleView.super.new(self)
|
||||
self.scrollable = true
|
||||
self.hovered_idx = -1
|
||||
views[self] = true
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:try_close(...)
|
||||
ConsoleView.super.try_close(self, ...)
|
||||
views[self] = nil
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:get_name()
|
||||
return "Console"
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:get_line_height()
|
||||
return style.code_font:get_height() * config.line_height
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:get_line_count()
|
||||
return #output - (output[#output].text == "" and 1 or 0)
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:get_scrollable_size()
|
||||
return self:get_line_count() * self:get_line_height() + style.padding.y * 2
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:get_visible_line_range()
|
||||
local lh = self:get_line_height()
|
||||
local min = math.max(1, math.floor(self.scroll.y / lh))
|
||||
return min, min + math.floor(self.size.y / lh) + 1
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:on_mouse_moved(mx, my, ...)
|
||||
ConsoleView.super.on_mouse_moved(self, mx, my, ...)
|
||||
self.hovered_idx = 0
|
||||
for i, item, x,y,w,h in self:each_visible_line() do
|
||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
||||
if item.text:find(item.file_pattern) then
|
||||
self.hovered_idx = i
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function resolve_file(name)
|
||||
if system.get_file_info(name) then
|
||||
return name
|
||||
end
|
||||
local filenames = {}
|
||||
for _, f in ipairs(core.project_files) do
|
||||
table.insert(filenames, f.filename)
|
||||
end
|
||||
local t = common.fuzzy_match(filenames, name)
|
||||
return t[1]
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:on_line_removed()
|
||||
local diff = self:get_line_height()
|
||||
self.scroll.y = self.scroll.y - diff
|
||||
self.scroll.to.y = self.scroll.to.y - diff
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:on_mouse_pressed(...)
|
||||
local caught = ConsoleView.super.on_mouse_pressed(self, ...)
|
||||
if caught then
|
||||
return
|
||||
end
|
||||
local item = output[self.hovered_idx]
|
||||
if item then
|
||||
local file, line, col = item.text:match(item.file_pattern)
|
||||
local resolved_file = resolve_file(file)
|
||||
if not resolved_file then
|
||||
core.error("Couldn't resolve file \"%s\"", file)
|
||||
return
|
||||
end
|
||||
core.try(function()
|
||||
core.set_active_view(core.last_active_view)
|
||||
local dv = core.root_view:open_doc(core.open_doc(resolved_file))
|
||||
if line then
|
||||
dv.doc:set_selection(line, col or 0)
|
||||
dv:scroll_to_line(line, false, true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:each_visible_line()
|
||||
return coroutine.wrap(function()
|
||||
local x, y = self:get_content_offset()
|
||||
local lh = self:get_line_height()
|
||||
local min, max = self:get_visible_line_range()
|
||||
y = y + lh * (min - 1) + style.padding.y
|
||||
max = math.min(max, self:get_line_count())
|
||||
|
||||
for i = min, max do
|
||||
local item = output[i]
|
||||
if not item then break end
|
||||
coroutine.yield(i, item, x, y, self.size.x, lh)
|
||||
y = y + lh
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:update(...)
|
||||
if self.last_output_id ~= output_id then
|
||||
if config.autoscroll_console then
|
||||
self.scroll.to.y = self:get_scrollable_size()
|
||||
end
|
||||
self.last_output_id = output_id
|
||||
end
|
||||
ConsoleView.super.update(self, ...)
|
||||
end
|
||||
|
||||
|
||||
function ConsoleView:draw()
|
||||
self:draw_background(style.background)
|
||||
local icon_w = style.icon_font:get_width("!")
|
||||
|
||||
for i, item, x, y, w, h in self:each_visible_line() do
|
||||
local tx = x + style.padding.x
|
||||
local time = os.date("%H:%M:%S", item.time)
|
||||
local color = style.text
|
||||
if self.hovered_idx == i then
|
||||
color = style.accent
|
||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||
end
|
||||
if item.text == "!DIVIDER" then
|
||||
local w = style.font:get_width(time)
|
||||
renderer.draw_rect(tx, y + h / 2, w, math.ceil(SCALE * 1), style.dim)
|
||||
else
|
||||
tx = common.draw_text(style.font, style.dim, time, "left", tx, y, w, h)
|
||||
tx = tx + style.padding.x
|
||||
if item.icon then
|
||||
common.draw_text(style.icon_font, color, item.icon, "left", tx, y, w, h)
|
||||
end
|
||||
tx = tx + icon_w + style.padding.x
|
||||
common.draw_text(style.code_font, color, item.text, "left", tx, y, w, h)
|
||||
end
|
||||
end
|
||||
|
||||
self:draw_scrollbar(self)
|
||||
end
|
||||
|
||||
|
||||
-- init static bottom-of-screen console
|
||||
local view = ConsoleView()
|
||||
local node = core.root_view:get_active_node()
|
||||
node:split("down", view, true)
|
||||
|
||||
function view:update(...)
|
||||
local dest = visible and config.console_size or 0
|
||||
self:move_towards(self.size, "y", dest)
|
||||
ConsoleView.update(self, ...)
|
||||
end
|
||||
|
||||
|
||||
local last_command = ""
|
||||
|
||||
command.add(nil, {
|
||||
["console:reset-output"] = function()
|
||||
output = { { text = "", time = 0 } }
|
||||
end,
|
||||
|
||||
["console:open-console"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
node:add_view(ConsoleView())
|
||||
end,
|
||||
|
||||
["console:toggle"] = function()
|
||||
visible = not visible
|
||||
end,
|
||||
|
||||
["console:run"] = function()
|
||||
core.command_view:set_text(last_command, true)
|
||||
core.command_view:enter("Run Console Command", function(cmd)
|
||||
console.run { command = cmd }
|
||||
last_command = cmd
|
||||
end)
|
||||
end
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["ctrl+."] = "console:toggle",
|
||||
["ctrl+shift+."] = "console:run",
|
||||
}
|
||||
|
||||
-- for `workspace` plugin:
|
||||
package.loaded["plugins.console.view"] = ConsoleView
|
||||
|
||||
console.clear()
|
||||
return console
|
|
@ -0,0 +1,37 @@
|
|||
local core = require "core"
|
||||
local keymap = require "core.keymap"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
|
||||
local function new_node() {
|
||||
return { input = "" }
|
||||
}
|
||||
|
||||
local nodes = {
|
||||
new_node()
|
||||
}
|
||||
|
||||
local ReplView = View:extend()
|
||||
|
||||
function ReplView:new()
|
||||
ReplView.super.new(self)
|
||||
self.scrollable = true
|
||||
self.brightness = 0
|
||||
-- self:begin_search(text, fn)
|
||||
end
|
||||
|
||||
local function begin_repl()
|
||||
local rv = ReplView()
|
||||
core.root_view:get_active_node():add_view(rv)
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
["repl:open"] = function()
|
||||
begin_repl()
|
||||
end
|
||||
})
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
## Steps
|
||||
|
||||
- create an object similar to a Doc (core/doc/init.lua) but not tied to a file.
|
||||
We may call it InputDoc.
|
||||
- use DocView(s) (defined in core/docview.lua) but tied to an InputDoc instead
|
||||
of to a Doc. To make this work InputDoc should implement the same methods
|
||||
and behave in the same way.
|
||||
- create a new object derived by View to "assemble" several InputDoc(s).
|
||||
The InputDoc(s) should corresponds to the input and output snippet of the
|
||||
REPL.
|
||||
We may call this view ReplView.
|
||||
The ReplView should draw its InputDoc(s) with some spacing and displatch
|
||||
text input events mouve movements etc.
|
||||
It could be similar to RootView.
|
Loading…
Reference in New Issue