Advertisement
Guest User

Jaster File Explorer v1.1

a guest
Jan 5th, 2016
918
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 19.22 KB | None | 0 0
  1. -- A small program to easily look over, and modify files.
  2.  
  3. -- CONTROLS:
  4. -- [W] and [S] to move up and down the list.
  5. -- [CTRL+W] to exit the explorer.
  6. -- [CTRL+E] to change the CWD to the selected directory
  7. -- [CTRL+Del] to delete the file/folder
  8. -- [CTRL+C] to prepare a file/folder for copying.
  9. -- [CTRL+V] to copy/move a prepared file/folder. (Requires the cursor to be over a directory)
  10. -- [C] to collapse/uncollapse a folder (Collapsed folders have a "~~" at the end of them)
  11. -- [D] to create a folder
  12. -- [F] to create a file
  13. -- [E] to open the edit program on the currently selected file
  14. -- [F3] to rename a file/folder
  15.  
  16. -- COLOURS(At least.. For 8-bit colours):
  17. -- Orange = Currently selected
  18. -- Purple = Current working directory
  19. -- Green  = Prepared to be copied/moved
  20.  
  21. -- POSSIBLE FETURES:
  22. -- A bit more colour for Tier 3/2 screens.
  23. -- Clean up the code a bit. More optimisation.
  24. -- Show the size of each file. (This one would take a decent amount of work if I wanted the text to be in line)
  25. -- That one must-need feature for this certain obscure reason that I'm satanic for not putting in.
  26.  
  27. -- BUGS:
  28. -- ... At some point change "pallet" to "palette"
  29. -- I haven't ran into anymore bugs than the ones I've fixed.
  30.  
  31. -- Imports
  32. local fs       = require("filesystem")
  33. local gpu      = require("component").gpu
  34. local term     = require("term")
  35. local shell    = require("shell")
  36. local keyboard = require("keyboard")
  37. local event    = require("event")
  38. local debug    = require("serialization")
  39.  
  40. -- Screen resolution
  41. local w,h = gpu.getResolution()
  42.  
  43. local root     = {} -- The root directory
  44. local startY   = 2  -- The Y position to start the item list
  45. local maxY     = h  -- The Y position to end the item list
  46. local toSkip   = 0  -- How many items to skip over when populating the item list
  47. local redrawSelected = false -- True if printList should only redraw the lines for the selected item(optimisation reasons).
  48.  
  49. -- The different modes printList uses
  50. local printMode_None    = 0 -- Normal mode, redraws the list one-by-one
  51. local printMode_CopyTop = 1 -- Optimisation. Copies all but the first item in the list, and redraws the copy 1 y-axis higher
  52. local printMode_CopyBot = 2 -- Optimisation. Copies all but the last item in the list, and redraws the copy 1 y-axist lower
  53. local printMode         = printMode_None
  54.  
  55. -- Item list settings
  56. local selected       = 1   -- The index of the currently selected item in the item list.
  57. local selectedItem   = {}  -- A reference to the currently selected item.
  58. local dirSettings    = {}  -- A cache to store settings of directories (such as if they're collapsed or not.) This is used to remember settings between item list refreshes
  59. local savedItem      = nil -- A reference to the item that has been "saved". Used for copying/moving
  60.  
  61. -- Save modes
  62. local saveMode_none  = 0
  63. local saveMode_copy  = 1
  64. local saveMode_move  = 2
  65. local saveMode       = saveMode_none  -- What the savedItem is being used for
  66. local modes = {[saveMode_none]="NONE", [saveMode_copy]="COPY", [saveMode_move]="MOVE"}
  67.  
  68. -- General variables
  69. local running    = true
  70. local itemList   = {} -- Contains all of the items to draw on screen.
  71.  
  72. -- Statuses
  73. local status_rename         = "ENTER NAME: "
  74. local status_errorRename    = "UNABLE TO RENAME ITEM"
  75. local status_errorDelete    = "UNABLE TO DELETE ITEM"
  76. local status_errorCopy      = "UNABLE TO COPY ITEM"
  77. local status_errorNewDir    = "UNABLE TO MAKE DIRECTORY"
  78. local status_errorNewFile   = "UNABLE TO MAKE FILE"
  79. local status_errorMove      = "UNABLE TO MOVE ITEM"
  80. local status_errorNotDir    = "PLEASE SELECT A DIR"
  81. local status_errorNotFile   = "PLEASE SELECT A FILE"
  82. local status_errorNoItem    = "PLEASE SELECT ITEM TO COPY/MOVE"
  83. local status_normal         = "Exit:{CTRL+W} Copy:{CTRL+C} Cut:{CTRL+X} Paste:{CTRL+V} Make CWD:{CTRL+E} Remove:{CTRL+Del} (Un)collapse:{C} Rename:{F3} Folder:{D} File:{F} Edit:{E}"
  84. local status                = status_normal
  85.  
  86. local version = "Jaster File Explorer v1.1"
  87.  
  88. -- All of the colours for the program
  89. local colours =
  90. {
  91.   bars          = 0,
  92.   text_normal   = 0,
  93.   text_highlight= 0,
  94.   item_cwd      = 0,
  95.   item_highlight= 0,
  96.   item_saved    = 0,
  97.  
  98.   usePallet     = false
  99. }
  100.  
  101. -- Setup all of the colours used
  102. if (gpu.getDepth() == 1) then
  103.   colours.bars             = 1
  104.   colours.text_normal      = 1
  105.   colours.text_highlight   = 0
  106.   colours.item_cwd         = 1
  107.   colours.item_highlight   = 1
  108.   colours.item_saved       = 1
  109. elseif (gpu.getDepth() == 8) then
  110.   colours.bars             = 0x00264D
  111.   colours.text_normal      = 0xFFFFFF
  112.   colours.text_highlight   = 0xFFFFFF
  113.   colours.item_cwd         = 0x660066
  114.   colours.item_highlight   = 0x803300
  115.   colours.item_saved       = 0x004400
  116. else
  117.   local col = require("colors")
  118.   colours.usePallet = true
  119.  
  120.   colours.bars             = col.blue
  121.   colours.text_normal      = col.white
  122.   colours.text_highlight   = col.white
  123.   colours.item_cwd         = col.magenta
  124.   colours.item_highlight   = col.red
  125.   colours.item_saved       = col.green
  126. end
  127.  
  128. -- Adds a directory to the list
  129. local function addDirectory(parent, dir)
  130.   local dirEntry =
  131.   {
  132.     name      = dir.."/",
  133.     isDir     = true,
  134.     collapsed = false,
  135.     children  = {},
  136.     tabCount  = 0,
  137.     parent    = parent
  138.   }
  139.  
  140.   if (dirSettings[dirEntry.name] == nil) then
  141.     dirSettings[dirEntry.name] = {collapsed = false}
  142.   end
  143.  
  144.   for entry in fs.list(dir) do
  145.     entry = fs.concat(dir, entry)
  146.  
  147.     if (fs.isDirectory(entry)) then
  148.       addDirectory(dirEntry, entry)
  149.     else
  150.       table.insert(dirEntry.children, {name=entry, isDir=false, tabCount = 0, parent = dirEntry})
  151.     end
  152.   end
  153.  
  154.   if (parent ~= nil) then
  155.     table.insert(parent.children, dirEntry)
  156.   else
  157.     root = dirEntry
  158.   end
  159. end
  160.  
  161. -- Populates the item list to contain the correct items to use.
  162. local function populateList(dir, tabCount, skipCount)
  163.   if (skipCount == toSkip) then
  164.     dir.tabCount = tabCount
  165.     table.insert(itemList, dir)
  166.   else
  167.     skipCount = skipCount + 1  
  168.   end
  169.   tabCount = tabCount + 1
  170.  
  171.   -- Get cached settings
  172.   local cache = dirSettings[dir.name]
  173.   if (cache ~= nil) then
  174.     dir.collapsed = cache.collapsed
  175.   end
  176.  
  177.   if (dir.collapsed) then return skipCount end
  178.  
  179.   for i, item in ipairs(dir.children) do
  180.     if (#itemList >= maxY-startY) then break end
  181.  
  182.     if (item.isDir) then
  183.       skipCount = populateList(item, tabCount, skipCount)
  184.     else
  185.       if (skipCount == toSkip) then
  186.         item.tabCount = tabCount
  187.         table.insert(itemList, item)
  188.       else
  189.         skipCount = skipCount + 1
  190.       end
  191.     end
  192.   end
  193.  
  194.   return skipCount
  195. end
  196.  
  197. -- Prints the item list. Sets redrawSelected to false.
  198. local function printList()
  199.   term.setCursor(1, startY)
  200.   local cwd = shell.getWorkingDirectory()
  201.   local draw = true
  202.  
  203.   -- Just so the root directory gets highlighted
  204.   if (cwd == "/") then cwd = "//" end
  205.  
  206.   -- CopyTop and CopyBot modes
  207.   -- Copying pixels that have already been drawn makes thing SO MUCH faster <3
  208.   -- Not sure if it still draws a ton of power though
  209.   if (printMode == printMode_CopyTop) then
  210.     gpu.copy(1, startY+1, w, h-(startY+1), 0, -1)
  211.   elseif (printMode == printMode_CopyBot) then
  212.     gpu.copy(1, startY, w, h-(startY+1), 0, 1)
  213.   end
  214.  
  215.   for i, item in ipairs(itemList) do
  216.     local extraText = ""
  217.     if (item.collapsed and item.isDir) then extraText = " ~~" end
  218.  
  219.     -- If we're redrawing only the selected item, then redraw the selected item, the one before it, and the one after it.
  220.     -- This is to cover all the possible lines that need to be redrawn
  221.     if (redrawSelected) then
  222.       draw = (i == selected or i == selected-1 or i==selected+1)
  223.  
  224.       -- Clear the lines we're redrawing if we're not using the normal print mode
  225.       -- This is because the copy modes sometimes mess up, and it leaves text from previous items in the wrong place.
  226.       if (draw and printMode ~= printMode_None) then
  227.         term.setCursor(1, startY + (i - 1))
  228.         term.clearLine()
  229.       end
  230.     end
  231.  
  232.     if (i == selected) then
  233.       gpu.setBackground(colours.item_highlight, colours.usePallet)
  234.       gpu.setForeground(colours.text_highlight, colours.usePallet)
  235.       selectedItem = item
  236.     elseif (item.name == cwd) then
  237.       gpu.setBackground(colours.item_cwd, colours.usePallet)
  238.       gpu.setForeground(colours.text_highlight, colours.usePallet)
  239.     elseif (savedItem == item) then
  240.       gpu.setBackground(colours.item_saved, colours.usePallet)
  241.       gpu.setForeground(colours.text_highlight, colours.usePallet)
  242.     end
  243.      
  244.     -- We want to make sure that the mode is always drawn.
  245.     if (savedItem == item) then
  246.       extraText = extraText.." ["..modes[saveMode].."]"
  247.     end
  248.  
  249.     if (draw) then
  250.       term.setCursor(1, startY + (i - 1))
  251.       local tabs = string.rep("  ", item.tabCount)
  252.  
  253.       print(tabs..item.name..extraText)
  254.     end
  255.  
  256.     draw = true
  257.     gpu.setBackground(0x000000)
  258.     gpu.setForeground(colours.text_normal, colours.usePallet)
  259.   end
  260.  
  261.   redrawSelected = false
  262.   printMode      = printMode_None
  263. end
  264.  
  265. -- Draws the gray bars to make it look pretty
  266. local function drawBars()
  267.   local old = gpu.getBackground()
  268.   gpu.setBackground(colours.bars, colours.usePallet)
  269.  
  270.   gpu.fill(1,    1,    w, 1,    " ")  -- Top bar
  271.   gpu.fill(1,    maxY, w, 1,    " ")  -- Bottom bar
  272.   --gpu.fill(barX, 1,    1, maxY, " ")  -- Middle bar
  273.  
  274.   gpu.setForeground(colours.text_highlight, colours.usePallet)
  275.   gpu.set(1,          1,     "FILES")
  276.   gpu.set(w-#version, 1,     version)
  277.   gpu.set(1,          maxY,  status)
  278.  
  279.   gpu.setBackground(old)
  280. end
  281.  
  282. -- Repopulates the entire list
  283. local function repopulate()
  284.   itemList = {}
  285.   populateList(root, 0, 0)
  286. end
  287.  
  288. -- Refreshes the list
  289. local function refresh(newRoot)
  290.   if newRoot then
  291.     savedItem = nil
  292.     addDirectory(nil, "/")
  293.   end
  294.  
  295.   status = status_normal
  296.   repopulate()
  297.  
  298.   term.clear()
  299.   printList()
  300.   drawBars()
  301. end
  302.  
  303. local function fullRefresh()
  304.   toSkip = 0
  305.   selected = 1
  306.   refresh(true)
  307. end
  308.  
  309. -- Saves/unsaves the selected item
  310. local function saveItem(mode)
  311.   if (selectedItem == savedItem and saveMode == mode) then
  312.     savedItem = nil
  313.     saveMode  = saveMode_none
  314.   else
  315.     savedItem = selectedItem
  316.     saveMode  = mode
  317.   end
  318.  
  319.   term.clear()
  320.   printList()
  321.   drawBars()
  322. end
  323.  
  324. -- Gets user input
  325. -- NOTE: Does not refresh the screen after input is entered.
  326. local function getInput(prompt)
  327.   status = prompt
  328.   drawBars()
  329.  
  330.   term.setCursor(#prompt, h)
  331.   term.setCursorBlink(true)
  332.   local input = require("text").trim(term.read())
  333.   term.setCursorBlink(false)
  334.  
  335.   return input
  336. end
  337.  
  338. -- If the name already exists, put "(1)" at the end of it.
  339. -- If THAT exists, "(2)", and so on.
  340. local function fixName(name)
  341.   local i = 0
  342.   local old = name
  343.  
  344.   while true do
  345.     if (fs.exists(name)) then
  346.       i = i + 1
  347.       name = old.."("..i..")"
  348.     else
  349.       break
  350.     end
  351.   end
  352.  
  353.   return name
  354. end
  355.  
  356. -- Sorts the children of the given directory into alphabetical order
  357. local function _sortDirectory(a, b) return a.name < b.name end
  358. local function sortDirectory(dir)
  359.   table.sort(dir.children, _sortDirectory)
  360. end
  361.  
  362. -- Removes an item from it's parent
  363. -- This function is used to avoid having to refresh the root
  364. local function removeFromParent(dir)
  365.   if (dir ~= nil and dir.parent ~= nil) then
  366.     for i, child in ipairs(dir.parent.children) do
  367.       if (child.name == dir.name) then
  368.         table.remove(dir.parent.children, i)
  369.         break
  370.       end
  371.     end
  372.   end
  373. end
  374.  
  375. -- Sets the parent for an item
  376. -- This function is used to avoid having to refresh the root
  377. local function setParent(item, parent)
  378.   removeFromParent(item)
  379.   item.parent = parent
  380.   table.insert(parent.children, item)
  381.   sortDirectory(parent)
  382. end
  383.  
  384. -- Save the previous fore and background
  385. local old_fore, old_fore_palette = gpu.getForeground()
  386. local old_back, old_back_palette = gpu.getBackground()
  387.  
  388. -- Clear the screen, refresh the list, and set the cursor into the right place
  389. refresh(true)
  390. --print(debug.serialize(root))
  391.  
  392. while running do
  393.   local _ = event.pull(0.25)
  394.  
  395.   -- Handle the CTRL commands
  396.   if (keyboard.isControlDown()) then
  397.     if (keyboard.isKeyDown(keyboard.keys.w)) then
  398.       break
  399.     elseif (keyboard.isKeyDown(keyboard.keys.e) and selectedItem.isDir) then
  400.       shell.setWorkingDirectory(selectedItem.name)
  401.       refresh(false)
  402.     elseif (keyboard.isKeyDown(keyboard.keys.delete)) then
  403.       -- If the item is saved, unsave it first
  404.       if (selectedItem == savedItem) then saveItem(saveMode) end
  405.  
  406.       local didIt, error = fs.remove(selectedItem.name)
  407.  
  408.       if (not didIt) then
  409.         status = status_errorDelete        
  410.         drawBars()
  411.       else
  412.         selected = selected - 1
  413.         removeFromParent(selectedItem) -- I remove it from the things directly, to save A LOT of time. Rather than having to refresh the root(On a Tier 3 computer, can take up to 4 seconds with only OpenOS installed).
  414.         refresh(false)
  415.       end
  416.     elseif (keyboard.isKeyDown(keyboard.keys.c)) then
  417.       saveItem(saveMode_copy)
  418.     elseif (keyboard.isKeyDown(keyboard.keys.x)) then
  419.       saveItem(saveMode_move)
  420.     elseif (keyboard.isKeyDown(keyboard.keys.v)) then
  421.       if (selectedItem.isDir and savedItem ~= nil) then
  422.         -- First, decide which function should be used.
  423.         local toUse   = nil
  424.         local message = nil
  425.         if (saveMode == saveMode_copy) then
  426.           toUse   = fs.copy
  427.           message = status_errorCopy
  428.         elseif (saveMode == saveMode_move) then
  429.           toUse   = fs.rename
  430.           message = status_errorMove
  431.         end
  432.  
  433.         -- Then use the function, and report back any errors
  434.         if (toUse ~= nil) then
  435.           local name = fixName(fs.concat(selectedItem.name, fs.name(savedItem.name)))
  436.           local didIt, msg = toUse(savedItem.name, name)
  437.  
  438.           if (not didIt) then
  439.             status = message
  440.             drawBars()
  441.           else
  442.             -- If it worked, add the new item thing the to parent. Also unsave it          
  443.  
  444.             if (fs.isDirectory(name)) then
  445.               addDirectory(selectedItem, name)
  446.             else
  447.               table.insert(selectedItem.children, {name=name, isDir=false, tabCount=0, parent=selectedItem})
  448.             end
  449.  
  450.             -- And remove the original if it no longer exists.
  451.             if (not fs.exists(savedItem.name)) then removeFromParent(savedItem) end
  452.  
  453.             saveItem(saveMode)
  454.             saveItem(saveMode)
  455.             sortDirectory(selectedItem)
  456.             refresh(false)
  457.           end
  458.         else
  459.           status = status_errorNoItem
  460.           drawBars()
  461.         end
  462.       else
  463.         if (selectedItem.isDir) then
  464.           status = status_errorNoItem
  465.         else
  466.           status = status_errorNotDir
  467.         end
  468.  
  469.         drawBars()
  470.       end
  471.     end
  472.   end
  473.  
  474.   -- Move down in the list
  475.   if (keyboard.isKeyDown(keyboard.keys.s)) then
  476.     -- If we're on the last displayed item
  477.     if (selected == #itemList) then
  478.       -- Skip the next item when repopulating
  479.       toSkip = toSkip + 1
  480.       repopulate()
  481.  
  482.       redrawSelected = true
  483.  
  484.       -- If the list ended up being smaller than before
  485.       -- Then unskip the last item, and repopulate again, before refreshing.
  486.       -- This effectively stops the user from scrolling down past the list.
  487.       if (selected > #itemList) then
  488.         toSkip   = toSkip - 1
  489.         repopulate()
  490.         selected = #itemList
  491.         printList()
  492.       else -- Otherwise, refresh.
  493.         printMode = printMode_CopyTop -- Optimisation, prevents a whole screen-redraw.
  494.         printList()
  495.       end
  496.     else -- Otherwise, just print the next item
  497.       selected = selected + 1
  498.       redrawSelected = true
  499.       printList()
  500.     end
  501.  
  502.     -- reset the status
  503.     if (status ~= status_normal) then
  504.       status = status_normal
  505.       drawBars()
  506.     end
  507.   end
  508.  
  509.   -- Rename the thing
  510.   if (keyboard.isKeyDown(keyboard.keys.f3)) then
  511.     local name = getInput("[RNM]"..status_rename)
  512.  
  513.     -- Create the new path
  514.     local path = fs.concat(fs.path(selectedItem.name), name)
  515.  
  516.     -- And attempt a rename
  517.     local result, error = fs.rename(selectedItem.name, path)    
  518.     if (not result) then
  519.       status = status_errorRename -- This never actually gets seen, because "refresh" resets the status.
  520.       refresh(false)
  521.     else
  522.       if (selectedItem.isDir) then
  523.         removeFromParent(selectedItem)
  524.         addDirectory(selectedItem.parent, path)
  525.       else
  526.         selectedItem.name = path
  527.       end      
  528.  
  529.       sortDirectory(selectedItem.parent)
  530.       refresh(false)
  531.     end
  532.   end
  533.  
  534.   -- Go up in the list
  535.   if (keyboard.isKeyDown(keyboard.keys.w)) then
  536.     if (selected-1 == 0) then -- If we're on the first item, then either scroll up if we've skipped past some, or just don't do anything.
  537.       if (toSkip ~= 0) then
  538.         toSkip = toSkip - 1
  539.  
  540.         -- Optimise the drawing
  541.         redrawSelected = true
  542.         printMode = printMode_CopyBot
  543.  
  544.         -- Repopulate, and redraw the list
  545.         repopulate()
  546.         printList()
  547.       end
  548.     else -- Otherwise, just go up an item.
  549.       selected = selected - 1
  550.       redrawSelected = true
  551.       printList()
  552.     end
  553.  
  554.     if (status ~= status_normal) then
  555.       status = status_normal
  556.       drawBars()
  557.     end
  558.   end
  559.  
  560.   if (keyboard.isKeyDown(keyboard.keys.e)) then
  561.     if (not selectedItem.isDir) then
  562.       require("os").execute("edit \""..selectedItem.name.."\"")
  563.       refresh(false)
  564.     else
  565.       status = status_errorNotFile
  566.       drawBars()
  567.     end
  568.   end
  569.  
  570.   if (keyboard.isKeyDown(keyboard.keys.d)) then
  571.     if (selectedItem.isDir) then
  572.       local name = getInput("[DIR]"..status_rename)
  573.  
  574.       name = fs.concat(selectedItem.name, name)
  575.       local didIt, msg = fs.makeDirectory(name)
  576.       if (not didIt) then
  577.         status = status_errorNewDir
  578.         refresh(false)
  579.       else
  580.         addDirectory(selectedItem, name)
  581.         sortDirectory(selectedItem)
  582.         refresh(false)
  583.       end
  584.     else
  585.       status = status_errorNotDir
  586.       drawBars()
  587.     end
  588.   end
  589.  
  590.   if (keyboard.isKeyDown(keyboard.keys.f)) then
  591.     if (selectedItem.isDir) then
  592.       local name = getInput("[FIL]"..status_rename)
  593.       name = fs.concat(selectedItem.name, name)
  594.  
  595.       local _     = fs.open(name, "w")
  596.       local didIt = (_ ~= nil)
  597.  
  598.       _:close()
  599.       if (didIt) then
  600.         table.insert(selectedItem.children, {name=name, isDir=false, tabCount=0, parent=selectedItem})
  601.         sortDirectory(selectedItem)
  602.         refresh(false)
  603.       else
  604.         status = status_errorNewFile
  605.         drawBars()
  606.       end
  607.     else
  608.       status = status_errorNotDir
  609.       drawBars()
  610.     end
  611.   end
  612.  
  613.   -- (un)Collapses the directory currently selected
  614.   if (keyboard.isKeyDown(keyboard.keys.c) and not keyboard.isControlDown()) then
  615.     if (selectedItem.isDir) then
  616.       selectedItem.collapsed = not selectedItem.collapsed
  617.       dirSettings[selectedItem.name].collapsed = selectedItem.collapsed
  618.       refresh(false)
  619.     end
  620.   end
  621.  
  622.   --status = "Selected = "..selected.."| toSkip = "..toSkip.." | #ItemList = "..#itemList.." | CWD = "..shell.getWorkingDirectory()
  623.   --drawBars()
  624. end
  625.  
  626. gpu.setBackground(old_back, old_back_palette)
  627. gpu.setForeground(old_fore, old_fore_palette)
  628. term.clear()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement