Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Template for new versions:
## New Tools

## New Features
- `gui/quickcmd`: added custom command names and option to display command output

## Fixes

Expand Down
169 changes: 149 additions & 20 deletions gui/quickcmd.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,33 @@ local json = require('json')
local gui = require('gui')
local widgets = require('gui.widgets')

local CONFIG_FILE = 'dfhack-config/quickcmd.json'
local CONFIG_FILE_OLD = 'dfhack-config/quickcmd.json'
local CONFIG_FILE = 'dfhack-config/quickcmd-v2.json'
local HOTKEYWIDTH = 7
local OUTWIDTH = 4
local HOTKEYS = 'asdfghjklqwertyuiopzxcvbnm'

local function save_commands(data)
json.encode_file(data, CONFIG_FILE)
end

local function load_commands()
-- Try to load from new config file first
local ok, data = pcall(json.decode_file, CONFIG_FILE)
return ok and data or {}
end
if ok then return data end

local function save_commands(data)
json.encode_file(data, CONFIG_FILE)
-- New file doesn't exist or is invalid - try old file for migration
ok, data = pcall(json.decode_file, CONFIG_FILE_OLD)
if not ok then return {} end

-- Migrate old string format to new object format (in-memory only)
for i, cmd in ipairs(data) do
if type(cmd) == 'string' then
data[i] = {command = cmd, name = '', show_output = false}
end
end

return data
end

QCMDDialog = defclass(QCMDDialog, widgets.Window)
Expand All @@ -61,12 +77,12 @@ function QCMDDialog:init(info)
self:addviews{
widgets.Label{
frame={t=0},
text={{text='Hotkey', width=HOTKEYWIDTH}, ' Command'},
text={{text='Hotkey', width=HOTKEYWIDTH}, {text='Out', width=OUTWIDTH}, 'Name/Command'},
visible=function() return #self.commands > 0 end,
},
widgets.List{
view_id='list',
frame={t=2, b=3},
frame={t=2, b=4},
on_submit=self:callback('submit'),
},
widgets.Label{
Expand All @@ -75,53 +91,132 @@ function QCMDDialog:init(info)
visible=function() return #self.commands == 0 end,
},
widgets.HotkeyLabel{
frame={b=1, l=0},
frame={b=2, l=0},
key='CUSTOM_SHIFT_A',
label='Add command',
auto_width=true,
on_activate=self:callback('onAddCommand'),
},
widgets.HotkeyLabel{
frame={b=1, l=19},
frame={b=2, l=19},
key='CUSTOM_SHIFT_D',
label='Delete command',
auto_width=true,
on_activate=self:callback('onDelCommand'),
},
widgets.HotkeyLabel{
frame={b=0, l=0},
frame={b=1, l=0},
key='CUSTOM_SHIFT_E',
label='Edit command',
auto_width=true,
on_activate=self:callback('onEditCommand'),
},
widgets.HotkeyLabel{
frame={b=1, l=19},
key='CUSTOM_SHIFT_N',
label='Edit name',
auto_width=true,
on_activate=self:callback('onSetName'),
},
widgets.HotkeyLabel{
frame={b=0, l=0},
key='CUSTOM_SHIFT_O',
label='Capture output',
auto_width=true,
on_activate=self:callback('onToggleOutput'),
},
}

self:updateList()
end

function QCMDDialog:submit(idx, choice)
local cmd_obj = self.commands[idx]

if cmd_obj.show_output then
self:showCommandOutput(cmd_obj.command, cmd_obj.name)
else
local screen = self.parent_view
dfhack.screen.hideGuard(screen, function()
dfhack.run_command(cmd_obj.command)
end)
screen:dismiss()
end
end

function QCMDDialog:showCommandOutput(command, name)
local output = dfhack.run_command_silent(command)

-- Dismiss the quickcmd dialog before showing output
local screen = self.parent_view
dfhack.screen.hideGuard(screen, function()
dfhack.run_command(choice.command)
end)
screen:dismiss()

local OutputDialog = defclass(OutputDialog, gui.ZScreenModal)
OutputDialog.ATTRS{
focus_path='quickcmd_output',
command='',
name='',
output='',
}

function OutputDialog:init()
local title
if self.name and self.name ~= '' then
title = self.name .. ': ' .. self.command
else
title = self.command
end
self:addviews{
widgets.Window{
frame_title=title,
frame={w=80, h=25},
resizable=true,
resize_min={h=10, w=40},
subviews={
widgets.WrappedLabel{
view_id='output',
frame={t=0, l=0, r=0, b=2},
text_to_wrap=self.output or 'No output',
scroll_keys=widgets.STANDARDSCROLL,
},
widgets.HotkeyLabel{
frame={b=0, l=0},
key='LEAVESCREEN',
label='Close',
auto_width=true,
on_activate=self:callback('dismiss'),
},
}
}
}
end

if #output == 0 then
output = 'Command finished successfully'
end

OutputDialog{command=command, name=name, output=output}:show()
end

function QCMDDialog:updateList()
-- Build the list entries.
local choices = {}
for i,command in ipairs(self.commands) do
for i,cmd_obj in ipairs(self.commands) do
-- Get the hotkey for this entry.
local hotkey = nil
if i <= HOTKEYS:len() then
hotkey = HOTKEYS:sub(i, i)
end

-- Display name if set, otherwise display command
local display_text = cmd_obj.name and cmd_obj.name ~= '' and cmd_obj.name or cmd_obj.command

-- Store the entry.
table.insert(choices, {
text={{text=hotkey or '', width=HOTKEYWIDTH}, ' ', command},
command=command,
text={{text=hotkey or '', width=HOTKEYWIDTH}, {text=cmd_obj.show_output and '[X]' or '[ ]', width=OUTWIDTH}, display_text},
command=cmd_obj.command,
name=cmd_obj.name,
show_output=cmd_obj.show_output,
hotkey=hotkey and ('CUSTOM_' .. hotkey:upper()) or '',
})
end
Expand All @@ -148,7 +243,7 @@ function QCMDDialog:onAddCommand()
COLOR_GREEN,
'',
function(command)
table.insert(self.commands, command)
table.insert(self.commands, {command=command, name='', show_output=false})
save_commands(self.commands)
self:updateList()
end
Expand All @@ -165,7 +260,7 @@ function QCMDDialog:onDelCommand()
-- Prompt for confirmation.
dlg.showYesNoPrompt(
'Delete command',
'Are you sure you want to delete this command: ' .. NEWLINE .. item.command,
'Are you sure you want to delete this command: ' .. NEWLINE .. self.commands[index].command,
COLOR_GREEN,
function()
table.remove(self.commands, index)
Expand All @@ -187,15 +282,49 @@ function QCMDDialog:onEditCommand()
'Edit command',
'Enter command:',
COLOR_GREEN,
item.command,
self.commands[index].command,
function(command)
self.commands[index] = command
self.commands[index].command = command
save_commands(self.commands)
self:updateList()
end
)
end

function QCMDDialog:onSetName()
-- Get the selected command.
local index, item = self.subviews.list:getSelected()
if not item then
return
end

-- Prompt for new name.
dlg.showInputPrompt(
'Set name',
'Enter name:',
COLOR_GREEN,
self.commands[index].name or '',
function(name)
self.commands[index].name = name
save_commands(self.commands)
self:updateList()
end
)
end

function QCMDDialog:onToggleOutput()
-- Get the selected command.
local index, item = self.subviews.list:getSelected()
if not item then
return
end

-- Toggle the show_output flag.
self.commands[index].show_output = not self.commands[index].show_output
save_commands(self.commands)
self:updateList()
end

QCMDScreen = defclass(QCMDScreen, gui.ZScreen)
QCMDScreen.ATTRS {
focus_path='quickcmd',
Expand Down