Initial commit
This commit is contained in:
375
apps/Files.lua
Normal file
375
apps/Files.lua
Normal file
@@ -0,0 +1,375 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Util = require('util')
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
|
||||
local cleanEnv = Util.shallowCopy(getfenv(1))
|
||||
cleanEnv.require = nil
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Files')
|
||||
UI:configure('Files', ...)
|
||||
|
||||
local copied = { }
|
||||
local marked = { }
|
||||
local directories = { }
|
||||
local hidden = true
|
||||
local cutMode = false
|
||||
|
||||
function formatSize(size)
|
||||
if size >= 1000000 then
|
||||
return string.format('%dM', math.floor(size/1000000, 2))
|
||||
elseif size >= 1000 then
|
||||
return string.format('%dK', math.floor(size/1000, 2))
|
||||
end
|
||||
return size
|
||||
end
|
||||
|
||||
local Browser = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = '^-', event = 'updir' },
|
||||
{ text = 'File', event = 'dropdown', dropdown = 'fileMenu' },
|
||||
{ text = 'Edit', event = 'dropdown', dropdown = 'editMenu' },
|
||||
{ text = 'View', event = 'dropdown', dropdown = 'viewMenu' },
|
||||
},
|
||||
},
|
||||
fileMenu = UI.DropMenu {
|
||||
buttons = {
|
||||
{ text = 'Run', event = 'run' },
|
||||
{ text = 'Edit e', event = 'edit' },
|
||||
{ text = 'Shell s', event = 'shell' },
|
||||
{ text = 'Quit q', event = 'quit' },
|
||||
}
|
||||
},
|
||||
editMenu = UI.DropMenu {
|
||||
buttons = {
|
||||
{ text = 'Mark m', event = 'mark' },
|
||||
{ text = 'Cut ^x', event = 'cut' },
|
||||
{ text = 'Copy ^c', event = 'copy' },
|
||||
{ text = 'Paste ^v', event = 'paste' },
|
||||
{ text = 'Delete del', event = 'delete' },
|
||||
{ text = 'Unmark all u', event = 'unmark' },
|
||||
}
|
||||
},
|
||||
viewMenu = UI.DropMenu {
|
||||
buttons = {
|
||||
{ text = 'Refresh r', event = 'refresh' },
|
||||
{ text = 'Hidden ^h', event = 'toggle_hidden' },
|
||||
}
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name', width = UI.term.width-11 },
|
||||
{ key = 'flags', width = 2 },
|
||||
{ heading = 'Size', key = 'fsize', width = 6 },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
y = 2,
|
||||
height = UI.term.height-2,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
columns = {
|
||||
{ '', 'status', UI.term.width - 19 },
|
||||
{ '', 'info', 10 },
|
||||
{ 'Size: ', 'totalSize', 8 },
|
||||
},
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
e = 'edit',
|
||||
s = 'shell',
|
||||
r = 'refresh',
|
||||
space = 'mark',
|
||||
backspace = 'updir',
|
||||
m = 'move',
|
||||
u = 'unmark',
|
||||
d = 'delete',
|
||||
delete = 'delete',
|
||||
[ 'control-h' ] = 'toggle_hidden',
|
||||
[ 'control-x' ] = 'cut',
|
||||
[ 'control-c' ] = 'copy',
|
||||
paste = 'paste',
|
||||
},
|
||||
}
|
||||
|
||||
function Browser:enable()
|
||||
UI.Page.enable(self)
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
|
||||
function Browser.grid:sortCompare(a, b)
|
||||
if self.sortColumn == 'fsize' then
|
||||
return a.size < b.size
|
||||
elseif self.sortColumn == 'flags' then
|
||||
return a.flags < b.flags
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
end
|
||||
|
||||
function Browser.grid:getRowTextColor(file, selected)
|
||||
if file.marked then
|
||||
return colors.green
|
||||
end
|
||||
if file.isDir then
|
||||
return colors.cyan
|
||||
end
|
||||
if file.isReadOnly then
|
||||
return colors.pink
|
||||
end
|
||||
return colors.white
|
||||
end
|
||||
|
||||
function Browser.grid:getRowBackgroundColorX(file, selected)
|
||||
if selected then
|
||||
return colors.gray
|
||||
end
|
||||
return self.backgroundColor
|
||||
end
|
||||
|
||||
function Browser.statusBar:draw()
|
||||
if self.parent.dir then
|
||||
local info = '#:' .. Util.size(self.parent.dir.files)
|
||||
local numMarked = Util.size(marked)
|
||||
if numMarked > 0 then
|
||||
info = info .. ' M:' .. numMarked
|
||||
end
|
||||
self:setValue('info', info)
|
||||
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
|
||||
UI.StatusBar.draw(self)
|
||||
end
|
||||
end
|
||||
|
||||
function Browser:setStatus(status, ...)
|
||||
self.statusBar:timedStatus(string.format(status, ...))
|
||||
end
|
||||
|
||||
function Browser:unmarkAll()
|
||||
for k,m in pairs(marked) do
|
||||
m.marked = false
|
||||
end
|
||||
Util.clear(marked)
|
||||
end
|
||||
|
||||
function Browser:getDirectory(directory)
|
||||
|
||||
local s, dir = pcall(function()
|
||||
|
||||
local dir = directories[directory]
|
||||
if not dir then
|
||||
dir = {
|
||||
name = directory,
|
||||
size = 0,
|
||||
files = { },
|
||||
totalSize = 0,
|
||||
index = 1
|
||||
}
|
||||
directories[directory] = dir
|
||||
end
|
||||
|
||||
self:updateDirectory(dir)
|
||||
|
||||
return dir
|
||||
end)
|
||||
|
||||
return s, dir
|
||||
end
|
||||
|
||||
function Browser:updateDirectory(dir)
|
||||
|
||||
dir.size = 0
|
||||
dir.totalSize = 0
|
||||
Util.clear(dir.files)
|
||||
|
||||
local files = fs.list(dir.name, true)
|
||||
if files then
|
||||
dir.size = #files
|
||||
for _, file in pairs(files) do
|
||||
file.fullName = fs.combine(dir.name, file.name)
|
||||
file.directory = directory
|
||||
file.flags = ''
|
||||
if not file.isDir then
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
else
|
||||
file.flags = 'D'
|
||||
end
|
||||
if file.isReadOnly then
|
||||
file.flags = file.flags .. 'R'
|
||||
end
|
||||
if not hidden or file.name:sub(1, 1) ~= '.' then
|
||||
dir.files[file.fullName] = file
|
||||
end
|
||||
end
|
||||
end
|
||||
-- self.grid:update()
|
||||
-- self.grid:setIndex(dir.index)
|
||||
self.grid:setValues(dir.files)
|
||||
end
|
||||
|
||||
function Browser:setDir(dirName, noStatus)
|
||||
|
||||
self:unmarkAll()
|
||||
|
||||
if self.dir then
|
||||
self.dir.index = self.grid:getIndex()
|
||||
end
|
||||
DIR = fs.combine('', dirName)
|
||||
shell.setDir(DIR)
|
||||
local s, dir = self:getDirectory(DIR)
|
||||
if s then
|
||||
self.dir = dir
|
||||
elseif noStatus then
|
||||
error(dir)
|
||||
else
|
||||
self:setStatus(dir)
|
||||
self:setDir('', true)
|
||||
return
|
||||
end
|
||||
|
||||
if not noStatus then
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self.statusBar:draw()
|
||||
end
|
||||
self.grid:setIndex(self.dir.index)
|
||||
end
|
||||
|
||||
function Browser:run(path, ...)
|
||||
local tabId = multishell.launch(cleanEnv, path, ...)
|
||||
multishell.setFocus(tabId)
|
||||
end
|
||||
|
||||
function Browser:hasMarked()
|
||||
if Util.size(marked) == 0 then
|
||||
local file = self.grid:getSelected()
|
||||
if file then
|
||||
file.marked = true
|
||||
marked[file.fullName] = file
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
return Util.size(marked) > 0
|
||||
end
|
||||
|
||||
function Browser:eventHandler(event)
|
||||
local file = self.grid:getSelected()
|
||||
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
|
||||
elseif event.type == 'edit' and file then
|
||||
self:run('/apps/shell', 'edit', file.name)
|
||||
|
||||
elseif event.type == 'shell' then
|
||||
self:run('/apps/shell')
|
||||
|
||||
elseif event.type == 'refresh' then
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Refreshed')
|
||||
|
||||
elseif event.type == 'toggle_hidden' then
|
||||
hidden = not hidden
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
if hidden then
|
||||
self:setStatus('Hiding hidden')
|
||||
else
|
||||
self:setStatus('Displaying hidden')
|
||||
end
|
||||
|
||||
elseif event.type == 'mark' and file then
|
||||
file.marked = not file.marked
|
||||
if file.marked then
|
||||
marked[file.fullName] = file
|
||||
else
|
||||
marked[file.fullName] = nil
|
||||
end
|
||||
self.grid:draw()
|
||||
self.statusBar:draw()
|
||||
|
||||
elseif event.type == 'unmark' then
|
||||
self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Marked files cleared')
|
||||
|
||||
elseif event.type == 'grid_select' or event.type == 'run' then
|
||||
if file then
|
||||
if file.isDir then
|
||||
self:setDir(file.fullName)
|
||||
else
|
||||
self:run('/apps/shell', file.name)
|
||||
end
|
||||
end
|
||||
|
||||
elseif event.type == 'updir' then
|
||||
local dir = (self.dir.name:match("(.*/)"))
|
||||
self:setDir(dir or '/')
|
||||
|
||||
elseif event.type == 'delete' then
|
||||
if self:hasMarked() then
|
||||
local width = self.statusBar:getColumnWidth('status')
|
||||
self.statusBar:setColumnWidth('status', UI.term.width)
|
||||
self.statusBar:setValue('status', 'Delete marked? (y/n)')
|
||||
self.statusBar:draw()
|
||||
self.statusBar:sync()
|
||||
local _, ch = os.pullEvent('char')
|
||||
if ch == 'y' or ch == 'Y' then
|
||||
for k,m in pairs(marked) do
|
||||
pcall(function()
|
||||
fs.delete(m.fullName)
|
||||
end)
|
||||
end
|
||||
end
|
||||
marked = { }
|
||||
self.statusBar:setColumnWidth('status', width)
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self:updateDirectory(self.dir)
|
||||
|
||||
self.statusBar:draw()
|
||||
self.grid:draw()
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
|
||||
elseif event.type == 'copy' or event.type == 'cut' then
|
||||
if self:hasMarked() then
|
||||
cutMode = event.type == 'cut'
|
||||
Util.clear(copied)
|
||||
Util.merge(copied, marked)
|
||||
--self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Copied %d file(s)', Util.size(copied))
|
||||
end
|
||||
|
||||
elseif event.type == 'paste' then
|
||||
for k,m in pairs(copied) do
|
||||
local s, m = pcall(function()
|
||||
if cutMode then
|
||||
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
else
|
||||
fs.copy(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
end
|
||||
end)
|
||||
end
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Pasted ' .. Util.size(copied) .. ' file(s)')
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
self:setFocus(self.grid)
|
||||
return true
|
||||
end
|
||||
|
||||
--[[-- Startup logic --]]--
|
||||
local args = { ... }
|
||||
|
||||
Browser:setDir(args[1] or shell.dir())
|
||||
|
||||
UI:setPage(Browser)
|
||||
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
||||
Reference in New Issue
Block a user