From 20ddad2951dcabeb7499684aaf2161cfb60033fd Mon Sep 17 00:00:00 2001 From: smartscripts-nl Date: Wed, 2 Sep 2020 16:09:27 +0200 Subject: [PATCH] Terminal: add shortcuts for re-useable commands (#6609) To be able to quickly repeat often used terminal commands --- frontend/dispatcher.lua | 2 +- plugins/terminal.koplugin/main.lua | 437 +++++++++++++++++++++++++++-- 2 files changed, 408 insertions(+), 31 deletions(-) diff --git a/frontend/dispatcher.lua b/frontend/dispatcher.lua index 03667fce7..cf2b5c9ba 100644 --- a/frontend/dispatcher.lua +++ b/frontend/dispatcher.lua @@ -77,7 +77,7 @@ local settingsList = { calibre_search = { category="none", event="CalibreSearch", title=_("Search in calibre metadata"), device=true,}, calibre_browse_tags = { category="none", event="CalibreBrowseTags", title=_("Browse all calibre tags"), device=true,}, calibre_browse_series = { category="none", event="CalibreBrowseSeries", title=_("Browse all calibre series"), device=true, separator=true,}, - edit_last_edited_file = { category = "none", event = "OpenLastEditedFile", title = 'Texteditor: open last file', device = true, separator = true, }, + edit_last_edited_file = { category = "none", event = "OpenLastEditedFile", title = _("Texteditor: open last file"), device = true, separator = true, }, favorites = { category="none", event="ShowColl", arg="favorites", title=_("Favorites"), device=true,}, -- filemanager settings diff --git a/plugins/terminal.koplugin/main.lua b/plugins/terminal.koplugin/main.lua index 8f37f17c1..6ea8c711d 100644 --- a/plugins/terminal.koplugin/main.lua +++ b/plugins/terminal.koplugin/main.lua @@ -1,7 +1,13 @@ +local ButtonDialog = require("ui/widget/buttondialog") +local CenterContainer = require("ui/widget/container/centercontainer") local DataStorage = require("datastorage") local Font = require("ui/font") local InfoMessage = require("ui/widget/infomessage") local InputDialog = require("ui/widget/inputdialog") +local LuaSettings = require("luasettings") +local Menu = require("ui/widget/menu") +local Screen = require("device").screen +local T = require("ffi/util").template local TextViewer = require("ui/widget/textviewer") local Trapper = require("ui/trapper") local UIManager = require("ui/uimanager") @@ -9,49 +15,388 @@ local WidgetContainer = require("ui/widget/container/widgetcontainer") local logger = require("logger") local util = require("ffi/util") local _ = require("gettext") -local Screen = require("device").screen local Terminal = WidgetContainer:new{ name = "terminal", - dump_file = util.realpath(DataStorage:getDataDir()) .. "/terminal_output.txt", command = "", + dump_file = util.realpath(DataStorage:getDataDir()) .. "/terminal_output.txt", + items_per_page = G_reader_settings:readSetting("items_per_page") or 16, + settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/terminal_shortcuts.lua"), + shortcuts_dialog = nil, + shortcuts_menu = nil, + -- shortcuts_file = DataStorage:getSettingsDir() .. "/terminal_shortcuts.lua", + shortcuts = {}, + source = "terminal", } function Terminal:init() self.ui.menu:registerToMainMenu(self) + self.shortcuts = self.settings:readSetting("shortcuts") or {} +end + +function Terminal:saveShortcuts() + self.settings:saveSetting("shortcuts", self.shortcuts) + self.settings:flush() + UIManager:show(InfoMessage:new{ + text = _("Shortcuts saved"), + timeout = 2 + }) +end + +function Terminal:manageShortcuts() + self.shortcuts_dialog = CenterContainer:new { + dimen = Screen:getSize(), + } + self.shortcuts_menu = Menu:new{ + show_parent = self.ui, + width = Screen:getWidth(), + height = Screen:getHeight(), + covers_fullscreen = true, -- hint for UIManager:_repaint() + is_borderless = true, + is_popout = false, + perpage = self.items_per_page, + onMenuHold = self.onMenuHoldShortcuts, + _manager = self, + } + table.insert(self.shortcuts_dialog, self.shortcuts_menu) + self.shortcuts_menu.close_callback = function() + UIManager:close(self.shortcuts_dialog) + end + + -- sort the shortcuts: + if #self.shortcuts > 0 then + table.sort(self.shortcuts, function(v1, v2) + return v1.text < v2.text + end) + end + self:updateItemTable() +end + +function Terminal:updateItemTable() + local item_table = {} + if #self.shortcuts > 0 then + local actions_count = 3 -- separator + actions + for nr, f in ipairs(self.shortcuts) do + local item = { + nr = nr, + text = f.text, + commands = f.commands, + editable = true, + deletable = true, + callback = function() + -- so we know which middle button to display in the results: + self.source = "shortcut" + -- execute immediately, skip terminal dialog: + self.command = self:ensureWhitelineAfterCommands(f.commands) + Trapper:wrap(function() + self:execute() + end) + end + } + table.insert(item_table, item) + -- add page actions at end of each page with shortcuts: + local factor = self.items_per_page - actions_count + if nr % factor == 0 or nr == #self.shortcuts then + -- insert "separator": + table.insert(item_table, { + text = " ", + deletable = false, + editable = false, + callback = function() + self:manageShortcuts() + end, + }) + -- actions: + self:insertPageActions(item_table) + end + end + -- no shortcuts defined yet: + else + self:insertPageActions(item_table) + end + local title = #self.shortcuts == 1 and _("Terminal shortcut") or _("Terminal shortcuts") + self.shortcuts_menu:switchItemTable(tostring(#self.shortcuts) .. " " .. title, item_table) + UIManager:show(self.shortcuts_dialog) +end + +function Terminal:insertPageActions(item_table) + table.insert(item_table, { + text = " " .. _("to terminal…"), + deletable = false, + editable = false, + callback = function() + self:terminal() + end, + }) + table.insert(item_table, { + text = " " .. _("close…"), + deletable = false, + editable = false, + callback = function() + return false + end, + }) +end + +function Terminal:onMenuHoldShortcuts(item) + if item.deletable or item.editable then + local shortcut_shortcuts_dialog + shortcut_shortcuts_dialog = ButtonDialog:new{ + buttons = {{ + { + text = _("Edit name"), + enabled = item.editable, + callback = function() + UIManager:close(shortcut_shortcuts_dialog) + if self._manager.shortcuts_dialog ~= nil then + UIManager:close(self._manager.shortcuts_dialog) + self._manager.shortcuts_dialog = nil + end + self._manager:editName(item) + end + }, + { + text = _("Edit commands"), + enabled = item.editable, + callback = function() + UIManager:close(shortcut_shortcuts_dialog) + if self._manager.shortcuts_dialog ~= nil then + UIManager:close(self._manager.shortcuts_dialog) + self._manager.shortcuts_dialog = nil + end + self._manager:editCommands(item) + end + }, + }, + { + { + text = _("Copy"), + enabled = item.editable, + callback = function() + UIManager:close(shortcut_shortcuts_dialog) + if self._manager.shortcuts_dialog ~= nil then + UIManager:close(self._manager.shortcuts_dialog) + self._manager.shortcuts_dialog = nil + end + self._manager:copyCommands(item) + end + }, + { + text = _("Delete"), + enabled = item.deletable, + callback = function() + UIManager:close(shortcut_shortcuts_dialog) + if self._manager.shortcuts_dialog ~= nil then + UIManager:close(self._manager.shortcuts_dialog) + self._manager.shortcuts_dialog = nil + end + self._manager:deleteShortcut(item) + end + } + }} + } + UIManager:show(shortcut_shortcuts_dialog) + return true + end +end + +function Terminal:copyCommands(item) + local new_item = { + text = item.text .. " (copy)", + commands = item.commands + } + table.insert(self.shortcuts, new_item) + UIManager:show(InfoMessage:new{ + text = _("Shortcut copied"), + timeout = 2 + }) + self:saveShortcuts() + self:manageShortcuts() +end + +function Terminal:editCommands(item) + local edit_dialog + edit_dialog = InputDialog:new{ + title = T(_('Edit commands for "%1"'), item.text), + input = item.commands, + width = Screen:getWidth() * 0.9, + para_direction_rtl = false, -- force LTR + input_type = "string", + allow_newline = true, + cursor_at_end = true, + fullscreen = true, + buttons = {{{ + text = _("Cancel"), + callback = function() + UIManager:close(edit_dialog) + edit_dialog = nil + self:manageShortcuts() + end, + }, { + text = _("Save"), + callback = function() + local input = edit_dialog:getInputText() + UIManager:close(edit_dialog) + edit_dialog = nil + if input:match("[A-Za-z]") then + self.shortcuts[item.nr]["commands"] = input + self:saveShortcuts() + self:manageShortcuts() + end + end, + }}}, + } + UIManager:show(edit_dialog) + edit_dialog:onShowKeyboard() +end + +function Terminal:editName(item) + local edit_dialog + edit_dialog = InputDialog:new{ + title = _("Edit name"), + input = item.text, + width = Screen:getWidth() * 0.9, + para_direction_rtl = false, -- force LTR + input_type = "string", + allow_newline = false, + cursor_at_end = true, + fullscreen = true, + buttons = {{{ + text = _("Cancel"), + callback = function() + UIManager:close(edit_dialog) + edit_dialog = nil + self:manageShortcuts() + end, + }, { + text = _("Save"), + callback = function() + local input = edit_dialog:getInputText() + UIManager:close(edit_dialog) + edit_dialog = nil + if input:match("[A-Za-z]") then + self.shortcuts[item.nr]["text"] = input + self:saveShortcuts() + self:manageShortcuts() + end + end, + }}}, + } + UIManager:show(edit_dialog) + edit_dialog:onShowKeyboard() +end + +function Terminal:deleteShortcut(item) + local shortcuts = {} + for _, element in ipairs(self.shortcuts) do + if element.text ~= item.text and element.commands ~= item.commands then + table.insert(shortcuts, element) + end + end + self.shortcuts = shortcuts + self:saveShortcuts() + self:manageShortcuts() end function Terminal:onTerminalStart() + -- if shortcut commands are defined, go directly to the the shortcuts manager (so we can execute scripts more quickly): + if #self.shortcuts == 0 then + self:terminal() + else + self:manageShortcuts() + end +end + +function Terminal:terminal() self.input = InputDialog:new{ - title = _("Enter a command and press \"Execute\""), - input = self.command, + title = _("Enter a command and press \"Execute\""), + input = self.command:gsub("\n+$", ""), para_direction_rtl = false, -- force LTR - text_height = math.floor(Screen:getHeight() * 0.4), input_type = "string", - -- allow multiple lines with commands: allow_newline = true, cursor_at_end = true, + fullscreen = true, buttons = {{{ - text = _("Cancel"), - callback = function() - UIManager:close(self.input) - end, - }, { - text = _("Execute"), - callback = function() - UIManager:close(self.input) - Trapper:wrap(function() - self:execute() - end) - end, - }}}, + text = _("Cancel"), + callback = function() + UIManager:close(self.input) + end, + }, { + text = _("Shortcuts"), + callback = function() + UIManager:close(self.input) + self:manageShortcuts() + end, + }, { + text = _("Save"), + callback = function() + local input = self.input:getInputText() + if input:match("[A-Za-z]") then + + local function callback(name) + local new_shortcut = { + text = name, + commands = input, + } + table.insert(self.shortcuts, new_shortcut) + self:saveShortcuts() + end + + local prompt + prompt = InputDialog:new{ + title = _("Name"), + input = "", + input_type = "text", + fullscreen = true, + condensed = true, + allow_newline = false, + cursor_at_end = true, + buttons = {{{ + text = _("Cancel"), + callback = function() + UIManager:close(prompt) + end, + }, + { + text = _("Save"), + is_enter_default = true, + callback = function() + local newval = prompt:getInputText() + UIManager:close(prompt) + callback(newval) + end, + }}} + } + UIManager:show(prompt) + prompt:onShowKeyboard() + end + end, + }, { + text = _("Execute"), + callback = function() + UIManager:close(self.input) + -- so we know which middle button to display in the results: + self.source = "terminal" + self.command = self:ensureWhitelineAfterCommands(self.input:getInputText()) + Trapper:wrap(function() + self:execute() + end) + end, + }}}, } UIManager:show(self.input) self.input:onShowKeyboard() end +-- for prettier formatting of output by separating commands and result thereof with a whiteline: +function Terminal:ensureWhitelineAfterCommands(commands) + if string.sub(commands, -1) ~= "\n" then + commands = commands .. "\n" + end + return commands +end + function Terminal:execute() - self.command = self.input:getInputText() local wait_msg = InfoMessage:new{ text = _("Executing…"), } @@ -69,29 +414,61 @@ function Terminal:execute() end UIManager:close(wait_msg) local viewer - viewer = TextViewer:new { - title = _("Command output"), - text = table.concat(entries, "\n"), - justified = false, - text_face = Font:getFace("smallinfont"), - -- support re-invoking the terminal with a button: + local buttons_table + local back_button = { + text = _("Back"), + callback = function() + UIManager:close(viewer) + if self.source == "terminal" then + self:terminal() + else + self:manageShortcuts() + end + end, + } + local close_button = { + text = _("Close"), + callback = function() + UIManager:close(viewer) + end, + } + if self.source == "terminal" then buttons_table = { { + back_button, { - text = _("Back"), + text = _("Shortcuts"), + -- switch to shortcuts: callback = function() UIManager:close(viewer) - self:onTerminalStart() + self:manageShortcuts() end, }, + close_button, + }, + } + else + buttons_table = { + { + back_button, { - text = _("Close"), + text = _("Terminal"), + -- switch to terminal: callback = function() UIManager:close(viewer) + self:terminal() end, }, + close_button, }, - }, + } + end + viewer = TextViewer:new{ + title = _("Command output"), + text = table.concat(entries, "\n"), + justified = false, + text_face = Font:getFace("smallinfont"), + buttons_table = buttons_table, } UIManager:show(viewer) end