Skip to content

Commit

Permalink
Merge branch 'release/v1.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
andweeb committed Apr 8, 2019
2 parents 99db49c + b730ce4 commit 07a850a
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 25 deletions.
5 changes: 5 additions & 0 deletions docs/html/File.html
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ <h5><a href="#initialize">initialize</a></h5>
<ul>
<li><code>path</code> - The initial directory path</li>
<li><code>shortcuts</code> - The list of shortcuts containing keybindings and actions for the file entity</li>
<li><code>options</code> - A table containing various options that configures the file instance<ul>
<li><code>showHiddenFiles</code> - A flag to display hidden files in the file selection modal. Defaults to <code>false</code></li>
<li><code>sortAttribute</code> - The file attribute to sort the file selection list by. File attributes come from <a href="http://www.hammerspoon.org/docs/hs.fs.html#dir">hs.fs.dir</a>. Defaults to <code>modification</code> (last modified timestamp)</li>
</ul>
</li>
</ul>
<p>Each <code>shortcut</code> item should be a list with items at the following indices:</p>
<ul>
Expand Down
2 changes: 1 addition & 1 deletion docs/markdown/File.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ File class that subclasses [Entity](Entity.html) to represent some directory or
| **Signature** | `File:initialize(path, shortcuts)` |
| **Type** | Method |
| **Description** | Initializes a new file instance with its path and custom shortcuts. By default, a cheatsheet and common shortcuts are initialized. |
| **Parameters** | <ul><li>`path` - The initial directory path</li><li>`shortcuts` - The list of shortcuts containing keybindings and actions for the file entity</li></ul> |
| **Parameters** | <ul><li>`path` - The initial directory path</li><li>`shortcuts` - The list of shortcuts containing keybindings and actions for the file entity</li><li>`options` - A table containing various options that configures the file instance</li><li> `showHiddenFiles` - A flag to display hidden files in the file selection modal. Defaults to `false`</li><li> `sortAttribute` - The file attribute to sort the file selection list by. File attributes come from [hs.fs.dir](http://www.hammerspoon.org/docs/hs.fs.html#dir). Defaults to `modification` (last modified timestamp)</li></ul> |
| **Returns** | <ul><li>None</li></ul> |

| [move](#move) | |
Expand Down
30 changes: 25 additions & 5 deletions spec/file.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,26 @@ local function initializeTests()
{
name = "creates a file entity instance",
args = { "~/.vimrc" },
absolutePath = "/Users/test/.vimrc",
},
{
name = "creates a file entity instance",
name = "creates a directory file entity instance",
args = { "~/Downloads" },
isDirectory = true,
absolutePath = "/Users/test/Downloads",
getAttributes = function() return { mode = "directory" } end,
},
{
name = "notifies on invalid file path error",
args = { "~/.vimrc" },
absolutePath = nil,
notifiesError = true,
},
{
name = "notifies on get file attributes error",
args = { "~/.vimrc" },
getAttributes = function() return error("Unable to get file attributes") end,
absolutePath = "/Users/test/.vimrc",
notifiesError = true,
},
}

Expand All @@ -87,16 +102,21 @@ local function initializeTests()
local mockHs = hammerspoonMocker()
local file = require("file")
local mockFs = {
attributes = function() return { mode = test.isDirectory and "directory" } end,
pathToAbsolute = function() end,
attributes = test.getAttributes or function() return { mode = "file" } end,
pathToAbsolute = function() return test.absolutePath end,
}

_G.hs = mock(mockHs({ fs = mockFs }))

file.notifyError = spy.new(function() end)
file.mergeShortcuts = function() end
file:initialize(table.unpack(test.args))

assert.are.same(file.path, test.args[1])
if test.notifiesError then
assert.spy(file.notifyError).was.called(1)
else
assert.are.same(file.path, test.args[1])
end
end)
end
end
Expand Down
86 changes: 67 additions & 19 deletions src/file.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ File.behaviors = Entity.behaviors + {
--- Parameters:
--- * `path` - The initial directory path
--- * `shortcuts` - The list of shortcuts containing keybindings and actions for the file entity
--- * `options` - A table containing various options that configures the file instance
--- * `showHiddenFiles` - A flag to display hidden files in the file selection modal. Defaults to `false`
--- * `sortAttribute` - The file attribute to sort the file selection list by. File attributes come from [hs.fs.dir](http://www.hammerspoon.org/docs/hs.fs.html#dir). Defaults to `modification` (last modified timestamp)
---
--- Each `shortcut` item should be a list with items at the following indices:
--- * `1` - An optional table containing zero or more of the following keyboard modifiers: `"cmd"`, `"alt"`, `"shift"`, `"ctrl"`, `"fn"`
Expand All @@ -67,9 +70,24 @@ File.behaviors = Entity.behaviors + {
---
--- Returns:
--- * None
function File:initialize(path, shortcuts)
function File:initialize(path, shortcuts, options)
options = options or {}

local absolutePath = hs.fs.pathToAbsolute(path)
local attributes = hs.fs.attributes(absolutePath) or {}

if not absolutePath then
self.notifyError("Error initializing File entity", "Path "..path.." may not exist.")
return
end

local success, value = pcall(function() return hs.fs.attributes(absolutePath) or {} end)

if not success then
self.notifyError("Error initializing File entity", value or "")
return
end

local attributes = value
local isDirectory = attributes.mode == "directory"
local openFileInFolder = self:createEvent(path, function(target) self.open(target) end)

Expand Down Expand Up @@ -117,6 +135,8 @@ function File:initialize(path, shortcuts)
self.path = path
self.shortcuts = mergedShortcuts
self.cheatsheet = cheatsheet
self.showHiddenFiles = options.showHiddenFiles or false
self.sortAttribute = options.sortAttribute or "modification"

local cheatsheetDescription = "Ki shortcut keybindings registered for file "..self.path
self.cheatsheet:init(self.path, cheatsheetDescription, mergedShortcuts)
Expand Down Expand Up @@ -226,7 +246,6 @@ function File:showFileSelectionModal(path, handler)
local parentPathRegex = "^(.+)/.+$"
local absolutePath = hs.fs.pathToAbsolute(path)
local parentDirectory = absolutePath:match(parentPathRegex) or "/"
local iterator, directory = hs.fs.dir(absolutePath)

-- Add selection modal shortcut to open files with cmd + return
local function openFile(modal)
Expand All @@ -235,40 +254,69 @@ function File:showFileSelectionModal(path, handler)
handler(choice.filePath, true)
modal:cancel()
end
-- Add selection modal shortcut to toggle hidden files cmd + shift + "."
local function toggleHiddenFiles(modal)
modal:cancel()
self.showHiddenFiles = not self.showHiddenFiles

-- Defer execution to avoid conflicts with the prior selection modal that just closed
hs.timer.doAfter(0, function()
self:showFileSelectionModal(path, handler)
end)
end
local navigationShortcuts = {
{ { "cmd" }, "return", openFile },
{ { "cmd", "shift" }, ".", toggleHiddenFiles },
}
self.selectionModalShortcuts = self.mergeShortcuts(navigationShortcuts, self.selectionModalShortcuts)

local iterator, directory = hs.fs.dir(absolutePath)
if iterator == nil then
self.notifyError("Error walking the path at "..path)
return
end

for file in iterator, directory do
local filePath = absolutePath.."/"..file
local attributes = hs.fs.attributes(filePath) or {}
local displayName = hs.fs.displayName(filePath) or file
local isHiddenFile = string.sub(file, 1, 1) == "."
local shouldShowFile = isHiddenFile and self.showHiddenFiles or not isHiddenFile
local subText = filePath

if file == "." then
displayName = file
subText = absolutePath.." (Current directory)"
filePath = absolutePath
elseif file == ".." then
displayName = file
subText = parentDirectory.." (Parent directory)"
filePath = parentDirectory
if file ~= "." and file ~= ".." and shouldShowFile then
table.insert(choices, {
text = displayName,
subText = subText,
file = file,
filePath = filePath,
image = filePath and self.getFileIcon(filePath),
fileAttributes = attributes,
})
end

table.insert(choices, {
text = displayName,
subText = subText,
file = file,
filePath = filePath,
image = filePath and self.getFileIcon(filePath),
})
end

-- Sort choices by last modified timestamp and add current/parent directories to choices
table.sort(choices, function(a, b)
local value1 = a.fileAttributes[self.sortAttribute]
local value2 = b.fileAttributes[self.sortAttribute]
return value1 > value2
end)
table.insert(choices, {
text = "..",
subText = parentDirectory.." (Parent directory)",
file = "..",
filePath = parentDirectory,
image = self.getFileIcon(absolutePath),
})
table.insert(choices, {
text = ".",
subText = absolutePath.." (Current directory)",
file = ".",
filePath = absolutePath,
image = self.getFileIcon(absolutePath),
})

self.showSelectionModal(choices, function(choice)
if choice then
handler(choice.filePath)
Expand Down

0 comments on commit 07a850a

Please sign in to comment.